Hi Roman, I think it's a good idea to clarify this.
I checked the Java side. DateTimeUtil.nanosToMicros has used Math.floorDiv since timestamp_ns / timestamptz_ns were first added, and Bucket hashes that result. I also recomputed the example: hashing -1 µs gives bucket[100] = 12, and hashing 0 µs gives 76. So floor division is what the reference impl has always done. Since Java, Go and Rust (now) all agree, I see this as a clarification of existing behavior, not a format change. I have a couple of suggestions on the PR (I will cast there too): - Put the rounding rule straight into note 3 so all the conversion requirements stay in one place, something like "Nanosecond timestamps must be converted to microsecond precision before hashing, by dividing by 1,000 and rouding toward negative infinity (floor division) to ensure timestamps have the same hash value". - Add a pre epoch test value to the timestamp_ns and timestamptz_ns rows, for example 1969-12-31T23:59:59.999999999 → 1651860712 (that's hashLong(-1)). Truncate toward zero would give 1669671676 instead, so the table itself would catch any implementation that gets this wrong. I will review the PR. Thanks! Regards JB On Fri, Nov 6, 2026 at 6:17 PM Roman Denysov via dev <[email protected]> wrote: > Hi everyone, > > While reviewing the partition transform fixtures in Pull request [1], @ > laskoviymishk <https://github.com/laskoviymishka>a and I noticed that > Appendix B [2] requires converting nanosecond timestamps to microseconds > before bucket hashing, but does not specify how to round negative values. > > For 1969-12-31T23:59:59.999999999 (−1 ns), floor division hashes −1 µs > and gives bucket[100] = 12; > > truncation toward zero hashes 0 µs and gives 76. > > Iceberg Java [3], Go [4], and Rust [5] use floor division. Rust recently > changed its implementation to match Java [6]. > > Could Appendix B [2] be updated with next sentence? > > For bucket hashing, convert nanoseconds since the Unix epoch to > microseconds by dividing by 1,000 and rounding toward negative infinity. > > That would give the pre-epoch conformance case an explicit spec basis. > > [1] > https://github.com/apache/iceberg-verification/pull/17#discussion_r4472635274 > [2] https://iceberg.apache.org/spec/#appendix-b-32-bit-hash-requirements > [3] > https://github.com/apache/iceberg/blob/main/api/src/main/java/org/apache/iceberg/util/DateTimeUtil.… > <https://github.com/apache/iceberg/blob/main/api/src/main/java/org/apache/iceberg/util/DateTimeUtil.java> > [4] https://github.com/apache/iceberg-go/blob/main/transforms.go > [5] > https://github.com/apache/iceberg-rust/blob/main/crates/iceberg/src/transform/bucket.rs > [6] https://github.com/apache/iceberg-rust/pull/2891 > > Thanks for attention, > Roman >
