Skip to content

Support DST timezones conversion for ORC#4432

Draft
res-life wants to merge 25 commits into
NVIDIA:mainfrom
res-life:orc-tz
Draft

Support DST timezones conversion for ORC#4432
res-life wants to merge 25 commits into
NVIDIA:mainfrom
res-life:orc-tz

Conversation

@res-life

@res-life res-life commented Apr 3, 2026

Copy link
Copy Markdown
Collaborator

Contributes to NVIDIA/cudf-spark#13437.

Description

Corresponding Spark-Rapids PR

Previously convertOrcTimezones only handled non-DST (fixed-offset) timezones by looking up a pre-built transition table shipped in orc_timezone_info.data. DST timezones such as America/Los_Angeles and America/New_York were not fully covered.

This PR adds DST timezone support and optimizes the ORC timezone conversion path:

DST timezone support (main feature)

  • Replace the static orc_timezone_info.data file with runtime-generated OrcTimezoneInfo built from JVM TimeZone / ZoneRules APIs, covering all JVM-known timezones.
  • Add DstRule extraction by probing TimeZone.getOffset() to compute DST transition parameters (start/end month, day, day-of-week, time, mode) compatible with java.util.SimpleTimeZone semantics.
  • Implement GPU-side DST offset computation (compute_dst_offset) that evaluates DST rules per-row using the extracted rule parameters, eliminating the need for a pre-built transition table for current-era timestamps.
  • The CUDA kernel now handles three cases per timezone: historical transitions (binary search), DST rule evaluation, and fixed-offset (direct return).

CUDA kernel optimizations

  • Replace iterative millis_to_fields with a branchless civil calendar algorithm (era-based) for O(1) date decomposition.
  • Add DAYS_BEFORE_MONTH[] lookup table for O(1) month-day calculation, replacing the loop over months.
  • Add fixed-offset timezone fast path to skip transition table lookups entirely for UTC and other non-DST timezones.
  • Load transition tables into shared memory with proper int64_t alignment padding between writer and reader sections.

JNI API improvements

  • Introduce OrcTimezoneContext to pre-build writer/reader transition tables and DST rules once per table, avoiding redundant GPU allocations when converting multiple timestamp columns.
  • Accept ColumnView instead of ColumnVector in convertOrcTimezones since only getNativeView() is used.
  • Pass DST rule parameters (13-element int array) through JNI to the CUDA kernel.

OrcTimezoneInfo optimizations

  • Lighter DST rule verification: targeted checks around DST transition boundaries instead of hourly scan over 3 years (~200 vs ~52K getOffset() calls).
  • Reuse TimeZone objects instead of redundant TimeZone.getTimeZone() calls.
  • Replace java.util.Calendar with java.time.Instant/LocalDateTime for lighter date decomposition.
  • Skip DST probing early for fixed-offset timezones.

Tests

  • 370+ new lines of test code in GpuTimeZoneDBTest.java covering DST rule extraction, historical transitions, fixed-offset timezones, and cross-timezone ORC conversion.

Performance

1B rows, 4.1 GB ORC data (random timestamps 2000-2033), single GPU:

Test CPU avg (ms) GPU avg (ms) Speedup
cross-tz (LA→UTC) 23,662 13,815 1.71x
same-tz-baseline (LA→LA) 79,699 13,766 5.79x

The custom timezone kernel contributes <600ms overhead for 1B rows of cross-timezone conversion.

How to run perf test: see companion PR NVIDIA/cudf-spark#14544.

Correctness test

Refer to the suite OrcTimezoneSuite in NVIDIA/cudf-spark#14544

Checklists

  • This PR has added documentation for new or modified features or behaviors.
  • This PR has added new tests or modified existing tests to cover new code paths.
    GpuTimeZoneDBTest.java covers DST rule extraction, historical transitions, fixed-offset timezones, and cross-timezone ORC conversion.
  • Performance testing has been performed and its results are added in the PR description. Or, an issue has been filed with a link in the PR description.

Signed-off-by: Chong Gao chongg@nvidia.com

@res-life res-life changed the title Optimize ORC timezone rebasing CUDA kernel and JNI layer Optimize ORC timezone rebasing CUDA kernel Apr 3, 2026
@res-life

res-life commented Apr 7, 2026

Copy link
Copy Markdown
Collaborator Author

build

