Support for chrono (#39) and jiff #88
Conversation
…ate_time_fixed_offset
|
🫣 |
|
This would be lovely to have, nice work 👍🏻 |
|
Sorry for the delay. I took a cursory look and found lots of I don't work with date libraries very often (I always use Unix time in my code) so I could be wrong about the existence or scope of the problem here.
TODO for usIt might be a good idea to introduce Finally, for code style, we would prefer if each date library used its own date types ( |
Sorry for the delay.
Here is what I need to do:
Is my understanding correct? Best regards |
To be clear, the
What I meant was
Yes, please use separate types for the crate (whether from the crate or new types in |
- Separate chrono and time feature support by moving chrono-specific code into dedicated modules/files and removing the shared Date component - Implement conversion from DateTimeDecode to chrono::NaiveDateTime and remove the previous conversion from DateTimeEncode - Remove support for chrono::DateTime<FixedOffset> - Add fuzz testing for: - chrono::NaiveTime - chrono::NaiveDate - chrono::NaiveDateTime - chrono::DateTime<Utc> This change improves modularity, eliminates unnecessary shared types, and strengthens testing coverage for the remaining chrono types.
…oftbearStudios#90) * Appease clippy. * Switch CI to a big endian arch that has rust-std component. * Remove unstable error number from test.
…ate_time_fixed_offset
…ate_time_fixed_offset
- Separate chrono and time feature support by moving chrono-specific code into dedicated modules/files and removing the shared Date component - Implement conversion from DateTimeDecode to chrono::NaiveDateTime and remove the previous conversion from DateTimeEncode - Remove support for chrono::DateTime<FixedOffset> - Add fuzz testing for: - chrono::NaiveTime - chrono::NaiveDate - chrono::NaiveDateTime - chrono::DateTime<Utc> This change improves modularity, eliminates unnecessary shared types, and strengthens testing coverage for the remaining chrono types.
…ate_time_fixed_offset
|
@finnbear It seems that if we want to use TryFrom, both Decoder and Decode need to be changed… impl<'a, F: TryConvertFrom<T>, T: Decode<'a>> TryDecoder<'a, F> for TryConvertFromDecoder<'a, T> {
#[inline(always)]
fn try_decode(&mut self) -> Result<F, crate::Error> {
F::try_convert_from(self.0.decode())
}
}It seems that, if we don’t consider bit flips or data tampering, using unwrap is safe, since we’re just decoding data that we ourselves encoded. Also, implementing it in chrono |
I agree |
|
@finnbear It seems like I don't need ConvertTryFrom anymore collapsed legacy code```rust use crate::{ coder::{Decoder, View}, convert::{impl_convert, ConvertFrom, ConvertIntoEncoder}, error::error, int::{ranged_int, IntDecoder}, Decode, Encode, }; use jiff::{ civil::{DateTime, Time}, tz::TimeZone, Timestamp, };// The value is guaranteed to be in the range type TimeEncode = (u8, u8, u8, u32); impl ConvertFrom<&Time> for TimeEncode { impl ConvertFrom for Time { type TimeStampEncode = (i64, i32); impl ConvertFrom<&Timestamp> for TimeStampEncode { impl Encode for Timestamp { #[derive(Default)] impl<'a> View<'a> for TimestampDecoder { } impl<'a> Decoder<'a, Timestamp> for TimestampDecoder { impl<'a> Decode<'a> for Timestamp { #[cfg(test)] } use crate::{
coder::{Decoder, View},
convert::{ConvertFrom, ConvertIntoEncoder},
error::error,
int::{ranged_int, IntDecoder},
Decode, Encode,
};
use jiff::{civil::Time, Timestamp};
// The value is guaranteed to be in the range `0..=23`.
ranged_int!(Hour, u8, 0, 23);
// The value is guaranteed to be in the range `0..=59`.
ranged_int!(Minute, u8, 0, 59);
// The value is guaranteed to be in the range `0..=59`.
ranged_int!(Second, u8, 0, 59);
// The value is guaranteed to be in the range `0..=999_999_999`
ranged_int!(Nanosecond, u32, 0, 999_999_999);
type TimeEncode = (u8, u8, u8, u32);
type TimeDecode = (Hour, Minute, Second, Nanosecond);
impl ConvertFrom<&Time> for TimeEncode {
fn convert_from(value: &Time) -> Self {
(
value.hour() as u8,
value.minute() as u8,
value.second() as u8,
value.subsec_nanosecond() as u32,
)
}
}
impl ConvertFrom<TimeDecode> for Time {
fn convert_from(value: TimeDecode) -> Self {
Time::constant(
value.0.into_inner() as i8,
value.1.into_inner() as i8,
value.2.into_inner() as i8,
value.3.into_inner() as i32,
)
}
}
type TimeStampEncode = (i64, i32);
impl ConvertFrom<&Timestamp> for TimeStampEncode {
fn convert_from(value: &Timestamp) -> Self {
(value.as_second(), value.subsec_nanosecond())
}
}
impl Encode for Timestamp {
type Encoder = ConvertIntoEncoder<TimeStampEncode>;
}
#[derive(Default)]
pub struct TimestampDecoder {
seconds: Vec<i64>,
nano: Vec<i32>,
pos: usize,
}
impl<'a> View<'a> for TimestampDecoder {
fn populate(&mut self, input: &mut &'a [u8], length: usize) -> Result<(), crate::Error> {
self.seconds.clear();
self.nano.clear();
self.pos = 0;
let mut sec_decoder = IntDecoder::<i64>::default();
sec_decoder.populate(input, length)?;
let mut nano_decoder = IntDecoder::<i32>::default();
nano_decoder.populate(input, length)?;
const SEC_MIN: i64 = -377705023201;
const SEC_MAX: i64 = 253402207200;
const NANOS_MIN: i32 = -999_999_999;
const NANOS_MAX: i32 = 999_999_999;
for _ in 0..length {
let sec: i64 = sec_decoder.decode();
let nano: i32 = nano_decoder.decode();
if !(SEC_MIN..=SEC_MAX).contains(&sec) {
return Err(error("seconds out of range"));
}
if !(NANOS_MIN..=NANOS_MAX).contains(&nano) {
return Err(error("nanoseconds out of range"));
}
if sec == SEC_MIN && nano < 0 {
return Err(error("nanoseconds must be >=0 when seconds are minimal"));
}
self.seconds.push(sec);
self.nano.push(nano);
}
// self.seconds = seconds;
// self.nano = nanos;
Ok(())
}
}
impl<'a> Decoder<'a, Timestamp> for TimestampDecoder {
fn decode(&mut self) -> Timestamp {
let sec = self.seconds[self.pos];
let nano = self.nano[self.pos];
self.pos += 1;
Timestamp::constant(sec, nano)
}
}
impl<'a> Decode<'a> for Timestamp {
type Decoder = TimestampDecoder;
}
#[cfg(test)]
mod tests {
#[test]
fn test() {
// 合法:正常秒和纳秒
let bytes = bitcode::encode(&(0i64, 0i32));
let ts: Timestamp = bitcode::decode(&bytes).unwrap();
assert_eq!(ts, Timestamp::new(0, 0).unwrap());
// 合法:秒最小值,纳秒 0
let bytes = bitcode::encode(&(UNIX_SECONDS_MIN, 0i32));
let ts: Timestamp = bitcode::decode(&bytes).unwrap();
assert_eq!(ts, Timestamp::new(UNIX_SECONDS_MIN, 0).unwrap());
// 合法:秒最大值,纳秒最大值
let bytes = bitcode::encode(&(UNIX_SECONDS_MAX, NANOS_MAX));
let ts: Timestamp = bitcode::decode(&bytes).unwrap();
assert_eq!(ts, Timestamp::new(UNIX_SECONDS_MAX, NANOS_MAX).unwrap());
// 非法:秒小于最小值
let bytes = bitcode::encode(&(UNIX_SECONDS_MIN - 1, 0i32));
let result: Result<Timestamp, _> = bitcode::decode(&bytes);
assert!(result.is_err());
// 非法:秒大于最大值
let bytes = bitcode::encode(&(UNIX_SECONDS_MAX + 1, 0i32));
let result: Result<Timestamp, _> = bitcode::decode(&bytes);
assert!(result.is_err());
// 非法:纳秒小于最小值(-1_000_000_000)
let bytes = bitcode::encode(&(0i64, -1_000_000_000i32));
let result: Result<Timestamp, _> = bitcode::decode(&bytes);
assert!(result.is_err());
// 非法:纳秒大于最大值(1_000_000_000)
let bytes = bitcode::encode(&(0i64, 1_000_000_000i32));
let result: Result<Timestamp, _> = bitcode::decode(&bytes);
assert!(result.is_err());
// 非法:秒为最小值,纳秒为负(组合约束)
let bytes = bitcode::encode(&(UNIX_SECONDS_MIN, -1i32));
let result: Result<Timestamp, _> = bitcode::decode(&bytes);
assert!(result.is_err());
// 合法:秒为最小值+1,纳秒为负
let bytes = bitcode::encode(&(UNIX_SECONDS_MIN + 1, -500i32));
let ts: Timestamp = bitcode::decode(&bytes).unwrap();
assert_eq!(ts, Timestamp::new(UNIX_SECONDS_MIN + 1, -500).unwrap());
// 合法:秒远大于最小值,纳秒为负
let bytes = bitcode::encode(&(1000i64, NANOS_MIN));
let ts: Timestamp = bitcode::decode(&bytes).unwrap();
assert_eq!(ts, Timestamp::new(1000, -999_999_999).unwrap());
// 合法:秒为最小值,纳秒为 0(已测)
// 合法:秒为最小值,纳秒为正
let bytes = bitcode::encode(&(UNIX_SECONDS_MIN, 500i32));
let ts: Timestamp = bitcode::decode(&bytes).unwrap();
assert_eq!(ts, Timestamp::new(UNIX_SECONDS_MIN, 500).unwrap());
assert!(crate::decode::<Timestamp>(&crate::encode(&Timestamp::now())).is_ok());
}
use alloc::vec::Vec;
use jiff::Timestamp;
const UNIX_SECONDS_MIN: i64 = -377705023201;
const UNIX_SECONDS_MAX: i64 = 253402207200;
const NANOS_MIN: i32 = -999_999_999;
const NANOS_MAX: i32 = 999_999_999;
fn bench_data() -> Vec<Timestamp> {
crate::random_data(1000)
.into_iter()
.map(|(s, n): (i64, i32)| Timestamp::new(s % UNIX_SECONDS_MAX, n % NANOS_MAX).unwrap())
.collect()
}
crate::bench_encode_decode!(duration_vec: Vec<_>);
}unning 3 tests
test ext::jiff::tests::test ... ok
test ext::jiff::tests::bench_duration_vec_encode ... ok
test ext::jiff::tests::bench_duration_vec_decode ... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 205 filtered out; finished in 0.00s
* Terminal will be reused by tasks, press any key to close it. Can I submit a PR for Jiff(crates.io: Github:)? |
…000_000_000 ns is valid (leap second support) 23:59:58 with 1_000_000_000 ns is invalid
- Timestamp::now() accesses system clock which is not allowed in Miri isolation mode. - The existing test cases already fully cover all valid/invalid scenarios, so removing this line does not affect test validity.
|
@finnbear cc |
|
Sorry for the delay. My review comments are above. Thanks very much for this PR! |
…le date/time types
1. Add comprehensive tests for Zoned encoding/decoding 2. Use infallible ConvertFrom for Zoned (Timestamp, Offset) tuple conversion 3. Fix comment for Day in date module
|
@finnbear Greetings to you, my friend far away. May you be blessed with peace, health, and joy. |
Cargo.toml
Outdated
| name = "bitcode" | ||
| authors = [ "Cai Bear", "Finn Bear" ] | ||
| version = "0.6.9" | ||
| version = "0.7.0" |
There was a problem hiding this comment.
Thanks again! Your changes look good to me. Before merging, @caibear and I will need to review more deeply, and I'm not sure when we'll have time to do that (could be weeks/months from now). If any further changes are required, such as not bumping the major version here, there is a good chance I'd personally make them during review, rather than requesting further changes.
Until then, I hope you can get some value from this PR using a patch in your Cargo.toml:
[patch.crates-io]
yew = { git = "https://github.com/l-7-l/bitcode.git", branch = "support_for_chrono#39" }(this doesn't work if you're making a library that you plan to publish on Crates.io, but otherwise it should work)
There was a problem hiding this comment.
Thank you, I will change the 0.70 version back to 0.69.
|
Thanks for the detailed breakdown of the required changes! It looks like you're making great progress on updating the chrono and jiff support. If you need help with the fuzz tests or any other aspect, feel free to reach out! |

#39
chrono version>= 0.4:
jiff (version >= 0.2):
crate version 0.6.9 -> 0.7.0
lz4_flex 0.11.2(yanked) -> 0.13.0