You are viewing a plain text version of this content. The canonical link for it is here.
Posted to github@datafusion.apache.org by "andygrove (via GitHub)" <gi...@apache.org> on 2026/10/14 21:02:56 UTC
[I] Howard Hinnant civil-date algorithms hand-written 7 times across 3 crates [datafusion-comet]
andygrove opened a new issue, #6997:
URL: https://github.com/apache/datafusion-comet/issues/6997
### Describe the bug
## Problem
Location: native/spark-expr/src/kernels/temporal.rs:402
Symbol: civil_from_days / days_from_civil
Grep finds `days_from_civil` and `civil_from_days` as separate private copies in: (1) native/spark-expr/src/kernels/temporal.rs:407 (civil_from_days) and :425 (days_from_civil); (2) native/spark-expr/src/conversion_funcs/string.rs:1300 (days_from_civil), plus a test copy at :3440; (3) native/core/src/parquet/datetime_rebase.rs:162 (days_from_civil); (4) native/spark-expr/src/iceberg_funcs/temporal.rs:132 (civil_from_days, i32 in, returns (year, month0)); (5) native/core/src/execution/operators/iceberg_partition_path.rs:278 (civil_from_days, i64 in, (i64,u32,u32) out). The copies have already drifted in how they do floor division: string.rs uses `if y >= 0 { y / 400 } else { (y - 399) / 400 }`, iceberg_partition_path.rs uses a manual `shifted - 146_096` floor plus a cast to u64, and the others use `div_euclid`. Month indexing also differs (`(m + 9) % 12` vs. if/else). Each copy is a separately maintained implementation of the same correctness-critical calendar math that Spark compa
tibility depends on.
## Suggested fix:
Keep the `div_euclid` i64 versions from kernels/temporal.rs as `pub fn days_from_civil(y, m, d) -> i64` and `pub fn civil_from_days(days) -> (i64, i64, i64)` in a crate that both spark-expr and core depend on (for example native/common, or a `calendar` module in spark-expr). Replace the other copies with thin adapters, such as `(year as i32, month - 1)` for Iceberg, and add one shared property test that checks the pair round-trips across the full i32 epoch-day range.
## Code context
```rust
// Integer proleptic Gregorian calendar conversion, adapted from DataFusion 55.1.0's
// datetime/date_trunc.rs (Howard Hinnant's algorithms):
fn civil_from_days(days: i64) -> (i64, i64, i64) {
let z = days + 719_468;
let era = z.div_euclid(146_097);
...
}
fn days_from_civil(year: i64, month: i64, day: i64) -> i64 {
let year = year - i64::from(month <= 2);
let era = year.div_euclid(400);
...
era * 146_097 + day_of_era - 719_468
}
// other copies: conversion_funcs/string.rs:1300, core/parquet/datetime_rebase.rs:162,
// iceberg_funcs/temporal.rs:132, core/.../iceberg_partition_path.rs:278
```
### Steps to reproduce
_No response_
### Expected behavior
_No response_
### Additional context
_No response_
### Willingness to contribute
None
--
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: github-unsubscribe@datafusion.apache.org.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: github-unsubscribe@datafusion.apache.org
For additional commands, e-mail: github-help@datafusion.apache.org