@res-life res-life changed the title Optimize ORC timezone rebasing CUDA kernel Support non-DST(Daylight Saving Time) timezones conversion for ORC Apr 7, 2026
@res-life res-life changed the title Support non-DST(Daylight Saving Time) timezones conversion for ORC Support DST(Daylight Saving Time) timezones conversion for ORC Apr 7, 2026
@res-life res-life changed the title Support DST(Daylight Saving Time) timezones conversion for ORC Support DST timezones conversion for ORC Apr 7, 2026
Signed-off-by: Chong Gao <res_life@163.com>
@res-life res-life marked this pull request as ready for review April 7, 2026 10:22
@greptile-apps

greptile-apps Bot commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds DST timezone support to the ORC timestamp conversion path in convertOrcTimezones, replacing the static orc_timezone_info.data snapshot with a runtime-generated OrcTimezoneInfo built from public JVM TimeZone/ZoneRules APIs.

  • The static binary file is removed and transition tables are now built at runtime (and cached) via buildRuntimeOrcTimezoneInfo, with DST rules extracted by either hourly probing or ZoneOffsetTransitionRule inspection and validated against reference years 2060, 2400, and 9997.
  • A new convert_timezones_kernel replaces the Thrust functor, adding shared-memory caching of transition tables, a branchless civil-calendar O(1) date decomposition, and a GPU-side compute_dst_offset that mirrors SimpleTimeZone.getOffset() for timestamps beyond the historical table.
  • A new OrcTimezoneContext is introduced to pre-build writer/reader tables once per pair and reuse them across multiple timestamp columns.

Confidence Score: 4/5

The PR is safe to merge. The core DST math is well-implemented with consistent Java/CUDA mirror implementations, verified across multiple reference years, and covered by new tests at historical transition boundaries and future-year DST fallback paths.

The main new code paths are internally consistent and cross-validated. Findings are limited to style and a minor inconsistency in how dstSavings is sourced between the two extraction paths, both caught by the existing verification. No correctness gaps were found in the shared-memory layout arithmetic, the southern-hemisphere year-boundary logic, or the JNI marshaling of DST rule parameters.

src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java warrants a second look around the two DST extraction paths and their differing dstSavings sources; the verification safety net is robust but the inconsistency is non-obvious.

Important Files Changed

Filename Overview
src/main/cpp/src/timezones.cu Adds GPU DST computation, civil-calendar helpers, shared-memory transition loading, and new kernel launch. Core math is well-implemented with proper handling of southern-hemisphere year-boundary DST and negative-epoch floor division.
src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java Complete rewrite from static file reader to runtime JVM API-based builder. DST extraction has two paths (probing + ZoneRules) with cross-year verification. The dstSavings used by the probing path comes from getDSTSavings() rather than the observed transition delta, but verification catches any mismatch.
src/main/java/com/nvidia/spark/rapids/jni/GpuTimeZoneDB.java Adds OrcTimezoneContext with proper idempotent close(), dstRuleToArray() for JNI serialization, and backward-compat zero-baseOffsetUs overload. Exception handling in buildOrcTimezoneContext correctly closes GPU tables on any failure path.
src/main/cpp/src/GpuTimeZoneDBJni.cpp Adds parse_dst_rule() helper that correctly null-checks GetIntArrayElements result and uses JNI_ABORT release mode. JNI signature updated for all new parameters.
src/test/java/com/nvidia/spark/rapids/jni/GpuTimeZoneDBTest.java 370+ lines of new test coverage: historical transition boundaries, future DST rule fallback, fixed-offset zones, southern hemisphere, and exhaustive half-hour optional stress test.
src/main/cpp/src/timezones.hpp Adds dst_rule struct and updated convert_orc_writer_reader_timezones signature with full parameter documentation.

Reviews (1): Last reviewed commit: "Add back convertOrcTimezones" | Re-trigger Greptile

Comment thread src/main/cpp/src/GpuTimeZoneDBJni.cpp Outdated
Comment thread src/main/cpp/src/timezones.cu
Comment thread src/main/java/com/nvidia/spark/rapids/jni/GpuTimeZoneDB.java
Comment thread src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java Outdated
@res-life

res-life commented Apr 8, 2026

Copy link
Copy Markdown
Collaborator Author

build

@res-life res-life requested review from mythrocks and ttnghia April 8, 2026 01:29
Chong Gao added 10 commits April 17, 2026 17:25
GetIntArrayElements returns nullptr and sets a pending OutOfMemoryError
when it cannot pin/copy the array (e.g. under memory pressure).
Dereferencing that pointer would crash the JVM. Return early with
has_dst=false so the pending exception surfaces on JNI exit instead of
triggering a SIGSEGV.

