Skip to content

Commit 804163f

Browse files
committed
add support for naive_date naive_date_time naive_time date_time_utc date_time_fixed_offset
1 parent 76e5998 commit 804163f

File tree

10 files changed

+436
-11
lines changed

10 files changed

+436
-11
lines changed

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ exclude = ["fuzz/"]
1818
arrayvec = { version = "0.7", default-features = false, optional = true }
1919
bitcode_derive = { version = "=0.6.7", path = "./bitcode_derive", optional = true }
2020
bytemuck = { version = "1.14", features = [ "min_const_generics", "must_cast" ] }
21+
chrono = { version = ">=0.4", default-features = false, optional = true }
2122
glam = { version = ">=0.21", default-features = false, optional = true }
2223
rust_decimal = { version = "1.36", default-features = false, optional = true }
2324
serde = { version = "1.0", default-features = false, features = [ "alloc" ], optional = true }
@@ -43,6 +44,7 @@ zstd = "0.13.0"
4344
derive = [ "dep:bitcode_derive" ]
4445
std = [ "serde?/std", "glam?/std", "arrayvec?/std" ]
4546
default = [ "derive", "std" ]
47+
chrono_datetime_fixedoffset = ["dep:chrono"]
4648

4749
[package.metadata.docs.rs]
4850
features = [ "derive", "serde", "std" ]

src/ext/date.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
use crate::int::ranged_int;
2+
#[cfg(feature = "chrono")]
3+
mod chrono;
4+
#[cfg(feature = "time")]
5+
mod time;
6+
7+
ranged_int!(Hour, u8, 0, 23);
8+
ranged_int!(Minute, u8, 0, 59);
9+
ranged_int!(Second, u8, 0, 59);
10+
ranged_int!(Nanosecond, u32, 0, 999_999_999);
11+
12+
type TimeEncode = (u8, u8, u8, u32);
13+
type TimeDecode = (Hour, Minute, Second, Nanosecond);
14+
15+
#[cfg(feature = "chrono")]
16+
type DateEncode = i32;
17+
#[cfg(feature = "chrono")]
18+
type DateDecode = i32;
19+
20+
#[cfg(feature = "chrono")]
21+
type DateTimeEncode = (DateEncode, TimeEncode);
22+
#[cfg(feature = "chrono")]
23+
type DateTimeDecode = (DateEncode, TimeEncode);
24+
25+
#[cfg(feature = "chrono")]
26+
pub type DateTimeWithOffsetEncode = (DateTimeEncode, i32);
27+
#[cfg(feature = "chrono")]
28+
pub type DateTimeWithOffsetDecode = (DateTimeDecode, i32);

