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:)? |
|
It's *incorrect to * try encoding and decoding |
|
But if we introduce TryConvertFrom, since the decode method of the Decoder trait does not support returning a Result, we would need a new TryDecoder. This would also require a TryDecode for the Decode trait. Would that be a good approach? |
In the Lines 94 to 100 in d0049d9 Then, in the Line 126 in d0049d9 (the unspoken assumption is that the Another (less efficient but more flexible) approach is to decode all of the values in However, both of these approaches are tedious and error-prone code that deserve to be abstracted into |

#39