This is an automated email from the ASF dual-hosted git repository. github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-2892-119fad92c891c95a7819d87e4195407b3f7c0578 in repository https://gitbox.apache.org/repos/asf/iceberg-rust.git
commit 9a2111a914e9b17fdc039700186056ec14fae606 Author: Minh Vu <[email protected]> AuthorDate: Fri Sep 25 21:32:09 2026 +0000 fix(spec): format pre-epoch timestamptz values (#2892) * fix(spec): format pre-epoch timestamptz values * fix(spec): strengthen pre-epoch timestamptz regression * style: format temporal conversion * test(spec): match chrono fractional timestamp display * test(spec): match trimmed timestamp fraction --- crates/iceberg/src/spec/values/temporal.rs | 12 +++++------ crates/iceberg/src/spec/values/tests.rs | 32 ++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/crates/iceberg/src/spec/values/temporal.rs b/crates/iceberg/src/spec/values/temporal.rs index 80aadeb65..76b2c897e 100644 --- a/crates/iceberg/src/spec/values/temporal.rs +++ b/crates/iceberg/src/spec/values/temporal.rs @@ -98,15 +98,15 @@ pub(crate) mod timestamptz { } pub(crate) fn microseconds_to_datetimetz(micros: i64) -> DateTime<Utc> { - let (secs, rem) = (micros / 1_000_000, micros % 1_000_000); - - DateTime::from_timestamp(secs, rem as u32 * 1_000).unwrap() + // Returns None only outside chrono's range (~year -262144 to +262142); + // unreachable for any realistic Iceberg timestamp. + DateTime::from_timestamp_micros(micros).expect( + "timestamp micros outside chrono's representable range (~year -262144..+262142)", + ) } pub(crate) fn nanoseconds_to_datetimetz(nanos: i64) -> DateTime<Utc> { - let (secs, rem) = (nanos / 1_000_000_000, nanos % 1_000_000_000); - - DateTime::from_timestamp(secs, rem as u32).unwrap() + DateTime::from_timestamp_nanos(nanos) } /// Nanoseconds since the Unix epoch, or `None` if outside the representable `i64` range diff --git a/crates/iceberg/src/spec/values/tests.rs b/crates/iceberg/src/spec/values/tests.rs index 97b2f6b32..ff51f318e 100644 --- a/crates/iceberg/src/spec/values/tests.rs +++ b/crates/iceberg/src/spec/values/tests.rs @@ -1124,6 +1124,38 @@ fn test_parse_timestamptz() { ); } +#[test] +fn test_pre_epoch_timestamptz_formatting() { + assert_eq!( + Datum::timestamptz_micros(-1).to_string(), + "1969-12-31 23:59:59.999999 UTC" + ); + assert_eq!( + Datum::timestamptz_nanos(-1).to_string(), + "1969-12-31 23:59:59.999999999 UTC" + ); + assert_eq!( + Datum::timestamptz_micros(-1_500_000).to_string(), + "1969-12-31 23:59:58.500 UTC" + ); + + check_json_serde( + r#""1969-12-31T23:59:59.999999+00:00""#, + Literal::long(-1), + &Primitive(PrimitiveType::Timestamptz), + ); + check_json_serde( + r#""1969-12-31T23:59:58.500+00:00""#, + Literal::long(-1_500_000), + &Primitive(PrimitiveType::Timestamptz), + ); + check_json_serde( + r#""1969-12-31T23:59:59.999999999+00:00""#, + Literal::long(-1), + &Primitive(PrimitiveType::TimestamptzNs), + ); +} + #[test] fn test_datum_ser_deser() { let test_fn = |datum: Datum| {