Signed-off-by: Chong Gao <chongg@nvidia.com>
Made-with: Cursor
parse_dst_rule can leave a pending Java exception (bad array length or
OOM from GetIntArrayElements), but control returns to the caller which
would otherwise launch convert_orc_writer_reader_timezones with partially
initialized rules and waste GPU work. Check for pending exceptions after
each parse_dst_rule call so the outer JNI_CATCH propagates the failure
immediately.

Signed-off-by: Chong Gao <chongg@nvidia.com>
Made-with: Cursor
Close() could be called more than once (e.g. explicit close followed by
try-with-resources unwind), which would double-close the underlying
writer/reader tables and trigger undefined behavior in the native layer.
Guard with a "closed" flag so subsequent calls are no-ops.

Signed-off-by: Chong Gao <chongg@nvidia.com>
Made-with: Cursor
get_transition_index only iterates by index off offset_begin; the matching
end pointer is never dereferenced. Dropping it also lets
convert_timestamp_between_timezones and convert_timezones_kernel stop
computing/forwarding those pointers, reducing register pressure in the
hot device code path.

Signed-off-by: Chong Gao <chongg@nvidia.com>
Made-with: Cursor
thrust::upper_bound returns the first element strictly greater than the
needle, so the subsequent `*iter == time_ms` check could never be true.
Remove the dead branch and add a comment explaining the upper_bound
semantics so the index-1 math is obvious.

Signed-off-by: Chong Gao <chongg@nvidia.com>
Made-with: Cursor
ZoneOffsetTransitionRule can express both "day-of-week on or after day"
(positive indicator, DOW_GE_DOM_MODE=2) and "day-of-week on or before
day" (negative indicator, DOW_LE_DOM_MODE=3). We reject the latter in
the precondition, so mode=2 is always safe. Document this invariant so
future readers don't wonder why the mode isn't derived from the
transition rule.

Signed-off-by: Chong Gao <chongg@nvidia.com>
Made-with: Cursor
DST_RULE_VALIDATION_YEARS previously only checked 2400 and 9997, both
well past any IANA explicit transition entry. That meant a zone whose
CPU-side TimeZone.getOffset() still followed explicit transitions into
the 2040s-2050s could drift from our derived recurring rule without the
verification catching it. Add 2060 so we notice mismatches within a
typical application lifetime; keep 2400/9997 to continue exercising
the recurring-rule fallback.

Signed-off-by: Chong Gao <chongg@nvidia.com>
Made-with: Cursor
MIN_SUPPORTED_ORC_UTC_MILLIS is computed via java.time.LocalDate
(proleptic Gregorian), whereas TimeZone.getOffset(long) uses a hybrid
Julian/Gregorian calendar internally. The distinction is academic here
since the offset lookup is instant-based and no zone has DST in year
0001, but the subtlety is non-obvious; add a comment so future readers
don't have to re-derive why this is safe.

Signed-off-by: Chong Gao <chongg@nvidia.com>
Made-with: Cursor
buildRuntimeOrcTimezoneInfo already resolves ZoneId and ZoneRules for
the incoming timezone id, but extractDstRuleFromZoneRules re-calls
GpuTimeZoneDB.getZoneId(timezoneId).getRules() for the same zone.
Pass the ZoneRules through extractDstRule so each
buildRuntimeOrcTimezoneInfo call resolves the zone at most once.

Signed-off-by: Chong Gao <chongg@nvidia.com>
Made-with: Cursor
Timezone metadata is now generated at runtime from java.util.TimeZone
and java.time.zone.ZoneRules, so results depend on whichever IANA
tzdata ships with the running JVM. Previously we shipped a frozen
OpenJDK-8 snapshot, so results were identical across environments.
Add a Javadoc note so users who see cross-environment discrepancies
know to check their JVM's tzdata version first.

Signed-off-by: Chong Gao <chongg@nvidia.com>
Made-with: Cursor
@res-life

Copy link
Copy Markdown
Collaborator Author

build

Chong Gao added 3 commits April 17, 2026 18:42
@res-life

Copy link
Copy Markdown
Collaborator Author

build

@firestarman

firestarman commented Apr 24, 2026

Copy link
Copy Markdown
Collaborator

Can you address the comments from greptile first ?

Chong Gao added 3 commits May 6, 2026 11:40
Per review: callers should know that the historical scan + DST-rule
probing path is costly, and that they should always go through the
cached get(...) wrapper.