src/ext/date/chrono.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#[cfg(feature = "chrono_datetime_fixedoffset")]
2+
mod date_time_fixed_offset;
3+
mod date_time_utc;
4+
mod naive_date;
5+
mod naive_date_time;
6+
mod naive_time;
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
use chrono::{DateTime, FixedOffset, NaiveDateTime};
2+
3+
use crate::{
4+
convert::{impl_convert, ConvertFrom},
5+
ext::date::{DateTimeEncode, DateTimeWithOffsetDecode, DateTimeWithOffsetEncode},
6+
};
7+
8+
impl_convert!(
9+
DateTime<FixedOffset>,
10+
DateTimeWithOffsetEncode,
11+
DateTimeWithOffsetDecode
12+
);
13+
14+
impl ConvertFrom<&DateTime<FixedOffset>> for DateTimeWithOffsetEncode {
15+
fn convert_from(x: &DateTime<FixedOffset>) -> Self {
16+
let naive_enc = DateTimeEncode::convert_from(&x.naive_utc());
17+
let offset_sec = x.offset().local_minus_utc();
18+
19+
(naive_enc, offset_sec)
20+
}
21+
}
22+
23+
impl ConvertFrom<DateTimeWithOffsetEncode> for DateTime<FixedOffset> {
24+
fn convert_from(enc: DateTimeWithOffsetEncode) -> Self {
25+
let naive = NaiveDateTime::convert_from(enc.0);
26+
let offset =
27+
FixedOffset::east_opt(enc.1).unwrap_or_else(|| FixedOffset::east_opt(0).unwrap());
28+
29+
DateTime::<FixedOffset>::from_naive_utc_and_offset(naive, offset)
30+
}
31+
}
32+
33+
#[cfg(test)]
34+
mod tests {
35+
use alloc::vec::Vec;
36+
use chrono::{DateTime, FixedOffset, NaiveDate};
37+
38+
#[test]
39+
fn test_chrono_datetime_fixedoffset() {
40+
let dates = [
41+
(1, 1, 1),
42+
(1970, 1, 1), // epoch
43+
(2025, 10, 6),
44+
(-44, 3, 15), // BCE
45+
(9999, 12, 31),
46+
];
47+
48+
let offsets = [
49+
-12 * 3600, // UTC-12, Baker Island Time
50+
-11 * 3600, // UTC-11, Niue / Samoa
51+
-5 * 3600, // UTC-5, EST (Eastern Standard Time, 美东冬令时)
52+
-3 * 3600, // UTC-3, BRT (Brasilia Time)
53+
0, // UTC+0, GMT
54+
3600, // UTC+1, CET (Central European Time)
55+
3 * 3600, // UTC+3, MSK (Moscow Time)
56+
5 * 3600 + 1800, // UTC+5:30, IST (India Standard Time)
57+
8 * 3600, // UTC+8, CST (China Standard Time)
58+
14 * 3600, // UTC+14, Line Islands Time
59+
];
60+
61+
let times = [(0, 0, 0), (12, 34, 56), (23, 59, 59)];
62+
63+
for &(y, m, d) in &dates {
64+
for &(h, mi, s) in &times {
65+
let naive = NaiveDate::from_ymd_opt(y, m, d)
66+
.unwrap()
67+
.and_hms_opt(h, mi, s)
68+
.unwrap();
69+
70+
for &offset_sec in &offsets {
71+
let offset = FixedOffset::east_opt(offset_sec).unwrap();
72+
let dt_fixed =
73+
DateTime::<FixedOffset>::from_naive_utc_and_offset(naive, offset);
74+
75+
let enc = crate::encode(&dt_fixed);
76+
let decoded: DateTime<FixedOffset> = crate::decode(&enc).unwrap();
77+
78+
assert_eq!(
79+
dt_fixed, decoded,
80+
"Failed for datetime {:?} with offset {}",
81+
dt_fixed, offset
82+
);
83+
}
84+
}
85+
}
86+
}
87+
88+
fn bench_data() -> Vec<DateTime<FixedOffset>> {
89+
crate::random_data(1000)
90+
.into_iter()
91+
.map(
92+
|(y, m, d, h, mi, s, n, offset_sec): (i32, u32, u32, u32, u32, u32, u32, i32)| {
93+
let naive =
94+
NaiveDate::from_ymd_opt((y % 9999).max(1), (m % 12).max(1), (d % 28) + 1)
95+
.unwrap()
96+
.and_hms_nano_opt(h % 24, mi % 60, s % 60, n % 1_000_000_000)
97+
.unwrap();
98+
let offset = FixedOffset::east_opt(offset_sec % 86_400)
99+
.unwrap_or(FixedOffset::east_opt(0).unwrap());
100+
DateTime::<FixedOffset>::from_naive_utc_and_offset(naive, offset)
101+
},
102+
)
103+
.collect()
104+
}
105+
106+
crate::bench_encode_decode!(data: Vec<DateTime<FixedOffset>>);
107+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
use chrono::{DateTime, NaiveDateTime, Utc};
2+
3+
use crate::{
4+
convert::{impl_convert, ConvertFrom},
5+
ext::date::{DateTimeDecode, DateTimeEncode},
6+
};
7+
8+
impl_convert!(DateTime<Utc>, DateTimeEncode, DateTimeDecode);
9+
10+
impl ConvertFrom<&DateTime<Utc>> for DateTimeEncode {
11+
fn convert_from(x: &DateTime<Utc>) -> Self {
12+
DateTimeEncode::convert_from(&x.naive_utc())
13+
}
14+
}
15+
16+
impl ConvertFrom<DateTimeEncode> for DateTime<Utc> {
17+
fn convert_from(enc: DateTimeEncode) -> Self {
18+
let naive = NaiveDateTime::convert_from(enc);
19+
20+
DateTime::from_naive_utc_and_offset(naive, Utc)
21+
}
22+
}
23+
24+
#[cfg(test)]
25+
mod tests {
26+
use alloc::vec::Vec;
27+
use chrono::{DateTime, NaiveDate, Utc};
28+
29+
#[test]
30+
fn test_chrono_datetime_utc() {
31+
let ymds = [
32+
(1970, 1, 1), // epoch
33+
(2025, 10, 6),
34+
(1, 1, 1),
35+
(-44, 3, 15), // BCE
36+
(9999, 12, 31),
37+
];
38+
39+
for &(y, m, d) in ymds.iter() {
40+
let naive = NaiveDate::from_ymd_opt(y, m, d)
41+
.unwrap()
42+
.and_hms_opt(12, 34, 56)
43+
.unwrap();
44+
let dt_utc = DateTime::<Utc>::from_naive_utc_and_offset(naive, Utc);
45+
46+
let enc = crate::encode(&dt_utc);
47+
let decoded: DateTime<Utc> = crate::decode(&enc).unwrap();
48+
49+
assert_eq!(dt_utc, decoded, "failed for datetime {:?}", dt_utc);
50+
}
51+
}
52+
53+
fn bench_data() -> Vec<DateTime<Utc>> {
54+
crate::random_data(1000)
55+
.into_iter()
56+
.map(
57+
|(y, m, d, h, mi, s, n, _offset_sec): (i32, u32, u32, u32, u32, u32, u32, i32)| {
58+
let naive =
59+
NaiveDate::from_ymd_opt((y % 9999).max(1), (m % 12).max(1), (d % 28) + 1)
60+
.unwrap()
61+
.and_hms_nano_opt(h % 24, mi % 60, s % 60, n % 1_000_000_000)
62+
.unwrap();
63+
DateTime::<Utc>::from_naive_utc_and_offset(naive, Utc)
64+
},
65+
)
66+
.collect()
67+
}
68+
69+
crate::bench_encode_decode!(utc_vec: Vec<DateTime<Utc>>);
70+
}

src/ext/date/chrono/naive_date.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
use chrono::{Datelike, NaiveDate};
2+
3+
use crate::{
4+
convert::{impl_convert, ConvertFrom},
5+
ext::date::{DateDecode, DateEncode},
6+
};
7+
8+
impl_convert!(NaiveDate, DateEncode, DateDecode);
9+
10+
impl ConvertFrom<&NaiveDate> for DateEncode {
11+
fn convert_from(days: &NaiveDate) -> Self {
12+
days.num_days_from_ce() - 719_163 // 1970-1-1
13+
}
14+
}
15+
16+
impl ConvertFrom<DateDecode> for NaiveDate {
17+
fn convert_from(days: DateDecode) -> Self {
18+
NaiveDate::from_num_days_from_ce_opt(days + 719_163).unwrap() // 1970-1-1
19+
}
20+
}
21+
22+
#[cfg(test)]
23+
mod tests {
24+
#[test]
25+
fn test_chrono_naive_date() {
26+
let dates = [
27+
NaiveDate::from_ymd_opt(1970, 1, 1).unwrap(), // epoch
28+
NaiveDate::from_ymd_opt(2025, 10, 6).unwrap(),
29+
NaiveDate::from_ymd_opt(1, 1, 1).unwrap(),
30+
NaiveDate::from_ymd_opt(-44, 3, 15).unwrap(), // BCE
31+
NaiveDate::from_ymd_opt(9999, 12, 31).unwrap(),
32+
];
33+
34+
for x in dates {
35+
let enc = crate::encode(&x);
36+
let date: NaiveDate = crate::decode(&enc).unwrap();
37+
38+
assert_eq!(x, date, "failed for date {:?}", x);
39+
}
40+
}
41+
42+
use alloc::vec::Vec;
43+
use chrono::NaiveDate;
44+
45+
fn bench_data() -> Vec<NaiveDate> {
46+
crate::random_data(1000)
47+
.into_iter()
48+
.map(|(y, m, d): (i32, u32, u32)| {
49+
let year = (y % 9999).max(1); // 1 ~ 9998
50+
let month = (m % 12).max(1); // 1 ~ 12
51+
let day = (d % 28) + 1; // 1 ~ 28
52+
NaiveDate::from_ymd_opt(year, month, day).unwrap()
53+
})
54+
.collect()
55+
}
56+
crate::bench_encode_decode!(data: Vec<_>);
57+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
2+
3+
use crate::{
4+
convert::{impl_convert, ConvertFrom},
5+
ext::date::{DateEncode, DateTimeDecode, DateTimeEncode, TimeEncode},
6+
};
7+
8+
impl_convert!(NaiveDateTime, DateTimeEncode, DateTimeDecode);
9+
10+
impl ConvertFrom<&NaiveDateTime> for DateTimeEncode {
11+
fn convert_from(x: &NaiveDateTime) -> Self {
12+
(
13+
DateEncode::convert_from(&x.date()),
14+
TimeEncode::convert_from(&x.time()),
15+
)
16+
}
17+
}
18+
19+
impl ConvertFrom<DateTimeEncode> for NaiveDateTime {
20+
fn convert_from((date, time): DateTimeEncode) -> Self {
21+
NaiveDateTime::new(
22+
NaiveDate::convert_from(date),
23+
NaiveTime::from_hms_nano_opt(time.0 as u32, time.1 as u32, time.2 as u32, time.3)
24+
.unwrap(),
25+
)
26+
}
27+
}
28+
29+
#[cfg(test)]
30+
mod tests {
31+
use alloc::vec::Vec;
32+
use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
33+
34+
use crate::decode;
35+
use crate::encode;
36+
37+
#[test]
38+
fn test_chrono_naive_datetime() {
39+
let dt = NaiveDateTime::new(
40+
NaiveDate::from_ymd_opt(2025, 10, 6).unwrap(),
41+
NaiveTime::from_hms_nano_opt(12, 34, 56, 123_456_789).unwrap(),
42+
);
43+
44+
let encoded = encode(&dt);
45+
let decoded: NaiveDateTime = decode(&encoded).unwrap();
46+
47+
assert_eq!(dt, decoded);
48+
49+
let dt2 = NaiveDateTime::new(
50+
NaiveDate::from_ymd_opt(1, 1, 1).unwrap(),
51+
NaiveTime::from_hms_nano_opt(0, 0, 0, 0).unwrap(),
52+
);
53+
let encoded2 = encode(&dt2);
54+
let decoded2: NaiveDateTime = decode(&encoded2).unwrap();
55+
assert_eq!(dt2, decoded2);
56+
}
57+
58+
fn bench_data() -> Vec<NaiveDateTime> {
59+
crate::random_data(1000)
60+
.into_iter()
61+
.map(
62+
|(y, m, d, h, min, s, n): (i32, u32, u32, u8, u8, u8, u32)| {
63+
let year = (y % 9999).max(1);
64+
let month = (m % 12).max(1);
65+
let day = (d % 28) + 1;
66+
let date = NaiveDate::from_ymd_opt(year, month, day).unwrap();
67+
68+
let hour = h % 24;
69+
let minute = min % 60;
70+
let second = s % 60;
71+
let nano = n % 1_000_000_000;
72+
let time = NaiveTime::from_hms_nano_opt(
73+
hour as u32,
74+
minute as u32,
75+
second as u32,
76+
nano,
77+
)
78+
.unwrap();
79+
80+
NaiveDateTime::new(date, time)
81+
},
82+
)
83+
.collect()
84+
}
85+
86+
crate::bench_encode_decode!(data_vec: Vec<_>);
87+
}

0 commit comments

Comments
 (0)