You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@iceberg.apache.org by "andygrove (via GitHub)" <gi...@apache.org> on 2026/10/01 16:09:56 UTC
[PR] fix(transform): floor day transform for pre-epoch timestamps [iceberg-rust]
andygrove opened a new pull request, #3323:
URL: https://github.com/apache/iceberg-rust/pull/3323
## Which issue does this PR close?
- Closes https://github.com/apache/iceberg-rust/issues/3315.
## What changes are included in this PR?
`Day::day_timestamp_micro` and `Day::day_timestamp_nano` took the whole seconds of a timestamp with `/`, which truncates toward zero, but took the fraction of the second with `rem_euclid`, which floors. For a negative timestamp that is not on a whole second, the seconds came out one too high, so a timestamp in the last second of a day before 1969-12-31 got the next day. `Day::transform`, `Day::transform_literal` and predicate projection all go through these two functions.
This PR computes the day with `div_euclid` on the length of a day, the way `Hour` already does, and drops the `Duration` arithmetic and its error path. The result now matches Iceberg Java for every input in the issue's table.
Files that iceberg-rust wrote before this change can hold such timestamps in the next day's partition. Inclusive projection already widens pre-epoch `day` predicates by one day to cover such files, as Java's `ProjectionUtil.fixInclusiveTimeProjection` does, but only for `timestamp` columns. This PR applies it to `timestamptz`, `timestamp_ns` and `timestamptz_ns` as well, as Java's `Timestamps.project` does. Without that, a `<`, `<=`, `=` or `IN` scan on those types could skip a partition that iceberg-rust wrote before the fix.
The issue suggested flooring only the seconds. That would also copy what Java's `DateTimeUtil` does for a pre-epoch timestamp exactly 999999 microseconds (999999999 nanoseconds) after midnight: Java puts it in the day before, so `1969-01-01T00:00:00.999999` is day 1968-12-31. I tried that and decided against it:
- It makes the transform non-monotonic, because `1969-01-01T00:00:00.999999` would get an earlier day than `1969-01-01T00:00:00.999998`. Inclusive projection assumes the transform preserves order, so `ts >= '1969-01-01'` would project to `day >= 1969-01-01` and skip the partition holding that row. Java's own projection has the same gap.
- `day` would disagree with `hour`, `month` and `year`, which floor.
- iceberg-rust already returns the calendar day for these timestamps, as PyIceberg does, so this PR leaves them unchanged.
Not changed here:
- Iceberg Java writes those 999999 timestamps to the day before, and iceberg-rust's projection can miss them there, as it could before this PR.
- Equality deletes are matched to data files by partition, so a delete written after this fix will not reach a row that iceberg-rust wrote one day too high before it. Deletes written by Java already miss those rows. Rewriting the affected files puts the rows in the right partition.
- Strict projection has no port of Java's `fixStrictTimeProjection`. `StrictProjection` is only used in tests so far.
## Are these changes tested?
Yes. The new `test_transform_days_pre_epoch` runs `Day::transform` on microsecond and nanosecond arrays, and `transform_literal` on `timestamp`, `timestamptz`, `timestamp_ns` and `timestamptz_ns` literals, for seven timestamps around pre-epoch midnights. For six of them the expected day is what `Transforms.day()` returns in Iceberg Java 1.11.0 for all four types, and in 1.5.2 for `timestamp` and `timestamptz`. The seventh is the 999999 case above, where the test expects the calendar day and notes Java's value. Without the fix, three of the seven get the next day. The new `test_projection_timestamp_types_day_negative` checks that `<`, `<=`, `=` and `IN` projections on all four timestamp types still include the next day, and it fails for `timestamptz` without the projection change. The existing unit tests in the `iceberg` crate pass unchanged.
## AI Disclosure
The code, the tests and this description were drafted with Claude Code (an AI assistant) and then reviewed by me. The expected days in `test_transform_days_pre_epoch` were checked by running Iceberg Java, not taken from the Rust code. The part I'd most like a second look at is the choice not to follow Java for the 999999 timestamps.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@iceberg.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@iceberg.apache.org
For additional commands, e-mail: issues-help@iceberg.apache.org
Re: [PR] fix(transform): floor day transform for pre-epoch timestamps [iceberg-rust]
Posted by "kevinjqliu (via GitHub)" <gi...@apache.org>.
kevinjqliu commented on code in PR #3323:
URL: https://github.com/apache/iceberg-rust/pull/3323#discussion_r4179297030
##########
crates/iceberg/src/transform/temporal.rs:
##########
@@ -2650,6 +2651,48 @@ mod test {
test_timestamp_ns_and_tz_transform("2017-12-01T10:30:42.123000", &day, Datum::date(17501));
}
+ #[test]
+ fn test_transform_days_pre_epoch() {
+ let day = Box::new(super::Day) as BoxedTransformFunction;
+ let expected = [-1, -2, -2, -2, -2, -366, -365];
+
+ let micros = vec![
+ -500_000, // 1969-12-31T23:59:59.500000
+ -86_400_000_001, // 1969-12-30T23:59:59.999999
+ -86_400_000_002, // 1969-12-30T23:59:59.999998
+ -86_400_500_000, // 1969-12-30T23:59:59.500000
+ -86_401_000_000, // 1969-12-30T23:59:59.000000
+ -31_536_000_500_000, // 1968-12-31T23:59:59.500000
+ -31_535_999_000_001, // 1969-01-01T00:00:00.999999, Iceberg Java gives -366
Review Comment:
this is a bug on the java side
##########
crates/iceberg/src/transform/temporal.rs:
##########
@@ -205,50 +205,13 @@ pub struct Day;
impl Day {
#[inline]
- fn day_timestamp_micro(v: i64) -> Result<i32> {
- let secs = v / MICROS_PER_SECOND;
-
- let (nanos, offset) = if v >= 0 {
- let nanos = (v.rem_euclid(MICROS_PER_SECOND) * 1_000) as u32;
- let offset = 0i64;
- (nanos, offset)
- } else {
- let v = v + 1;
- let nanos = (v.rem_euclid(MICROS_PER_SECOND) * 1_000) as u32;
- let offset = 1i64;
- (nanos, offset)
- };
-
- let delta = Duration::new(secs, nanos).ok_or_else(|| {
- invalid_data!("Failed to create 'TimeDelta' from seconds {secs} and nanos {nanos}")
- })?;
-
- let days = (delta.num_days() - offset) as i32;
-
- Ok(days)
+ fn day_timestamp_micro(v: i64) -> i32 {
Review Comment:
I don't think we need to document this in the code comment.
This seems like a bug on the java side (`DateTimeUtil.convertMicros`), rust's behavior matches the spec. Lets just open an issue in on java instead
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@iceberg.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@iceberg.apache.org
For additional commands, e-mail: issues-help@iceberg.apache.org
Re: [PR] fix(transform): floor day transform for pre-epoch timestamps [iceberg-rust]
Posted by "kevinjqliu (via GitHub)" <gi...@apache.org>.
kevinjqliu commented on code in PR #3323:
URL: https://github.com/apache/iceberg-rust/pull/3323#discussion_r4179297030
##########
crates/iceberg/src/transform/temporal.rs:
##########
@@ -2650,6 +2651,48 @@ mod test {
test_timestamp_ns_and_tz_transform("2017-12-01T10:30:42.123000", &day, Datum::date(17501));
}
+ #[test]
+ fn test_transform_days_pre_epoch() {
+ let day = Box::new(super::Day) as BoxedTransformFunction;
+ let expected = [-1, -2, -2, -2, -2, -366, -365];
+
+ let micros = vec![
+ -500_000, // 1969-12-31T23:59:59.500000
+ -86_400_000_001, // 1969-12-30T23:59:59.999999
+ -86_400_000_002, // 1969-12-30T23:59:59.999998
+ -86_400_500_000, // 1969-12-30T23:59:59.500000
+ -86_401_000_000, // 1969-12-30T23:59:59.000000
+ -31_536_000_500_000, // 1968-12-31T23:59:59.500000
+ -31_535_999_000_001, // 1969-01-01T00:00:00.999999, Iceberg Java gives -366
Review Comment:
this is a bug on the java side
see https://github.com/apache/iceberg/issues/18371
##########
crates/iceberg/src/transform/temporal.rs:
##########
@@ -205,50 +205,13 @@ pub struct Day;
impl Day {
#[inline]
- fn day_timestamp_micro(v: i64) -> Result<i32> {
- let secs = v / MICROS_PER_SECOND;
-
- let (nanos, offset) = if v >= 0 {
- let nanos = (v.rem_euclid(MICROS_PER_SECOND) * 1_000) as u32;
- let offset = 0i64;
- (nanos, offset)
- } else {
- let v = v + 1;
- let nanos = (v.rem_euclid(MICROS_PER_SECOND) * 1_000) as u32;
- let offset = 1i64;
- (nanos, offset)
- };
-
- let delta = Duration::new(secs, nanos).ok_or_else(|| {
- invalid_data!("Failed to create 'TimeDelta' from seconds {secs} and nanos {nanos}")
- })?;
-
- let days = (delta.num_days() - offset) as i32;
-
- Ok(days)
+ fn day_timestamp_micro(v: i64) -> i32 {
Review Comment:
I don't think we need to document this in the code comment.
This seems like a bug on the java side (`DateTimeUtil.convertMicros`), rust's behavior matches the spec. Lets just open an issue in on java instead
Opened one: https://github.com/apache/iceberg/issues/18371
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@iceberg.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@iceberg.apache.org
For additional commands, e-mail: issues-help@iceberg.apache.org
Re: [PR] fix(transform): floor day transform for pre-epoch timestamps [iceberg-rust]
Posted by "kevinjqliu (via GitHub)" <gi...@apache.org>.
kevinjqliu commented on PR #3323:
URL: https://github.com/apache/iceberg-rust/pull/3323#issuecomment-5984419648
Thanks @andygrove for the pr and @comphead for the review
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@iceberg.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@iceberg.apache.org
For additional commands, e-mail: issues-help@iceberg.apache.org
Re: [PR] fix(transform): floor day transform for pre-epoch timestamps [iceberg-rust]
Posted by "kevinjqliu (via GitHub)" <gi...@apache.org>.
kevinjqliu commented on PR #3323:
URL: https://github.com/apache/iceberg-rust/pull/3323#issuecomment-5984417215
This seems pretty self contained, and we have enough coverage for transform, so Im comfortable merging this
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@iceberg.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@iceberg.apache.org
For additional commands, e-mail: issues-help@iceberg.apache.org
Re: [PR] fix(transform): floor day transform for pre-epoch timestamps [iceberg-rust]
Posted by "comphead (via GitHub)" <gi...@apache.org>.
comphead commented on code in PR #3323:
URL: https://github.com/apache/iceberg-rust/pull/3323#discussion_r4178487543
##########
crates/iceberg/src/transform/temporal.rs:
##########
@@ -205,50 +205,13 @@ pub struct Day;
impl Day {
#[inline]
- fn day_timestamp_micro(v: i64) -> Result<i32> {
- let secs = v / MICROS_PER_SECOND;
-
- let (nanos, offset) = if v >= 0 {
- let nanos = (v.rem_euclid(MICROS_PER_SECOND) * 1_000) as u32;
- let offset = 0i64;
- (nanos, offset)
- } else {
- let v = v + 1;
- let nanos = (v.rem_euclid(MICROS_PER_SECOND) * 1_000) as u32;
- let offset = 1i64;
- (nanos, offset)
- };
-
- let delta = Duration::new(secs, nanos).ok_or_else(|| {
- invalid_data!("Failed to create 'TimeDelta' from seconds {secs} and nanos {nanos}")
- })?;
-
- let days = (delta.num_days() - offset) as i32;
-
- Ok(days)
+ fn day_timestamp_micro(v: i64) -> i32 {
Review Comment:
Could you add a short comment here, and on `day_timestamp_nano`, saying the Java difference is intended? Right now the only in-code trace is the test comment that says what Java returns. Since the issue suggested flooring only the seconds to match Java, a later reader who compares this with `DateTimeUtil.convertMicros` may align it again and bring back the non-monotonic transform.
Something like: "Floors to the calendar day. Java returns the previous day for a pre-epoch timestamp exactly 999_999 µs after midnight. We keep the calendar day so the transform stays monotonic, which inclusive projection relies on."
If you file an upstream Java issue, linking it here would make the divergence trackable. I did not find an existing one.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@iceberg.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@iceberg.apache.org
For additional commands, e-mail: issues-help@iceberg.apache.org
Re: [PR] fix(transform): floor day transform for pre-epoch timestamps [iceberg-rust]
Posted by "kevinjqliu (via GitHub)" <gi...@apache.org>.
kevinjqliu commented on PR #3323:
URL: https://github.com/apache/iceberg-rust/pull/3323#issuecomment-5984364020
> PyIceberg floors as well (timedelta(microseconds=ts).days).
I double checked this, pyiceberg is using pyiceberg-core which is built from this repo, and fixed in this PR 😄
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@iceberg.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@iceberg.apache.org
For additional commands, e-mail: issues-help@iceberg.apache.org
Re: [PR] fix(transform): floor day transform for pre-epoch timestamps [iceberg-rust]
Posted by "kevinjqliu (via GitHub)" <gi...@apache.org>.
kevinjqliu merged PR #3323:
URL: https://github.com/apache/iceberg-rust/pull/3323
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@iceberg.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@iceberg.apache.org
For additional commands, e-mail: issues-help@iceberg.apache.org
Re: [PR] fix(transform): floor day transform for pre-epoch timestamps [iceberg-rust]
Posted by "andygrove (via GitHub)" <gi...@apache.org>.
andygrove commented on PR #3323:
URL: https://github.com/apache/iceberg-rust/pull/3323#issuecomment-5986189940
> Thanks @andygrove for the pr and @comphead for the review
Thanks @kevinjqliu! I appreciate you tying up those loose ends
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@iceberg.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@iceberg.apache.org
For additional commands, e-mail: issues-help@iceberg.apache.org