Nikolina Vraneš created SPARK-58237:
---------------------------------------

             Summary: Fix BIN BY multi-day civil-time bin boundaries to use the 
absolute grid
                 Key: SPARK-58237
                 URL: https://issues.apache.org/jira/browse/SPARK-58237
             Project: Spark
          Issue Type: Sub-task
          Components: SQL
    Affects Versions: 5.0.0
            Reporter: Nikolina Vraneš


h2. Summary

For {{TIMESTAMP}} (LTZ) inputs in a non-UTC session, {{BinByExec}} placed 
multi-day civil-time bin boundaries by forward-walking (each bin's end reused 
as the next bin's start) instead of computing each boundary independently from 
the origin on the absolute grid {{bin_start(k) = timestampAddDayTime(origin, k 
* width)}} that {{time_bucket}} uses. Because {{timestampAddDayTime}} splits 
the width into a calendar-day part (which absorbs DST: a calendar day is 23h or 
25h of real time across a transition) plus a real-microsecond remainder, the 
operation is not associative across a DST or offset transition, so the walk 
drifts off the grid.

h2. Symptoms

For a width with a non-whole-day part (for example {{INTERVAL '36' HOUR}}) 
whose bins span a DST transition, boundaries are off by the transition hour 
from the second emitted bin onward. Two consequences:

* The same calendar bin is reported with different {{bin_start}} / {{bin_end}} 
depending on where a row's range began (a row that walked into bin N versus a 
row that started in bin N), so {{GROUP BY bin_start}} fragments one bin into 
two groups an hour apart.
* At a whole-day zone skip (for example Pacific/Apia on 2011-12-30, which does 
not exist in that zone), two consecutive grid boundaries collapse to the same 
instant.

Whole-day widths ({{INTERVAL '1' DAY}}, {{'2' DAY}}) are unaffected because the 
walk and the grid agree when the width has no sub-day remainder. Sub-day widths 
use the UTC fast path and are already grid-aligned. {{TIMESTAMP_NTZ}} inputs 
use UTC arithmetic and are unaffected.

The defect was latent in a code path exercised only by the emission of the 
second and later bins of a row, which {{time_bucket}} does not have (it returns 
one bucket per row), so the shared {{time_bucket}} tests could not surface it, 
and the existing {{BinBySuite}} DST tests all used whole-day widths.

h2. Fix

Compute every bin boundary on the absolute grid, index-driven, matching the 
grid {{time_bucket}} uses:

* {{DateTimeUtils.timeBucketDTInterval}} is factored into 
{{timeBucketIndexDTInterval}} (the bucket index containing a timestamp) and 
{{timeBucketBoundaryDTInterval}} (the grid boundary of a given index), with 
{{timeBucketDTInterval}} now defined as {{boundary(index(ts))}}. This is 
behavior-preserving for {{time_bucket}}; the existing {{DateTimeUtilsSuite}} 
cases are the regression guard.
* {{BinByExec}} advances the bucket index and reads each boundary off the grid 
via {{timeBucketBoundaryDTInterval}}, rather than walking with 
{{timestampAddDayTime}}. The zero-length-range path computes {{bin_end}} as the 
next grid boundary for the same reason.

The collapsed zero-width bin at a whole-day zone skip is emitted as a ratio-0 
row; the real bins still tile the range so ratios sum to 1.0.

h2. Tests

* {{DateTimeUtilsSuite}}: a round-trip test that {{boundary(index(ts)) == 
timeBucketDTInterval(ts)}} across sub-day, 36h spring-forward, and fall-back 
cases, plus a whole-day Apia skip where two consecutive boundaries collapse.
* {{BinBySuite}}: multi-bin 36h boundaries across spring-forward land on the 
grid; two rows reaching the same bin from different starts report the identical 
boundary; a whole-day Apia skip emits the zero-width bin; a zero-length range 
under a 36h width places {{bin_end}} on the grid.




--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to