use chrono::{DateTime as ChronoDateTime, FixedOffset as ChronoFixedOffset, Utc as ChronoUtc};
use jiff::{Timestamp as JiffTimestamp, Zoned as JiffZoned};
use time::{OffsetDateTime, UtcDateTime};
use crate::Value;
fn chrono_utc(secs: i64, nanos: u32) -> ChronoDateTime<ChronoUtc> {
ChronoDateTime::from_timestamp(secs, nanos).unwrap()
}
fn time_utc(secs: i64, nanos: u32) -> UtcDateTime {
UtcDateTime::from_unix_timestamp_nanos(secs as i128 * 1_000_000_000 + nanos as i128).unwrap()
}
fn jiff_ts(secs: i64, nanos: u32) -> JiffTimestamp {
JiffTimestamp::new(secs, nanos as i32).unwrap()
}
fn assert_utc_whole_second_interop(secs: i64) {
let from_chrono = Value::date_time(chrono_utc(secs, 0));
let from_time = Value::date_time(time_utc(secs, 0));
let from_jiff = Value::date_time(jiff_ts(secs, 0));
assert_eq!(from_chrono, from_time);
assert_eq!(from_chrono, from_jiff);
let back_chrono = ChronoDateTime::<ChronoUtc>::try_from(&from_time).unwrap();
let back_time = UtcDateTime::try_from(&from_jiff).unwrap();
let back_jiff = JiffTimestamp::try_from(&from_chrono).unwrap();
assert_eq!(back_chrono.timestamp(), secs);
assert_eq!(back_time.unix_timestamp(), secs);
assert_eq!(back_jiff.as_second(), secs);
}
fn assert_utc_subsec_interop(secs: i64, nanos: u32) {
let from_chrono = Value::date_time(chrono_utc(secs, nanos));
let from_time = Value::date_time(time_utc(secs, nanos));
let from_jiff = Value::date_time(jiff_ts(secs, nanos));
for source in [&from_chrono, &from_time, &from_jiff] {
let c = ChronoDateTime::<ChronoUtc>::try_from(source).unwrap();
let t = UtcDateTime::try_from(source).unwrap();
let j = JiffTimestamp::try_from(source).unwrap();
assert_eq!(c.timestamp(), secs);
assert_eq!(c.timestamp_subsec_nanos(), nanos);
assert_eq!(t.unix_timestamp(), secs);
assert_eq!(t.nanosecond(), nanos);
assert_eq!(j.as_second(), secs);
assert_eq!(j.subsec_nanosecond(), nanos as i32);
}
}
fn assert_offset_instant_interop(rfc3339: &str, expected_offset_secs: i32) {
let v = Value::date_time(rfc3339);
let chrono_dt = ChronoDateTime::<ChronoFixedOffset>::try_from(&v).unwrap();
let time_dt = OffsetDateTime::try_from(&v).unwrap();
let jiff_dt = JiffZoned::try_from(&v).unwrap();
let instant = chrono_dt.timestamp();
assert_eq!(time_dt.unix_timestamp(), instant);
assert_eq!(jiff_dt.timestamp().as_second(), instant);
assert_eq!(chrono_dt.offset().local_minus_utc(), expected_offset_secs);
assert_eq!(time_dt.offset().whole_seconds(), expected_offset_secs);
assert_eq!(jiff_dt.offset().seconds(), expected_offset_secs);
}
fn assert_epoch_whole_second_interop(secs: i64) {
let from_chrono = Value::epoch_time(chrono_utc(secs, 0));
let from_time = Value::epoch_time(time_utc(secs, 0));
let from_jiff = Value::epoch_time(jiff_ts(secs, 0));
assert_eq!(from_chrono, from_time);
assert_eq!(from_chrono, from_jiff);
let back_chrono = ChronoDateTime::<ChronoUtc>::try_from(&from_time).unwrap();
let back_time = UtcDateTime::try_from(&from_jiff).unwrap();
let back_jiff = JiffTimestamp::try_from(&from_chrono).unwrap();
assert_eq!(back_chrono.timestamp(), secs);
assert_eq!(back_time.unix_timestamp(), secs);
assert_eq!(back_jiff.as_second(), secs);
}
fn assert_epoch_subsec_interop(secs: i64, nanos: u32) {
let from_chrono = Value::epoch_time(chrono_utc(secs, nanos));
let from_time = Value::epoch_time(time_utc(secs, nanos));
let from_jiff = Value::epoch_time(jiff_ts(secs, nanos));
for source in [&from_chrono, &from_time, &from_jiff] {
let c = ChronoDateTime::<ChronoUtc>::try_from(source).unwrap();
let t = UtcDateTime::try_from(source).unwrap();
let j = JiffTimestamp::try_from(source).unwrap();
assert_eq!(c.timestamp(), secs);
assert_eq!(t.unix_timestamp(), secs);
assert_eq!(j.as_second(), secs);
let ulp = 240;
assert!((c.timestamp_subsec_nanos() as i64 - nanos as i64).abs() < ulp);
assert!((t.nanosecond() as i64 - nanos as i64).abs() < ulp);
assert!((j.subsec_nanosecond() as i64 - nanos as i64).abs() < ulp);
}
}
#[test]
fn utc_whole_second_interop() {
for secs in [0_i64, 1, 946_684_800, 1_720_094_400, 2_147_483_647] {
assert_utc_whole_second_interop(secs);
}
}
#[test]
fn utc_subsec_interop() {
for (secs, nanos) in [
(0_i64, 1_u32),
(946_684_800, 500_000_000),
(1_720_094_400, 123_000_000),
(1_720_094_400, 123_456_789),
] {
assert_utc_subsec_interop(secs, nanos);
}
}
#[test]
fn offset_instant_interop() {
assert_offset_instant_interop("1970-01-01T00:00:00Z", 0);
assert_offset_instant_interop("2000-01-01T01:00:00+01:00", 3600);
assert_offset_instant_interop("2024-07-04T12:00:00-04:00", -4 * 3600);
assert_offset_instant_interop("2024-07-04T12:00:00.123+05:30", 5 * 3600 + 30 * 60);
}
#[test]
fn epoch_whole_second_interop() {
for secs in [0_i64, 1, 946_684_800, 1_720_094_400, 2_147_483_647] {
assert_epoch_whole_second_interop(secs);
}
}
#[test]
fn epoch_subsec_interop() {
for (secs, nanos) in [
(0_i64, 500_000_000_u32),
(1_720_094_400, 500_000_000),
(1_720_094_400, 123_000_000),
] {
assert_epoch_subsec_interop(secs, nanos);
}
}
#[test]
fn tag0_cross_decode() {
let (secs, nanos) = (1_720_094_400_i64, 250_000_000_u32);
for source in [
Value::date_time(chrono_utc(secs, nanos)),
Value::date_time(time_utc(secs, nanos)),
Value::date_time(jiff_ts(secs, nanos)),
] {
let c = ChronoDateTime::<ChronoUtc>::try_from(&source).unwrap();
let t = UtcDateTime::try_from(&source).unwrap();
let j = JiffTimestamp::try_from(&source).unwrap();
assert_eq!(c.timestamp(), secs);
assert_eq!(c.timestamp_subsec_nanos(), nanos);
assert_eq!(t.unix_timestamp(), secs);
assert_eq!(t.nanosecond(), nanos);
assert_eq!(j.as_second(), secs);
assert_eq!(j.subsec_nanosecond(), nanos as i32);
}
}