Signed-off-by: Chong Gao <res_life@163.com>
~4 ms typical / ~12 ms worst-case (ART) on an Intel i7-10700K with
OpenJDK 17, measured across all 626 JVM-known zones.

Signed-off-by: Chong Gao <res_life@163.com>
Per review: cuda::std::array carries length and bounds-friendly access
better than a C-style array. __device__ storage is still needed because
days_in_month/days_before_month index this with non-compile-time values.

Signed-off-by: Chong Gao <res_life@163.com>
@res-life

res-life commented May 6, 2026

Copy link
Copy Markdown
Collaborator Author

build

Comment thread src/main/java/com/nvidia/spark/rapids/jni/GpuTimeZoneDB.java
@firestarman

Copy link
Copy Markdown
Collaborator

One nit as the above, others LGTM (Only JVM part)

Signed-off-by: Chong Gao <res_life@163.com>
@res-life

res-life commented May 7, 2026

Copy link
Copy Markdown
Collaborator Author

build

}
closed = true;
if (writerTzInfoTable != null) {
writerTzInfoTable.close();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just confirming here that OrcTimezoneContext is not meant for use from multiple threads, right? Each thread would have its own instance of OrcTimezoneContext? It probably would.

In case it doesn't, I would be afraid of a double-close at this point.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not thread safe, it's loaded/closed in Spark-Rapids plugin init/close phase, not multi-threaded.
Anyway, added not thread safe comment in the code.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in the split sub PRs.

int time = getTransitionRuleTimeMillis(transitionRule);
int timeMode = getTransitionRuleTimeMode(transitionRule);
// SimpleTimeZone mode constant: DOW_GE_DOM_MODE. Guaranteed by the precondition above.
int mode = 2;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry, this is not readable.

My previous comment was to introduce an enum for dst_rule_mode. We did that in timezones.cu. (Thank you.)
But the code here is using magic numbers. We should have a Java-side enum with the same indexes and names.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in the split sub PRs.

*/
class OrcTimezoneInfo {
public OrcTimezoneInfo(int rawOffset, long[] transitions, int[] offsets) {
public OrcTimezoneInfo(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: This should probably not be public. The class itself is package private.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in the split sub PRs.

Comment on lines +567 to +568
bool const writer_fixed = is_fixed_offset_tz(writer_trans_begin, writer_trans_end, writer_dst);
bool const reader_fixed = is_fixed_offset_tz(reader_trans_begin, reader_trans_end, reader_dst);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Silly question: Does this computation depend on the ts timestamp instance? My understanding is that it doesn't. We are recomputing the same result, per row.

Wouldn't it make sense to move these out of this function? In fact, this could be moved out of the kernel itself. We can pre-compute this, and pass it in as a function parameter. I suspect it will speed things up.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in sub-PR 5

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What changed

Before (per-row, called inside convert_timestamp_between_timezones — which runs once per timestamp):
bool const writer_fixed = is_fixed_offset_tz(writer_trans_begin, writer_trans_end, writer_dst);
bool const reader_fixed = is_fixed_offset_tz(reader_trans_begin, reader_trans_end, reader_dst);

After (host-side, once per kernel launch, in convert_timezones):
// Fixed-offset (no transitions and no DST) is a per-call property, so resolve
// it on the host and pass the bools through; the per-row path can then skip
// the redundant check.
bool const writer_fixed = (writer_trans_count == 0) && !writer_dst.has_dst;
bool const reader_fixed = (reader_trans_count == 0) && !reader_dst.has_dst;

convert_timezones_kernel<<<...>>>(
..., writer_fixed, ..., reader_fixed, ...);

Then the two bools are threaded through:

  1. convert_timezones_kernel signature gains bool writer_fixed and bool reader_fixed parameters.
  2. They're passed straight down into convert_timestamp_between_timezones(..., writer_fixed, ..., reader_fixed, ...).
  3. The per-row is_fixed_offset_tz() calls are gone.

@mythrocks mythrocks left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of readability nitpicks. One possible performance improvement.

This is a long PR, and not easy to review. :]

@res-life

Copy link
Copy Markdown
Collaborator Author

I'll split this huge PR.

@nvauto

nvauto commented May 18, 2026

Copy link
Copy Markdown
Collaborator

NOTE: release/26.06 has been created from main. Please retarget your PR to release/26.06 if it should be included in the release.

res-life pushed a commit to res-life/spark-rapids-jni that referenced this pull request May 19, 2026
Replace `orc_timezone_info.data` (544KB, OpenJDK-8 snapshot) with a
runtime build path that derives historical transitions from
`ZoneRules.getTransitions()` and `TimeZone.getOffset()`. Per-id results
are cached in `RUNTIME_TIMEZONE_INFOS`. `OrcTimezoneInfo` keeps its
`(rawOffset, transitions, offsets)` shape; the CUDA kernel and the
`hasDaylightSavingTime` DST guard are unchanged.

Switch `isSupportedTimeZone` to catch `DateTimeException` so IDs like
`+05:30` are handled the same as named zones.

Split from NVIDIA#4432.

Signed-off-by: Chong Gao <chongg@nvidia.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
res-life pushed a commit that referenced this pull request May 19, 2026
Final part of the split of #4432.

Adds GpuTimeZoneDBTest coverage for:

- DST rule extraction across DST and non-DST zones, validating
  startMonth / startDay / startDayOfWeek / endMonth / time-mode /
  rule-mode values.
- Probe-based extraction fallback for fixed-offset zones.
- Historical transition handling for America/Los_Angeles, America/New_York,
  Europe/London, Asia/Tokyo, etc.
- buildHistoricalTransitions edge cases.
- End-to-end convertOrcTimezones across cross-tz and same-tz cases
  using OrcTimezoneContext lifecycle.

After this PR the working tree matches PR #4432 head; the cumulative
diff against main is identical to #4432.

Signed-off-by: Chong Gao <chongg@nvidia.com>
@res-life res-life marked this pull request as draft May 22, 2026 01:15
@res-life

Copy link
Copy Markdown
Collaborator Author

Need to split into small PRs, convert to draft first.

res-life added a commit that referenced this pull request May 25, 2026
…pregenerated binary (#4539)

First in a stack splitting #4432 into reviewable chunks.

### Description

Previously `OrcTimezoneInfo` loaded its transition tables from a
pregenerated 544KB binary (`src/main/resources/orc_timezone_info.data`,
derived from `sun.util.calendar.ZoneInfo` on OpenJDK 8). This froze ORC
timezone behavior to that snapshot and required regenerating and
committing the binary whenever the data needed to change.

This PR replaces the binary with a runtime build path:

- `buildHistoricalTransitions` walks `ZoneRules.getTransitions()` from
year 0001 (the lower bound ORC supports) and probes
`TimeZone.getOffset()` to capture the offset before each known
transition.
- `collectTimeZoneTransitionsByScanning` covers any gap between two
known transitions by stepping forward at 1-day granularity and
binary-searching the exact transition millisecond when an offset change
is detected.
- Per-id results are cached in `RUNTIME_TIMEZONE_INFOS`.
- `getAllTimezoneIds()` now returns the live
`TimeZone.getAvailableIDs()` instead of a hard-coded array.

`OrcTimezoneInfo` keeps its `(rawOffset, transitions, offsets)` shape so
the CUDA kernel is unchanged. DST timezones remain blocked by the
existing `hasDaylightSavingTime` guard in `convertOrcTimezones` —
follow-up PRs lift this guard:

1. **(this PR)** Build OrcTimezoneInfo at runtime
2. DST rule extraction (Java side, CPU verified)
3. GPU DST kernel + new `convertOrcTimezones` signature
4. `OrcTimezoneContext` + remaining tests

`isSupportedTimeZone` is broadened from `catch (ZoneRulesException)` to
`catch (DateTimeException)` so IDs like `+05:30` are rejected/accepted
consistently with the runtime build path (`ZoneId.of` may throw either,
depending on whether the id has a malformed offset vs. an unknown name).

### Checklists

- [ ] Documentation: no user-facing change.
- [x] Tests: behavior preserved for non-DST timezones; covered by
existing tests in this repo and the companion spark-rapids PR.
- [x] Performance: no expected change for the existing non-DST path. The
first call per timezone pays a one-time runtime-build cost (measured ~4
ms typical, ~12 ms worst on the previous full-DST iteration); subsequent
calls hit the cache.

Signed-off-by: Chong Gao <chongg@nvidia.com>

---------

Signed-off-by: Chong Gao <chongg@nvidia.com>
Signed-off-by: Chong Gao <res_life@163.com>
Co-authored-by: Chong Gao <res_life@163.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
res-life added a commit that referenced this pull request Jun 5, 2026
… APIs (#4635)

## Context

Part 2 of the split of #4432 (full ORC DST GPU support). Part 1 (#4539,
runtime build of `OrcTimezoneInfo`) is merged. This PR adds the
Java-side machinery to **extract** a recurring DST rule for a zone, but
does **not** yet wire it into the GPU dispatch path — production
behavior is unchanged.

## What this adds

New file `OrcDstRuleExtractor.java` (package-private `final class`),
which hosts:
- `static final class DstRule` — SimpleTimeZone-shaped DST descriptor
(raw offset, dst savings, start/end month / day / dayOfWeek / time /
mode), 0-based month and Calendar-style 1=Sun..7=Sat to match the GPU
consumer.
- `extractDstRule(timezoneId, tz, rules)` — entry point. Returns `null`
for non-DST zones (`rules.isFixedOffset()` or `!tz.useDaylightTime()`).
Fail-fast sanity check that `tz` and `rules` describe the same zone
(guards against the silent-GMT trap where `TimeZone.getTimeZone(id)`
returns GMT for offset-style ids).
- **Path B (tried first):** `extractDstRuleByProbing` — probe
`tz.getOffset()` hourly across a reference year, find the two
transitions, encode them in SimpleTimeZone form. Captures what
`java.util.TimeZone.getOffset` actually returns, which is the source of
truth the GPU side must match.
- **Path A (fallback):** `extractDstRuleFromZoneRules` — derive from
`ZoneRules.getTransitionRules()`. Used for zones whose recurring rule
cannot be recovered by probing alone.
- Cross-year verification: the extracted rule is re-evaluated against
`tz.getOffset` across anchor years **2060, 2400, 9997** to catch
divergence well into the recurring-rule fallback regime.

In `OrcTimezoneInfo.java`:
- Widen `utcMillisForDate` and `binarySearchTransition` from `private`
to package-private so `OrcDstRuleExtractor` can reuse the same UTC
anchor and bracketed binary search. No behavioral change.

## What this does **not** change

- `GpuTimeZoneDB.convertOrcTimezones` — DST gate still throws
`UnsupportedOperationException` for DST zones.
- `buildRuntimeOrcTimezoneInfo` — does not yet populate any `DstRule`.
- `OrcTimezoneInfo`'s public constructor / fields (the two widened
methods are package-private, not public).
- JNI, CUDA.

`extractDstRule` is package-private and exercised only by
`OrcTimezoneInfoTest`.

## Verification

Extracted rules cross-checked against tzdata; the test suite is green on
**both JDK 8 (the build's `maven.compiler.target`) and JDK 17 Zulu**:

| Zone | start | end | dstSavings |
|------|-------|-----|------------|
| America/New_York | 2nd Sun of March, 02:00 standard | 1st Sun of
November, 01:00 standard | +1h |
| Europe/London | last Sun of March, 01:00 standard | last Sun of
October, 01:00 standard | +1h |
| Australia/Sydney | 1st Sun of October, 02:00 standard | 1st Sun of
April, 02:00 standard | +1h |
| Asia/Shanghai, UTC, +05:30 | `null` | `null` | — |

Real-zone tests assert the exact (`month`, `day`, `dayOfWeek`, `time`,
`mode`) shape for each zone. Synthetic-zone tests additionally cover
both extraction paths (probing success, ZoneRules fallback), every
`IllegalStateException` branch (unsupported rule count / shape,
zero-/both-positive-/both-negative-delta, mismatched savings, negative
day indicator, cross-year verification failure, tz/rules zone mismatch),
and edge cases (`isMidnightEndOfDay`, `TimeDefinition.WALL`, February
day-of-month clamp, multiple-DST-on probing guard). 26 tests total.

## Diff size

`+1314 / -2` across 3 files:
- `OrcDstRuleExtractor.java` `+529` (new file)
- `OrcTimezoneInfo.java` `+6 / -2` (visibility widening)
- `OrcTimezoneInfoTest.java` `+779`

No changes outside this slice.

## Follow-up parts

- **Part 3**: CUDA civil-calendar utilities + Java↔CUDA `DstRule`
serialization scaffolding (still all dead-code).
- **Part 4**: CUDA DST kernel implementation + kernel-level C++ tests.
- **Part 5**: Remove the DST gate in `convertOrcTimezones`, dispatch DST
zones through the new kernel, add end-to-end Java tests. This is the
only follow-up part that changes runtime behavior.

---------

Signed-off-by: Chong Gao <res_life@163.com>
Co-authored-by: Chong Gao <res_life@163.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants