From e0df2a6cab2fdc09a0050a63379f8ad6d51663b0 Mon Sep 17 00:00:00 2001 From: Chong Gao Date: Tue, 12 May 2026 10:12:55 +0800 Subject: [PATCH 01/17] Build ORC timezone metadata at runtime, drop pregenerated binary 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 #4432. Signed-off-by: Chong Gao Co-Authored-By: Claude Opus 4.7 --- .../spark/rapids/jni/GpuTimeZoneDB.java | 9 +- .../spark/rapids/jni/OrcTimezoneInfo.java | 364 ++++++++++-------- src/main/resources/orc_timezone_info.data | Bin 544076 -> 0 bytes 3 files changed, 203 insertions(+), 170 deletions(-) delete mode 100644 src/main/resources/orc_timezone_info.data diff --git a/src/main/java/com/nvidia/spark/rapids/jni/GpuTimeZoneDB.java b/src/main/java/com/nvidia/spark/rapids/jni/GpuTimeZoneDB.java index 0939b56c38..11e9a8fab6 100644 --- a/src/main/java/com/nvidia/spark/rapids/jni/GpuTimeZoneDB.java +++ b/src/main/java/com/nvidia/spark/rapids/jni/GpuTimeZoneDB.java @@ -24,13 +24,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.time.DateTimeException; import java.time.DayOfWeek; import java.time.Instant; import java.time.ZoneId; import java.time.zone.ZoneOffsetTransition; import java.time.zone.ZoneOffsetTransitionRule; import java.time.zone.ZoneRules; -import java.time.zone.ZoneRulesException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -42,7 +42,7 @@ /** * Gpu timezone utility. - * + *

* Provides the following APIs * - Timezone rebasing APIs: `fromTimestampToUtcTimestamp`, etc. * - Utilities for casting string with timezone to timestamp APIs @@ -66,7 +66,7 @@ public class GpuTimeZoneDB { * If a timezone has DST, then the list has 12 integers, which contains 2 * rules(start rule and end rule) * The integers in a list are: - * + *

* index 0: month:int, // from 1 (January) to 12 (December) * index 1: dayOfMonth: int, // from -28 to 31 excluding 0 * index 2: dayOfWeek: int, // from 0 (Monday) to 6 (Sunday), -1 means ignore @@ -189,7 +189,7 @@ public static boolean isSupportedTimeZone(String zoneId) { // check that zoneID is valid and supported by Java getZoneId(zoneId); return true; - } catch (ZoneRulesException e) { + } catch (DateTimeException e) { return false; } } @@ -531,7 +531,6 @@ private static ColumnVector getOffsetsForUtilTZ(OrcTimezoneInfo info) { private static Table getTableForUtilTZ(OrcTimezoneInfo info) { if (info.transitions == null) { - // fixed offset timezone return null; } try (ColumnVector trans = getTransitionsForUtilTZ(info); diff --git a/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java b/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java index 29b2631eb1..a5bba1563e 100644 --- a/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java +++ b/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java @@ -1,23 +1,46 @@ +/* + * Copyright (c) 2025-2026, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.nvidia.spark.rapids.jni; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.net.URL; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; +import java.time.DateTimeException; +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.zone.ZoneOffsetTransition; +import java.time.zone.ZoneRules; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.TimeZone; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; /** - * Used to hold timezone info read from `java.util.TimeZone` - * This class is used for ORC timezone conversion. - * For the other timezone conversions, it uses `java.time.ZoneId` APIs. - * The information is generated from OpenJDK 8. So some timezones in newer JDKs are missing. - * The reason why we do not read timezone info directly from `java.util.TimeZone`: - * `sun.util.calendar.ZoneInfo` is not public API, on some JDK distributions (like Oracle JDK), - * it's not accessible, E.g.: report error: package sun.util.calendar is not visible + * Holds ORC timezone metadata generated at runtime from public java.time/java.util APIs. + * Historical transitions come from ZoneRules, while offsets before the first transition are + * derived from java.util.TimeZone so ORC rebasing matches + * SerializationUtils.convertBetweenTimezones semantics without relying on non-public ZoneInfo APIs. + * + *

Runtime dependency: because the metadata is generated on the fly from + * {@link java.util.TimeZone}/{@link java.time.zone.ZoneRules}, the exact transition table is + * determined by the JVM's bundled IANA {@code tzdata}. Different JDK distributions or + * {@code tzdata} versions may produce slightly different historical transitions for the same + * zone id. This is strictly more correct than the previous frozen OpenJDK-8 snapshot, but users + * debugging cross-environment differences should first check the JVM's {@code tzdata} version. */ class OrcTimezoneInfo { public OrcTimezoneInfo(int rawOffset, long[] transitions, int[] offsets) { @@ -35,6 +58,19 @@ public OrcTimezoneInfo(int rawOffset, long[] transitions, int[] offsets) { // in milliseconds int[] offsets; + // Lower bound of the range ORC supports (year 0001-01-01 UTC). Computed via + // java.time.LocalDate, which uses the proleptic Gregorian calendar, whereas + // java.util.TimeZone.getOffset(long) internally uses a hybrid Julian/Gregorian + // calendar with the 1582 cutover for date-field interpretations. In practice + // this difference does not affect offset lookup (which is purely instant-based + // for ZoneInfo), so the two calendars agree on the offset at this instant. + private static final long MIN_SUPPORTED_ORC_UTC_MILLIS = utcMillisForDate(1, 0, 1); + private static final long HISTORICAL_TRANSITION_SCAN_STEP_MILLIS = 24L * 3600_000L; + + private static long utcMillisForDate(int year, int month, int day) { + return LocalDate.of(year, month + 1, day).toEpochDay() * 24L * 3600_000L; + } + @Override public String toString() { return "OrcTimezoneInfo{" + @@ -44,180 +80,178 @@ public String toString() { '}'; } - // The following is Static fields and methods. - // The `orc_timezone_info.data` file is generated from `sun.util.calendar.ZoneInfo` on OpenJDK 8 - // It first reads `transitions` and `offsets` fields from `ZoneInfo` via reflection. - // Then calculate the actual transition and offset values via: - // - actual transition = transition >> 12 - // - actual offset = offsets[transition & 0x0FL] - // For more details, please refer to `sun.util.calendar.ZoneInfo` source code. + private static final ConcurrentMap RUNTIME_TIMEZONE_INFOS = + new ConcurrentHashMap<>(); - // Refer to `serializeTimezoneInfo` method for how to generate the file. - private static final String ORC_TIMEZONE_FILE = "orc_timezone_info.data"; + /** + * Get timezone info for the specified timezone ID. + * Historical transitions are generated at runtime from public JVM APIs and cached per ID. + * + * @param timezoneId timezone Id + * @return timezone info + */ + public static OrcTimezoneInfo get(String timezoneId) { + return RUNTIME_TIMEZONE_INFOS.computeIfAbsent( + timezoneId, + OrcTimezoneInfo::buildRuntimeOrcTimezoneInfo); + } - // the mapped memory for the file - private static MappedByteBuffer serializedBuf = null; + /** + * Build ORC timezone metadata from public java.time/java.util APIs. Invalid IDs use the same + * validation as {@link GpuTimeZoneDB#getZoneId(String)} and fail with + * {@link IllegalArgumentException} (no silent fallback to GMT). + * + *

Cost: this is non-trivial — it scans every historical {@link ZoneOffsetTransition} + * from year 1 onward. Results are cached in {@link #RUNTIME_TIMEZONE_INFOS} (see + * {@link #get(String)}), so callers should always go through {@code get(...)} rather than + * invoking this directly. + */ + private static OrcTimezoneInfo buildRuntimeOrcTimezoneInfo(String timezoneId) { + final ZoneId zoneId; + try { + zoneId = GpuTimeZoneDB.getZoneId(timezoneId); + } catch (DateTimeException e) { + throw new IllegalArgumentException("Timezone ID not found: " + timezoneId, e); + } + + ZoneRules rules = zoneId.getRules(); + if (rules.isFixedOffset()) { + // IDs like "+05:30" are valid ZoneIds but TimeZone.getTimeZone() silently + // maps them to GMT (offset 0). Derive the offset from ZoneRules instead so + // the GPU path doesn't treat them as UTC. + int fixedOffsetMs = rules.getOffset(Instant.EPOCH).getTotalSeconds() * 1000; + return new OrcTimezoneInfo(fixedOffsetMs, null, null); + } + TimeZone tz = TimeZone.getTimeZone(timezoneId); + List transitionList = rules.getTransitions(); + HistoricalTransitions historicalTransitions = buildHistoricalTransitions(tz, transitionList); + if (historicalTransitions.transitions == null) { + return new OrcTimezoneInfo(tz.getRawOffset(), null, null); + } + return new OrcTimezoneInfo(tz.getRawOffset(), + historicalTransitions.transitions, historicalTransitions.offsets); + } + + public static List getAllTimezoneIds() { + String[] ids = TimeZone.getAvailableIDs(); + Arrays.sort(ids); + return Arrays.asList(ids); + } - static { - readTimezoneInfoFromFile(); + private static int getInitialOffset(TimeZone tz) { + // ORC only supports timestamps from year 0001 onward. For dates before the + // first historical transition in that range, java.util.TimeZone can differ + // from ZoneRules' earliest wall offset (for example, it may use the zone's + // standard raw offset instead of an older LMT offset). Sample the beginning + // of the supported range so the GPU matches TimeZone.getOffset(). + return tz.getOffset(MIN_SUPPORTED_ORC_UTC_MILLIS); } - private static void readTimezoneInfoFromFile() { - URL path = OrcTimezoneInfo.class.getClassLoader().getResource(ORC_TIMEZONE_FILE); - if (path == null) { - throw new RuntimeException("Can not find ORC timezone info file " + ORC_TIMEZONE_FILE); + private static HistoricalTransitions buildHistoricalTransitions( + TimeZone tz, + List transitionList) { + if (transitionList.isEmpty()) { + return HistoricalTransitions.EMPTY; } - try (RandomAccessFile file = new RandomAccessFile(path.getPath(), "r"); - FileChannel fileChannel = file.getChannel()) { + List transitions = new ArrayList<>(); + List offsets = new ArrayList<>(); + long scanCursor = MIN_SUPPORTED_ORC_UTC_MILLIS; + int currentOffset = getInitialOffset(tz); - if (fileChannel.size() > 2 * 1024 * 1024) { // > 2M - throw new RuntimeException("Failed to load ORC timezone info, file is too large > 2M."); + for (ZoneOffsetTransition transition : transitionList) { + long transitionMs = transition.getInstant().toEpochMilli(); + if (transitionMs < MIN_SUPPORTED_ORC_UTC_MILLIS) { + continue; } - // Map the file into memory - serializedBuf = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size()); - } catch (IOException e) { - throw new RuntimeException("Failed to load ORC timezone info file " + ORC_TIMEZONE_FILE, e); - } - } + long beforeTransitionMs = transitionMs - 1; + int offsetBeforeTransition = tz.getOffset(beforeTransitionMs); + if (beforeTransitionMs >= scanCursor && offsetBeforeTransition != currentOffset) { + currentOffset = collectTimeZoneTransitionsByScanning( + tz, scanCursor, beforeTransitionMs, currentOffset, transitions, offsets); + } - /** - * Get timezone info for the specified timezone Id - * @param timezoneId timezone Id - * @return timezone info - */ - public static OrcTimezoneInfo get(String timezoneId) { - int index = Arrays.binarySearch(timezoneIds, timezoneId); - if (index < 0) { - throw new IllegalArgumentException("Timezone ID not found: " + timezoneId); + int offsetAtTransition = tz.getOffset(transitionMs); + if (offsetAtTransition != offsetBeforeTransition) { + transitions.add(transitionMs); + offsets.add(offsetAtTransition); + currentOffset = offsetAtTransition; + } + scanCursor = transitionMs; } - // shallow copy - ByteBuffer buf = serializedBuf.duplicate(); - buf.order(ByteOrder.BIG_ENDIAN); - - int timezoneInfoOffsetInFile = buf.getInt(Integer.BYTES * index); - buf.position(timezoneInfoOffsetInFile); + if (transitions.isEmpty()) { + return HistoricalTransitions.EMPTY; + } + return new HistoricalTransitions(toLongArray(transitions), toIntArray(offsets)); + } - int rawOffsets = buf.getInt(); + private static int collectTimeZoneTransitionsByScanning( + TimeZone tz, + long scanStartMs, + long scanEndMs, + int startOffset, + List transitions, + List offsets) { + long cursor = scanStartMs; + int currentOffset = startOffset; + while (cursor < scanEndMs) { + long probe = Math.min(cursor + HISTORICAL_TRANSITION_SCAN_STEP_MILLIS, scanEndMs); + int probeOffset = tz.getOffset(probe); + if (probeOffset == currentOffset) { + cursor = probe; + continue; + } - int numTransitions = buf.getInt(); - long[] transitions = new long[numTransitions]; - for (int i = 0; i < numTransitions; ++i) { - transitions[i] = buf.getLong(); + long exactTransition = binarySearchTransition(tz, cursor, probe); + int offsetAfterTransition = tz.getOffset(exactTransition); + transitions.add(exactTransition); + offsets.add(offsetAfterTransition); + currentOffset = offsetAfterTransition; + cursor = exactTransition; } + return currentOffset; + } - int numOffsets = buf.getInt(); - int[] offsets = new int[numOffsets]; - for (int i = 0; i < numOffsets; ++i) { - offsets[i] = buf.getInt(); + private static long binarySearchTransition(TimeZone tz, long lo, long hi) { + int loOffset = tz.getOffset(lo); + while (hi - lo > 1) { + long mid = lo + (hi - lo) / 2; + if (tz.getOffset(mid) == loOffset) { + lo = mid; + } else { + hi = mid; + } } + return hi; + } - return new OrcTimezoneInfo(rawOffsets, transitions, offsets); + private static long[] toLongArray(List values) { + long[] result = new long[values.size()]; + for (int i = 0; i < values.size(); i++) { + result[i] = values.get(i); + } + return result; } - public static List getAllTimezoneIds() { - return Arrays.asList(timezoneIds); + private static int[] toIntArray(List values) { + int[] result = new int[values.size()]; + for (int i = 0; i < values.size(); i++) { + result[i] = values.get(i); + } + return result; } - private static final String[] timezoneIds = {"ACT", "AET", "AGT", "ART", "AST", "Africa/Abidjan", "Africa/Accra", "Africa/Addis_Ababa", "Africa/Algiers", "Africa/Asmara", "Africa/Asmera", "Africa/Bamako", "Africa/Bangui", "Africa/Banjul", "Africa/Bissau", "Africa/Blantyre", "Africa/Brazzaville", "Africa/Bujumbura", "Africa/Cairo", "Africa/Casablanca", "Africa/Ceuta", "Africa/Conakry", "Africa/Dakar", "Africa/Dar_es_Salaam", "Africa/Djibouti", "Africa/Douala", "Africa/El_Aaiun", "Africa/Freetown", "Africa/Gaborone", "Africa/Harare", "Africa/Johannesburg", "Africa/Juba", "Africa/Kampala", "Africa/Khartoum", "Africa/Kigali", "Africa/Kinshasa", "Africa/Lagos", "Africa/Libreville", "Africa/Lome", "Africa/Luanda", "Africa/Lubumbashi", "Africa/Lusaka", "Africa/Malabo", "Africa/Maputo", "Africa/Maseru", "Africa/Mbabane", "Africa/Mogadishu", "Africa/Monrovia", "Africa/Nairobi", "Africa/Ndjamena", "Africa/Niamey", "Africa/Nouakchott", "Africa/Ouagadougou", "Africa/Porto-Novo", "Africa/Sao_Tome", "Africa/Timbuktu", "Africa/Tripoli", "Africa/Tunis", "Africa/Windhoek", "America/Adak", "America/Anchorage", "America/Anguilla", "America/Antigua", "America/Araguaina", "America/Argentina/Buenos_Aires", "America/Argentina/Catamarca", "America/Argentina/ComodRivadavia", "America/Argentina/Cordoba", "America/Argentina/Jujuy", "America/Argentina/La_Rioja", "America/Argentina/Mendoza", "America/Argentina/Rio_Gallegos", "America/Argentina/Salta", "America/Argentina/San_Juan", "America/Argentina/San_Luis", "America/Argentina/Tucuman", "America/Argentina/Ushuaia", "America/Aruba", "America/Asuncion", "America/Atikokan", "America/Atka", "America/Bahia", "America/Bahia_Banderas", "America/Barbados", "America/Belem", "America/Belize", "America/Blanc-Sablon", "America/Boa_Vista", "America/Bogota", "America/Boise", "America/Buenos_Aires", "America/Cambridge_Bay", "America/Campo_Grande", "America/Cancun", "America/Caracas", "America/Catamarca", "America/Cayenne", "America/Cayman", "America/Chicago", "America/Chihuahua", "America/Ciudad_Juarez", "America/Coral_Harbour", "America/Cordoba", "America/Costa_Rica", "America/Coyhaique", "America/Creston", "America/Cuiaba", "America/Curacao", "America/Danmarkshavn", "America/Dawson", "America/Dawson_Creek", "America/Denver", "America/Detroit", "America/Dominica", "America/Edmonton", "America/Eirunepe", "America/El_Salvador", "America/Ensenada", "America/Fort_Nelson", "America/Fort_Wayne", "America/Fortaleza", "America/Glace_Bay", "America/Godthab", "America/Goose_Bay", "America/Grand_Turk", "America/Grenada", "America/Guadeloupe", "America/Guatemala", "America/Guayaquil", "America/Guyana", "America/Halifax", "America/Havana", "America/Hermosillo", "America/Indiana/Indianapolis", "America/Indiana/Knox", "America/Indiana/Marengo", "America/Indiana/Petersburg", "America/Indiana/Tell_City", "America/Indiana/Vevay", "America/Indiana/Vincennes", "America/Indiana/Winamac", "America/Indianapolis", "America/Inuvik", "America/Iqaluit", "America/Jamaica", "America/Jujuy", "America/Juneau", "America/Kentucky/Louisville", "America/Kentucky/Monticello", "America/Knox_IN", "America/Kralendijk", "America/La_Paz", "America/Lima", "America/Los_Angeles", "America/Louisville", "America/Lower_Princes", "America/Maceio", "America/Managua", "America/Manaus", "America/Marigot", "America/Martinique", "America/Matamoros", "America/Mazatlan", "America/Mendoza", "America/Menominee", "America/Merida", "America/Metlakatla", "America/Mexico_City", "America/Miquelon", "America/Moncton", "America/Monterrey", "America/Montevideo", "America/Montreal", "America/Montserrat", "America/Nassau", "America/New_York", "America/Nipigon", "America/Nome", "America/Noronha", "America/North_Dakota/Beulah", "America/North_Dakota/Center", "America/North_Dakota/New_Salem", "America/Nuuk", "America/Ojinaga", "America/Panama", "America/Pangnirtung", "America/Paramaribo", "America/Phoenix", "America/Port-au-Prince", "America/Port_of_Spain", "America/Porto_Acre", "America/Porto_Velho", "America/Puerto_Rico", "America/Punta_Arenas", "America/Rainy_River", "America/Rankin_Inlet", "America/Recife", "America/Regina", "America/Resolute", "America/Rio_Branco", "America/Rosario", "America/Santa_Isabel", "America/Santarem", "America/Santiago", "America/Santo_Domingo", "America/Sao_Paulo", "America/Scoresbysund", "America/Shiprock", "America/Sitka", "America/St_Barthelemy", "America/St_Johns", "America/St_Kitts", "America/St_Lucia", "America/St_Thomas", "America/St_Vincent", "America/Swift_Current", "America/Tegucigalpa", "America/Thule", "America/Thunder_Bay", "America/Tijuana", "America/Toronto", "America/Tortola", "America/Vancouver", "America/Virgin", "America/Whitehorse", "America/Winnipeg", "America/Yakutat", "America/Yellowknife", "Antarctica/Casey", "Antarctica/Davis", "Antarctica/DumontDUrville", "Antarctica/Macquarie", "Antarctica/Mawson", "Antarctica/McMurdo", "Antarctica/Palmer", "Antarctica/Rothera", "Antarctica/South_Pole", "Antarctica/Syowa", "Antarctica/Troll", "Antarctica/Vostok", "Arctic/Longyearbyen", "Asia/Aden", "Asia/Almaty", "Asia/Amman", "Asia/Anadyr", "Asia/Aqtau", "Asia/Aqtobe", "Asia/Ashgabat", "Asia/Ashkhabad", "Asia/Atyrau", "Asia/Baghdad", "Asia/Bahrain", "Asia/Baku", "Asia/Bangkok", "Asia/Barnaul", "Asia/Beirut", "Asia/Bishkek", "Asia/Brunei", "Asia/Calcutta", "Asia/Chita", "Asia/Choibalsan", "Asia/Chongqing", "Asia/Chungking", "Asia/Colombo", "Asia/Dacca", "Asia/Damascus", "Asia/Dhaka", "Asia/Dili", "Asia/Dubai", "Asia/Dushanbe", "Asia/Famagusta", "Asia/Gaza", "Asia/Harbin", "Asia/Hebron", "Asia/Ho_Chi_Minh", "Asia/Hong_Kong", "Asia/Hovd", "Asia/Irkutsk", "Asia/Istanbul", "Asia/Jakarta", "Asia/Jayapura", "Asia/Jerusalem", "Asia/Kabul", "Asia/Kamchatka", "Asia/Karachi", "Asia/Kashgar", "Asia/Kathmandu", "Asia/Katmandu", "Asia/Khandyga", "Asia/Kolkata", "Asia/Krasnoyarsk", "Asia/Kuala_Lumpur", "Asia/Kuching", "Asia/Kuwait", "Asia/Macao", "Asia/Macau", "Asia/Magadan", "Asia/Makassar", "Asia/Manila", "Asia/Muscat", "Asia/Nicosia", "Asia/Novokuznetsk", "Asia/Novosibirsk", "Asia/Omsk", "Asia/Oral", "Asia/Phnom_Penh", "Asia/Pontianak", "Asia/Pyongyang", "Asia/Qatar", "Asia/Qostanay", "Asia/Qyzylorda", "Asia/Rangoon", "Asia/Riyadh", "Asia/Saigon", "Asia/Sakhalin", "Asia/Samarkand", "Asia/Seoul", "Asia/Shanghai", "Asia/Singapore", "Asia/Srednekolymsk", "Asia/Taipei", "Asia/Tashkent", "Asia/Tbilisi", "Asia/Tehran", "Asia/Tel_Aviv", "Asia/Thimbu", "Asia/Thimphu", "Asia/Tokyo", "Asia/Tomsk", "Asia/Ujung_Pandang", "Asia/Ulaanbaatar", "Asia/Ulan_Bator", "Asia/Urumqi", "Asia/Ust-Nera", "Asia/Vientiane", "Asia/Vladivostok", "Asia/Yakutsk", "Asia/Yangon", "Asia/Yekaterinburg", "Asia/Yerevan", "Atlantic/Azores", "Atlantic/Bermuda", "Atlantic/Canary", "Atlantic/Cape_Verde", "Atlantic/Faeroe", "Atlantic/Faroe", "Atlantic/Jan_Mayen", "Atlantic/Madeira", "Atlantic/Reykjavik", "Atlantic/South_Georgia", "Atlantic/St_Helena", "Atlantic/Stanley", "Australia/ACT", "Australia/Adelaide", "Australia/Brisbane", "Australia/Broken_Hill", "Australia/Canberra", "Australia/Currie", "Australia/Darwin", "Australia/Eucla", "Australia/Hobart", "Australia/LHI", "Australia/Lindeman", "Australia/Lord_Howe", "Australia/Melbourne", "Australia/NSW", "Australia/North", "Australia/Perth", "Australia/Queensland", "Australia/South", "Australia/Sydney", "Australia/Tasmania", "Australia/Victoria", "Australia/West", "Australia/Yancowinna", "BET", "BST", "Brazil/Acre", "Brazil/DeNoronha", "Brazil/East", "Brazil/West", "CAT", "CET", "CNT", "CST", "CST6CDT", "CTT", "Canada/Atlantic", "Canada/Central", "Canada/Eastern", "Canada/Mountain", "Canada/Newfoundland", "Canada/Pacific", "Canada/Saskatchewan", "Canada/Yukon", "Chile/Continental", "Chile/EasterIsland", "Cuba", "EAT", "ECT", "EET", "EST", "EST5EDT", "Egypt", "Eire", "Etc/GMT", "Etc/GMT+0", "Etc/GMT+1", "Etc/GMT+10", "Etc/GMT+11", "Etc/GMT+12", "Etc/GMT+2", "Etc/GMT+3", "Etc/GMT+4", "Etc/GMT+5", "Etc/GMT+6", "Etc/GMT+7", "Etc/GMT+8", "Etc/GMT+9", "Etc/GMT-0", "Etc/GMT-1", "Etc/GMT-10", "Etc/GMT-11", "Etc/GMT-12", "Etc/GMT-13", "Etc/GMT-14", "Etc/GMT-2", "Etc/GMT-3", "Etc/GMT-4", "Etc/GMT-5", "Etc/GMT-6", "Etc/GMT-7", "Etc/GMT-8", "Etc/GMT-9", "Etc/GMT0", "Etc/Greenwich", "Etc/UCT", "Etc/UTC", "Etc/Universal", "Etc/Zulu", "Europe/Amsterdam", "Europe/Andorra", "Europe/Astrakhan", "Europe/Athens", "Europe/Belfast", "Europe/Belgrade", "Europe/Berlin", "Europe/Bratislava", "Europe/Brussels", "Europe/Bucharest", "Europe/Budapest", "Europe/Busingen", "Europe/Chisinau", "Europe/Copenhagen", "Europe/Dublin", "Europe/Gibraltar", "Europe/Guernsey", "Europe/Helsinki", "Europe/Isle_of_Man", "Europe/Istanbul", "Europe/Jersey", "Europe/Kaliningrad", "Europe/Kiev", "Europe/Kirov", "Europe/Kyiv", "Europe/Lisbon", "Europe/Ljubljana", "Europe/London", "Europe/Luxembourg", "Europe/Madrid", "Europe/Malta", "Europe/Mariehamn", "Europe/Minsk", "Europe/Monaco", "Europe/Moscow", "Europe/Nicosia", "Europe/Oslo", "Europe/Paris", "Europe/Podgorica", "Europe/Prague", "Europe/Riga", "Europe/Rome", "Europe/Samara", "Europe/San_Marino", "Europe/Sarajevo", "Europe/Saratov", "Europe/Simferopol", "Europe/Skopje", "Europe/Sofia", "Europe/Stockholm", "Europe/Tallinn", "Europe/Tirane", "Europe/Tiraspol", "Europe/Ulyanovsk", "Europe/Uzhgorod", "Europe/Vaduz", "Europe/Vatican", "Europe/Vienna", "Europe/Vilnius", "Europe/Volgograd", "Europe/Warsaw", "Europe/Zagreb", "Europe/Zaporozhye", "Europe/Zurich", "GB", "GB-Eire", "GMT", "GMT0", "Greenwich", "HST", "Hongkong", "IET", "IST", "Iceland", "Indian/Antananarivo", "Indian/Chagos", "Indian/Christmas", "Indian/Cocos", "Indian/Comoro", "Indian/Kerguelen", "Indian/Mahe", "Indian/Maldives", "Indian/Mauritius", "Indian/Mayotte", "Indian/Reunion", "Iran", "Israel", "JST", "Jamaica", "Japan", "Kwajalein", "Libya", "MET", "MIT", "MST", "MST7MDT", "Mexico/BajaNorte", "Mexico/BajaSur", "Mexico/General", "NET", "NST", "NZ", "NZ-CHAT", "Navajo", "PLT", "PNT", "PRC", "PRT", "PST", "PST8PDT", "Pacific/Apia", "Pacific/Auckland", "Pacific/Bougainville", "Pacific/Chatham", "Pacific/Chuuk", "Pacific/Easter", "Pacific/Efate", "Pacific/Enderbury", "Pacific/Fakaofo", "Pacific/Fiji", "Pacific/Funafuti", "Pacific/Galapagos", "Pacific/Gambier", "Pacific/Guadalcanal", "Pacific/Guam", "Pacific/Honolulu", "Pacific/Johnston", "Pacific/Kanton", "Pacific/Kiritimati", "Pacific/Kosrae", "Pacific/Kwajalein", "Pacific/Majuro", "Pacific/Marquesas", "Pacific/Midway", "Pacific/Nauru", "Pacific/Niue", "Pacific/Norfolk", "Pacific/Noumea", "Pacific/Pago_Pago", "Pacific/Palau", "Pacific/Pitcairn", "Pacific/Pohnpei", "Pacific/Ponape", "Pacific/Port_Moresby", "Pacific/Rarotonga", "Pacific/Saipan", "Pacific/Samoa", "Pacific/Tahiti", "Pacific/Tarawa", "Pacific/Tongatapu", "Pacific/Truk", "Pacific/Wake", "Pacific/Wallis", "Pacific/Yap", "Poland", "Portugal", "ROK", "SST", "Singapore", "SystemV/AST4", "SystemV/AST4ADT", "SystemV/CST6", "SystemV/CST6CDT", "SystemV/EST5", "SystemV/EST5EDT", "SystemV/HST10", "SystemV/MST7", "SystemV/MST7MDT", "SystemV/PST8", "SystemV/PST8PDT", "SystemV/YST9", "SystemV/YST9YDT", "Turkey", "UCT", "US/Alaska", "US/Aleutian", "US/Arizona", "US/Central", "US/East-Indiana", "US/Eastern", "US/Hawaii", "US/Indiana-Starke", "US/Michigan", "US/Mountain", "US/Pacific", "US/Samoa", "UTC", "Universal", "VST", "W-SU", "WET", "Zulu"}; + private static final class HistoricalTransitions { + static final HistoricalTransitions EMPTY = new HistoricalTransitions(null, null); - /** - * This method is only used to generate the timezone info file for maintenance purpose. - * - * The generated file is based on OpenJDK 8's `sun.util.calendar.ZoneInfo` implementation. - * Since `ZoneInfo` is not public API, on some JDK distributions (like Oracle JDK), - * it's not accessible. So we comment the method out to avoid build issues. - * - * File format: - * - First N * 4 bytes: N is number of timezone Ids - * - each 4 bytes is the offset of the timezone info in the file - * - Then each timezone info: - * - 4 bytes: rawOffset (int) - * - 4 bytes: numTransitions (int) - * - numTransitions * 8 bytes: transitions (long[]) - * - 4 bytes: numOffsets (int) - * - numOffsets * 4 bytes: offsets (int[]) - * - * How to do the maintenance: - * - update the `timezoneIds` via TimeZone.getAvailableIDs() and sort them. - * - run this method to generate the timezone info file, and copy the file to resources folder. - */ - public static void serializeTimezoneInfo() { -// try { -// String path = "/tmp/orc_timezone_info.data"; -// -// // sort timezone ids -// String[] ids = TimeZone.getAvailableIDs(); -// ArrayList sortedIds = new ArrayList<>(Arrays.asList(ids)); -// sortedIds.sort(String::compareTo); -// -// List timezoneOffsets = new ArrayList<>(); -// DataOutputStream out = new DataOutputStream(Files.newOutputStream(Paths.get(path))); -// -// // from ZoneInfo source code -// long OFFSET_MASK_IN_ZONE_INFO = 0x0FL; -// int TRANSITION_NSHIFT_IN_ZONE_INFO = 12; -// -// // collect offsets for each timezone -// int timezoneOffsetInFile = 0; -// for (String id : sortedIds) { -// timezoneOffsets.add(timezoneOffsetInFile); -// -// ZoneInfo zoneInfo = (ZoneInfo) TimeZone.getTimeZone(id); -// long[] trans = (long[]) FieldUtils.readField(zoneInfo, "transitions"); -// int numTransitions = trans == null ? 0 : trans.length; -// -// // timezone serialized size calculation -// timezoneOffsetInFile += 4; // rawOffset -// timezoneOffsetInFile += 4; // numTransitions -// timezoneOffsetInFile += numTransitions * 8; // transitions longs -// timezoneOffsetInFile += 4; // numOffsets -// timezoneOffsetInFile += numTransitions * 4; // offsets ints -// } -// -// // First write all timezone offsets in the file -// int totalOffsetIndicesSize = sortedIds.size() * 4; -// for (int off : timezoneOffsets) { -// out.writeInt(off + totalOffsetIndicesSize); -// } -// -// // Then write each timezone info -// for (String id : sortedIds) { -// ZoneInfo zoneInfo = (ZoneInfo) TimeZone.getTimeZone(id); -// long[] trans = (long[]) FieldUtils.readField(zoneInfo, "transitions"); -// int[] offs = (int[]) FieldUtils.readField(zoneInfo, "offsets"); -// int rawOff = (int) FieldUtils.readField(zoneInfo, "rawOffset"); -// -// int numTransitions = trans == null ? 0 : trans.length; -// -// long[] actualTrans = new long[numTransitions]; -// int[] actualOffsets = new int[numTransitions]; -// for (int i = 0; i < numTransitions; ++i) { -// // `trans` is combination of transition and offset index -// actualTrans[i] = trans[i] >> TRANSITION_NSHIFT_IN_ZONE_INFO; -// // the `offs` is a dictionary, get the actual offset value via index -// // `trans[i] & OFFSET_MASK_IN_ZONE_INFO` is to get offset index -// actualOffsets[i] = offs[(int) (trans[i] & OFFSET_MASK_IN_ZONE_INFO)]; -// } -// -// out.writeInt(rawOff); -// -// out.writeInt(numTransitions); -// for (long t : actualTrans) { -// out.writeLong(t); -// } -// -// out.writeInt(numTransitions); -// for (int o : actualOffsets) { -// out.writeInt(o); -// } -// } -// out.flush(); -// out.close(); -// } catch (Exception e) { -// throw new RuntimeException("Failed to serialize ORC timezone info.", e); -// } + final long[] transitions; + final int[] offsets; + + private HistoricalTransitions(long[] transitions, int[] offsets) { + this.transitions = transitions; + this.offsets = offsets; + } } } diff --git a/src/main/resources/orc_timezone_info.data b/src/main/resources/orc_timezone_info.data deleted file mode 100644 index 38b0fb56dcc6d6901308d3c747126e36c7e37983..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 544076 zcmeF430zKF`}f!GZjuZclTs8>BuXl!K`9CmNup2`l`<7lw+3}pPnJvei7^vr#>~{n=p6lQ$pXnPGiE{CECR^3k+h{>1WGs>PPYhse+fv4M67Q{IES-+cpyM)%7&~&Av7@qNbnH2b zUydnb7vsrt8M~6n*i}2mZYD8yi?-j1CZp|l>DZD_jFnON%g&5d(mCGJIX(9C8Q1h79@~RLh8rwsn^#vm;AloS`e@%#Je7Jdkl# zDvTTK$T<71jB|`<+!$TPjick-DSUD!lF_~!;ux1q*GSz)M)BEO$+&&Fj7y_)q$Q9&X51k<{wUcAX~vzQYh*8FT&^SI zF70C6wIs$B)Bo?&{-vReD<>_d4lRla(_u)1^$zB7v-kiS?hs#H=crn6)+&8N!yi8C2(Z>GWotsI%4O&}Artzv?9LMBkY%mnRe zyDIJ1$%rg~3A&__J!FDzU70}V8WZSwFhOrOvMeU(!OxIyQ+ZOsIC=sb_;*q3zf%0MzY?js#1%4Xt{i>(4^zsJN))tIMUG5-NhT9tM!IY<6JMpv#1rXy zy8@W_LE=NwO#E~f6VDA~;y38HVqYfyB#DW?UdqJZsxa{nu}p$%!6c-Tm_!ps=E@}G zWtl{~XtH=F(PbBt=%KvAd}d0nMoYgWfCW| znM5A#ca`E*EXySBQ(PX!Gl{2ZOrqS2NmPq7NtVPUWu=*9i!>&w=*A?~oS9^oawgeL zpGo$5$RrIY+++`v?C;AYhbS`1;YXR|=s+epj@Z+KNqQ}1k~2b?B$YRk^Q@VqkdC2u zKys-UlU&i2NydF*k{j)q`m{+UIl&CYhbdBrlXR$@~l^dCQSW z-fhYxAMRn2p?&3&m{haQOscsxSuT@mq0gjR7BQ)Ii3Qd+cc z4>H|yCe`~BlQMB+QvK7Ilr0@IT$k)JlN#a0q#T|zsgZO(7rLfL9Fy{V%%uD*$d)oG zf4Y|c9wrq;@dyqe%Os<7%?~8o#iT+gULh2(5LYI(z=JHCNrlq6LVd|HnAAcevTbBH znN*lGnLZhvm&z=u#k4Klj!Z~K*9fP0L@1Kkk~uOddS|4T(tb;;nAEb)WVCHrC6kJz zYev#FBk7uvUQCMKC#i&7Cbh|wjIO)sF&XWbO7Ywy&7_VsC8P7^sFBfeITWWGl)f8w zOzI(>^PwA)s-W|{qvPJuai6y_X>n(=Dkj}jnn}0V#iW&wGHDe>Capf4Nq76iqAu8$ZOI(Tz>k@v8fc=^gjCei{9P(k zS4{K<+IyGfXlS1i5vQRh9g6sH^KC6xm@>vax4w6ll*!=SVy4;rHGWpHPsgFOJB$25 z9FnsbABWa)q!fojeWBOaXC-PMjzDdVRb+DbLBaT0O&NmZ!+&8^5^i&%ven`)pHm!o5 z_rhlsJ9x&68bxz^&v|H-Z&JP1k~zJg!{;cW^j)7OZw=7vd*UH`G)B zYnhm=25av>J{D|zZ&yCpWP(aM*zV?sgWwVBW_*0EHl9=s&W|oEM)-9lt##mA`=jQ7 zi)P$C2fp9XbSt@$q{#E&Lt$jY$$8{_aV8+%M6`nC_>se@&lV z3V1h_t{0xbJyyOrya71kQ|UqQ(tBCU!OO=l5rJcMhETf$m0`D3?ZN97-sUsA1kDsx z@TQyY90^Mf(tiWqG5FI6@a=U~%HTUYj^jF9>&~BVK;;kEt)$JIQuBtvU=^uCckpwY zs42ws%v(GZiq!fu_`qLYV^qJy^VcfU1pD#+@OSS!lzg_Kj?W7;{U=n>GgB9ST!LrV zrP=f(Ji~6{4Fq6q#ZoV@Zu3NSu)fui;ov?~hS`Hn4t2E!n`d_^1Y5NdTmujER+s`F z?0tZNH@BG49lYhk6`pq(=PHAD4-M&x@VzV344`RAd$X}U+etGA8odCY z9%abaAI~MeYy-~O^K2W!FMil)3BD>Fe+^vVFe?vy%YBA3_)d!Y2k^aP9T$U3C2n;D zKW_86Dfs!Ah@s$Dekn%aH;2dF0>8Vs5cOD5b+hIY;F?Z)<+z7po>@JFq!RPK)N2}? zoH+(8dwq>H36pDQTMBN`(--9#lb=~52W}hD{u5X^FO|MqsqyuGKFUp|-qF<)tYHv3 z9IO@6y8_%})#xH{uUk))!1^y6NrR1htQ`P0wLFe;npuR~%miDlpXy4&Y|1)(7LpGB z*#9xu-uPMTQ4%Dv#pMP^;2skhPG#~#f{Y{m@ z*Io^dL-@_OoLAr?vs)hEyU#|P0GGtN>4P5`--`l2lc`<`ei=Q5zdx_XggFzl4QyJh zG@t(^UHFm@)@%KJKzaBF`$3J|diY=6wh=MMk(?=&B|1$$^YySn7oILb*;38=?L%2p zvwfH*ju9Jnk=nedjHxcF1~=F;Y8bfDv>8Lda&61Ef?H&Xl)>^ob7z4STZg{_D+&8b zf>kHCI05c>No5*X|H)xGmKxH2m3#o%%^K^bif*LP~N2pLeh{DfhRaj#YqPoF>38G@Um&8 z0`T%(9hZS)&rHn+$JOi^1CBS6QU@mt?b8jsIdb`XaPsCepTRrsE1mjPo0h2YXP!IU>q8FMvr1o%bM)_nRaI@)vuSB_8QpKF!>^|#>a z16}#&DthTXfRGbN9mI3xq)e;YfMuM_UBHc3gggPunI>-rw~|pX2FtJLIUTIzyrmIX zx%5~+uv%*OJ79I^;YdH%wd1)SV6Bw9NI$0&FpLB1ag%$44ReaFfQ{|nN`cLlCMAL` zwg%q-519Je4{Y;6CK+sZYMvR`erh7p&p8^jSP6DI)fMUIT$XHf2aj)Ya2^=PIMq;5{y> z@GoxP<8v#(2lpAmzqrGFhD`vU5YK{taVMAE(E*U<}TxVF>Nlzi*?z^4gc!YT|i@t-<>zkYv| zxr|BGIltjuTT@pm`YtsRf0RjjyX#s0yRgw>Z)IYU-l7w^D*W{=QJ-W|EG$2Pg%3C7 z%WvrFCinRN=Wpb1cwfHwr(_-f?9^z_9uM*(qc-p1 zA2n{_@{hrhott=N)_HD{Om=DrGde^dVce>opE|N3{3KM51B6NeGk@?(UwS?x%E zC(>{4y13}<%LLJRIfQ-r`_hh7_gW@-a@WfFV**cO9|7~XY0CS>ci;H$ruVnT;miK@ zZC~|mU-fNY^=)62Mb)=`{n2e7_2;L$D9RnwKmLvSrMBQwr;af}yLSUus^_*J?$Ltk zIie2x#-Scg^<=A~;K657_UDe2&H0J6I|C zfwe+>?vXHUXO0t+_PVVi3)U~5AOSYMGRqlkqENusk3Gh?`hsUh%`_8IUZ7y+K?f(v$U*Jm4-J6YAIRm;H=^AD{(2*>E*^vJj?L}XLq|)GLN?r&jBEy*;7_bfxxUcjzkV{CUuL7sA1W zLCR^v-~2cWBAR(`%p)0~+sz zKD}sG3VjwLoDHqGxqmnG^`4qDq|8P1&@ci2`TzRPsz3hs-M=s2MZtCQ9|?*5(v+{% zgw8yCjPjM5MUkt~uU)f8z55{W{x}*EOM1L81NlzPDdV>2f37(zqsNDzkJ_pN&YLeD z4ZR{sLy-T#{KBgD(8oc`P)1v-er(GTZ%H(CD{S ztDvzPjccHBUQth>@hz_pgKj*ba2UGT!?go6S#C=NbjRVRuc0Y!+D_1Yjb@&L9!Ni; z0X^i(_;18x4Jp_qeACJ=MgZOkkxqO9>$MYwp1F?UFsxu#tN+VwdaMjF5d_1ZnKl14k zJr6=WII;T+kuFZkqygf=$>=sky12&6V-XKdE^Hmr#kG8>jCgSJpL?c2mGpKX9-PXM zV?j{0a9zZMQ{QAg7^*4CMLamI%KUavona#p4^Gc&%uA?Y(tX5(Gd}j#9BTI7i;st8 zQ(-gkfC2CMc-T&mc>uPH58>lse_-W4uw(N!d^|==tAv1E+>`itjCa~O3_LNTJ0B0v zE9P6lJ~D^-c=)O2%>vIHW5LHGV3uQlaA1BBACKVdl2+i*PLud}ENbgr23`_S!N(&q zM#2yrb0LtA$I6G%GT^oCr1*HO@2zkfoah_F$0I3x%MS3iM_u@M>@3or1Kwkh#>Zp7 z){%kWgG){LcpTm`Yy<^~>$ve{{RzAASBmt{rlCoig`*;PYpA5?Y#VkjMPi zx_W)xyuNP!ht$o7i!NGnwkCKq;56=gF>Bo>_!`w4}vh4W3Fq(Bmmkkpdp|p}>DdWCc|ZTGHZnZJ zKf0X#DL-k*_uJO{$rnFynbGGa`OUZYt|RkT`|^JMS$^^3>wbUV>(&o{IluZN>Q{f4 zbJE*Se|7KGFW062h;)5<@9M_km!H>f-T$u>=RdlP`{U04N9Vm?{IKpghlm-OX0Eik z@kKx5J$#wNH?c)I;()-)>Yp zj!i;T?3BSr{Y!klq~goJzdr3>@{JC4o?~>p`HTdrPtNJ6xfS)xxktvBpnkddL}es6 zVruCd@M_bjJn$N2Ya4K?=AkwePv)p;Z-rQls+L>^9<#j-x(AxT52c~WH zLx07+!oB_@weGp`|62aZF@d6X!?*6lz3;G3BN?i;hxLJKhqhe;)miU;MM$bYX`UXm zk8`LDug5!4Jmy^0Sw^P_hl`1GrteZkqb zRkq*@6U}45mo_d?1Yga_8xJmc-RL>^c0EKN>{5c1wY>7jkad? zqS4`dB&=MlYLk$(($$iR6`Bh+JL(;{df#OTtI1V3OaI{n5~ESXaFXq|tOCo7EK>nD z^3$FOmfPW54sLP!h#t62dua=>qT#Rzu*&om{5z()^o|o){jAbMglpXO&;Zx=P512a z`Xg9xicToP4MOMTfQ|3x+ytB6kX{S6)E&XU!`6c~(tAmL&0+Tf5I!V9<2cy<))Z^7 z!|MZ);L)1z$6>yB0y@aojRN_Xn}cnxMZ|%J%v;k8Y_HzZ1MF~F^#Ryvc7i^5Z2u#A z;BmQ{(qQ*B{UyPk=1Y8_UeDG%5|a9^Y{jQ*MqjmqVE;#ne7fdFr!E2qU6dR~LUVZx zM}ostmUjS0jJmQDyli^g*Wl&5oacgL&ul#fj;ncO1CBT9+X|d8bXr^R=E&?aaPsC) z&%itG+oyw56=F@m`*e!K!0Fz~O~8l3JRQKtPHevm&iu@FfX_73O$BEUSuzKl>vkd- zd}&jwf#Cdu1BZYMDiitVTG;LWW^l2UCjVR?2)(9*OV^|yCZV}J@B4vYG#$*RzoKJE zb8zMOoBVUF3Xm-US09+bKUdMqq~63dm**v(D<@^vT?#DYGI7VNI%Wxu~`6iIyC|5r@1@@ z_Tcd?UPgc?7LQW}drq3|1oqK>_6F>GSX=`}`krmuyr$2#)OB=Os9%A_e|Mb9v4l2d}j-7zvKg8yX2tT$QB`PU`mDNc+&f-_~)$|NnoE#TrTU z?Nq0%bPK;;NZ)ZaWz`ug==wEJ*IW2OUpnaU+bb&Hu1CDQygjDSexj*e&f7qzpJ}ri zI!jl)Bf{q@=9~u4i!R&^4w=1)-!Ihq(E)J2-|=$@zpnV64==pCXf*gv6ZJ%d-~ZfV z9k?XdNdo-%aDNf_nOegh;BvVtP4H{sgG0D4Ge+A_L0bH!Wd?xfhF?KFna#_Y&;T5A zKZO782(6LAIA#{nltVq9E$zDSGq`qbt`+CHTn8VUd251@^u%p{18`Q-NYrE5xuK1h zfiHZLE(BkEw$B}$?>##oT%e%Mpa0uhUD4SR2!GHlYC6Inp6EXm{3N{7Iq-A2EA7D* zRyXjSz}~oxt{~wI=lAPJ%o&y5Zv(Df%d3}YNZ?PeclG1-ul_Flb^R_%Wc}K7^=s38 z&)eV^b@RHv0{_-`>Z@=6)Y^3Q>oNWf)?+MfM%mJ@U5}A{D(=MR;h#-=@ogPHUZ1fx z5C7+^$B6gy$L~a~&)2@Ywc%g4*XQ$mz2>jG25G&||KGJlYke46@ALn>b$5T0&;JY8 z-Tft>ulsJ)=KZ9U&l}bGuB=~c`cGbK`t!uI^hnsciyG(;VjD|xWx&68t?AmjjM3kI zZRy&2%CGV9?{yfHdf)!*t)o77*=+c9{B?=R(BHV8`me6PoBZv49)4U~m#O`KUH$6k z-@dE712)%=CpY7Wm(fWNPsn>9&Dn$WBx-=4hb=DJtl)7@~*xP{}=S_|KB?NO%dO(`nht(9D2n6r>&;e z=j-e6_5P3g&Y#{6Kk?!R@>3S~qdNTh5m^(l{o6Q#FTeT4KK+b;w_e-q3hmCxFP|e4 zk}AIH^%Si9P~s9;%`EdU_@CqdKd#IFTwUg8_M@9&THl8A=llQ9{ubVEs@r_8&ntEr z4?q9$H|)>-HlFD}sx7Csj#H~Y_fGsMoNicsd(K~=Ecn@X;Wzy*B>R+qiysU3?-_?s zTmP@CKj-_{PGti9{l4**bUYpFS*PrHq_o-)+gj+@MvyRNU0Hr?p@yMrV{kX?S0{v| zJy!R|93rNhkbpTg^xKAyPl5YX4a1xtX4-!Z=Jc?BueR~+cvc&1_;x)T+hD#OZ*X;o zO$Z;B)B|&Ln8UFAi{Me!53Imrc7%QekF#IFk2Q1`)9VEG*fY6WNb2R%C?+ShPiE2MH&( z<~HKXNxok>2i)MfJL1bV9;kx&a&mpk@%*`#nFkTyZ*?-I=H@v6_d1z-9^%WXKfQ$e z$!YqU;Qn*njhzJGo~Ozdr?RT|6rXY-{4x z2W;1aTMM>N7x3{NxnSvJ@M!5sKE7iMS{s7Lk8Z=qccN`dB-nHJX53%Sd!xZvupiU9 z1fEfmr3DU{br$2CxIl}+HQ?a$c5lE7R^7S?UevbmG&tgk8=wA2pNT8LF)r13-ds#% zI}>niUDQ+5mFAoOxSm$ee~qz_U-QwL7noQ0gY_{C_idPF%0I`?)_hn^p?fCwgVoO+ zuAe(xKX;hVXY1z<*UugP(|(We?>?9Je;NO;WAf|v3%G^vb9b$u55?kY`<{$XcA;v( z_pvozCNsgwn=JV{_V%^MPJ&Y|pGF;!?H%Q#1Ws2Sj5;98*je=g{AbUv(vjKuisHld zIygR$qVugTY9gBUI)R^GSKCJNZ&9k})uemk+0^y_jl<@?rFQa~bN%IZL+=HjoWw{= z3T}J`m%U2k`RRs{ZHPs>OLFew9GRa+7Yj+x$VFkT9+o}0ECrnFrjQ1{l;{@(&QITh zwR%{=n@@wmh24y>Ru9e9Uepf!Aov{C>S1Mr>_Wg#%vC(VFPdyO2Un=w!CF1EzKCu! zaFxFY*6Lx^X~!RcMK45NB(%QBfPKXDTfq@VV3|?Za6VdJq@^raZkNk2aLdw-Mc_6a z-faad>1*r)tIV7~3#=A#AP}scE!!X5m1}t)+Fil5Gq&s4C+r35c|ENH8*bH{4>m3d zu?3q|r4I#L>NQdX53n(754K&r`Z?Hc{e=p!ePM^AV8<2{utpX)y4&uhV3&z6TY|?6 z^_{^J4+WHhJ>Om01NM;>902h1GZW- zdKcIx<#QBxaM5%RV#<%c%=eWOb=)5Cli$WB$4q~6``7lZAIuT{(q^SxCtYsg1*Ix@ zfB)I@e81)E%A?)@qG`uBWwLY@0Ra!1HW@|T)LVO$CMPfbMQbNe$%BX2I<4_>~| zMGCx1v-JXS+>IwU!0YF2vIi$<%t!_&RT^LoznbJgkw17>|15uS%B2V^@cuPJtib90 z+phy3es;Sx_*mSoDd1Cm-5!Cnp7rhnJ|F$+AUN0fR73C;sYMOJ`O&t8;G0fzh2Yx{ zZtMi#-4&0u1Zy6Q@)3c{MCx6O zZC-0aEE4y2F(DR7zU$Zo-0;NHCg8?iM<;-rH=A)6-13B#4p_lWlnPdoyD9*;FI+7E ztGPMngS%+8&j)L!KiUk|7Vg>%*69}X9^C8pV*Y&|GE#$Izsz~XM=6B6nC6Z~_=K|1 z8{mm6Mrwkm7&O8C7I~N6uLe&`-C7Nvsqb|GJV&g@1#n=*`*q;?!;h{5hjNjxz>AZu zrhu0WZ*u}1_2DXi{g|yuQQ+0KV;UlS&4=zT;0>oLUBHPphr5HfwhZnL-nPL)1m5k@ zKm^{?qR1Ycb~yGD_~4{Ts^FuIlsAHpAF6l*KD{9Q4fw2V;AwD9f!S$ro{Q89@YT+_ zE5O(G#!LX;nmcj=xTtfNgW!944|KpKbJ7-pAFFut_xov{$w=_4)#4)&{#rRlJ(Kb? z#oL`13^y&J!F3^__lXI}W;%(O@#I&ZwMkiv?R1Ibb zoqonH3_44HSfV8_YYkKht~An2>#aJ5M6`za9Z=>?06FPKyMxP|5m1}irQc2>l)w6=pUEa z|0||v^|f8M?i#{V+B7;w@nuO$bNDqJx1Z4OKuGcb(#|VUQ+|qX<3;_Ww!Ig1McQdq zo#(#b(2~7$KWi4f&NZa_TeC#A)qHT27aL0>tw}Lf}e9QuCx%(@Cd-jaszb$%Y za8JPo3l$%eFk`JXeZi(Tk~6>-^VJ%Itu=Zs2HWJP6oZEZ9&-fScQW9=g&ZzfcLqBJ zWCaOH$6DSE1dqEgg#Q+DkDH8d2Igs2wn<3p_4KVG*f-X18hD0rFuoa>f7vH<@Z4yb zqu`*taeVqi9<0PS0}E4r?jR(M7}fP230pSJzmt%3`Obs*W}vkuxp&~W8vZmazRw!| zek9oC;hTZhnpES{pS;;o1-#>43ZMSeHm_!Y_vsiM7LulW&9MLpVpcj1^?o-Qv8s9PRIWt{EO3LpPaykIX>_&&e(pD2H0Hj1N@8Dnw0wp9xydD z7Hm@$p98i#)x-^KKUKFK*inBg{EKrsc^v7dwI+MOzi6$=0ms1;?;M4Hah?;eMS^{_ zt>9mr?;)45;F%#;s=xu=-|^qVfge2KUtDm={1AkP+El{7XstI!AborT7kFAro+FuohvVvf%mwWkzlRK zQR(1=dvoDm++o9qVc-+uBUQjBmrZp5pLMuj4n8j~xdWV+Hyi%NU3OSL2Yg*c9R5XX zO)3rq7fp+N2rjNpRsffrX_E#n8{Fe5_*v_uQ1FX{qldw-CwCuC%vRN4JuH0l{#fz7 z`B(ZDu8+^(>Up4iC;zG+TlJ3_TlK5Q75+Qs4gI)n9Bmz6>Ve^P>VeVm)A-*y|3N*E z-i!ZKFH_DOhkX89y-dB?bL8{&^|HTAz06LV9tXA2M<2p-d9b;=E403TR$o6OKlrcJ z&%(*4{#NBa-+uLb>y4=6p)ICPdrW;D^gs7UeeJs$Q>WZ<3x6?Mg_;Te>FrJtP4(O1 z6)e#<@?~{ZaJBQ*BMu}7Lq1tew|z4HERMmc~js-61IJ9Whyx3 z^2dGPy`#1?1gEPm3;}2C>Rt#wHmm6{@QK?g>fmqVa)(;Z1z-4Nl0))+T(0+?Q{V!H zNx#@m_VC0!6Cv&QBz#Od_J7_?x(T?#x^Xk`8@HId;HqK4x4|C|D)I3Zz4x6=$cc43 zAP|y%8<#6I$p<%1w=6(-(|0!zU#?~RP6e=h#}U)P-^S(o-|Gif`#LVS(@X!=V9gB2 z#@JuWOl%TZN9I8>xYxloN5F>j=kx>{mB~wi&8|s^fi3hm-2e~h6m=DBlWM35wwtZ6 z32e`0Rf9)f*fI_5GtUE#$lFH^n93O;}~D4%Ze&lA!+6(HMC#Q7zoF=;OxPvnItTCLdh8+ z>7_(LBJ`?6qCW|Zh45JkE_idsm4wDZNIVge7We;QJ*#X`8?+O#CuUD(Bm6~^X?#0I zh1%9p;L35I)ktV8M2{}u>a<0bV9|^0dBijpqFELpjfJou1eO`K9_Qm4#}q4p<#u&; z1Gg;ocmZzH;RN6Aq}2Nj+J$K>#6Z5?Ni8C18Cd$8cIeu&^Uv0=aiT;>*6|aE-9GZ*r(%0%(ngs6O@F_}n zptbL{q)4VBK_q=KlYh5sm3*(hJ>gs5&YwPS_h;v7{myxd^=rm{U5^K4NNu|U{!hkO z-MLyd!`kO)p#SI9wD5NnMDI-$%0=48bQGT#@cn9Z+}FOXy1#4ri*pvfj(>Yr=bc?} zND052PJLKo$IvxvmNeXk-%YQHvUt)ByyEFG74WJfLcVWv&7R{H;0=u%4n=t4!A0A^ zNxMca18)5-R8$i0zBX|-_TM}2!DaA)P8Iz9 z%DC*E06w~Y@p^=3T8USJPnS3M2A^9Kl?A?FzJb4A7oRJ{g0HUZ!r!lgzFXYDw;t`k zgZ=L;*VYB!A8XSC{IKMRIQa3N0uKD#X_zCpT;R&z-#5GNbOyg4@r=K}A4QX@z%|=u z`QaTC8~Qq%g!)=JzB8vm(!4luBfmJl?jqN=1^SGruT|AuNGk8W5q(C~*P7A`tQ0&R zeNNP-XMnPg`dTeAz#2zRUk7Wsn|1~F>^YjRyYxDk^9gJaTKYjqYOFQh4s3eE_dM7_ z_(Fw*S$C;kBP6xS_vh5jsZ+qRXz*CeZK%6YU#q+;33HFr z86zb1G)qkbdp$kg4eT3h7z&}gcoH0&bwL^&SJQAgIKIz#^qH{)yH(@Bo0q-d)1SP#eGl-Cd;Wa- zQ`=-L1n<)k%O#<{)_x#}WNC1(czqP|x70I*uh5;?H?=v1Vi`dT}{zo@U(5b3AB*4^+g z>T5mY1vbplhksFDtDOT6w0NZ(`;AK_or*V^ndc;cOf@Gt6XU7rB<(QXR=qQ2J7zTlZ5>l%Oqx}V_d+JPT> z!oR4mbs%5Y4z)Q3|Kh@~UFYlCOEy@+zo@U(RhN;*lwXB^aVuv(69=#DHwOMieXTRE zffH9g_68^QYt$LMT{am0MSZQStH66)WZ+*^dUVJJAKbeV{zZMQyW_wo#8u#5)Yq!- z0Y2-nlLMa@TsjKQ%QJ_6QD3XmQt)-%@2tOsYnWrIF` z0zYf*y%PK)VPP)#wP%$(F?;qu8OvMu`{2+17O21em-fv1`|$U>5A@9FuWtYKZ<-H6 z$NuN?=$|qV#Fj4?x+|=1C8YdL$NokFC7;vtZeIpA+&Z2w7mP~|8G_BKUYH`>QqMvL zJisQPDcE-L#oJ)J4Py7e_JyN%fE`<`<;#W9-5$;XyG-iHmkZ;CQwM@49!llQ1<&`A z8^AuYhDnUnZ@_XN@XWDiW`G0Y75jn%uRHVQLh#Gv#^6x(M|`;BaCuWhgm>y*E7=BpJ^S?Oog zcq0k3)D3$qBpn#JW-8crkz59N?RE>kA8md497S+q^(8A3wz++WYsBB?8a1rR?XLf; zzC)=#@bw)Gs}moq;j7$fPH4@n2X`}tq+z1#SHMf39Ce_!vYPA)!7Xr}!8vpA|N8Qlm=exY0ZSMsB=%Z?a{Y3f#zCWGIOyT>}CHD36!F`dw zpp5=$)=2zfUvN{U9m(JpbKPUWt$lm>gWE2=;RJ4XD$@$Asxr4HxKm4;)?kh4l49U) zOCIp;@IB5%Uj^$Hd-oBN8pyZZ1UAyHY6v!s+=q61)^9;bA=v7^@mcV|f<`;RLv*rN zfrr^9iNFqF_WbySQ5(Bo0*|>_tVhCJD-Lf2yK8z%f;}uOr-HpUF+RRiS6wy%Pp@2} zLBeJ|8O^7Eu2svA;Gl9f^#8MvH3>Q3h5P2|fWx0_t_3fXkj8j|Z{sOqN5b*N&&i zoP4i0I4gTy2NIUuC3*!ow{6GQ;7ht*v=w zs~u1AIbjJM|84DA73pU7;M%ok)z6+v1#8CAJS@`gc}tsvdpr#~4%Yi5;{rBV*a6R< z>pOU5cd%*op{HQWL}d}!dawo3&kf%PRu>m|`q%YFXxpy7t z1NMA-ObYDXL~1bD&)Vt+c*eMxcHn@uSCheW(-dEVgI|pC2QSb_GY2m+d29ua7}YZp z9GM=B`^hc8bp8Q&<=&tn;M)GEu$s>u0{C%UqE{rFn$eH|k$Tc=D}aA^1`%B!iLaV6g$x1{0S7;u!OiX!sz+OgB7Z%r1u z5}W6=QNKspKVZ;j@DJw9!3S-ppuLvK{-*D8svEz52ZKe^7QHKmL-R{I0p=$cX4qtxjI#@2%7s+iWK8juVm&ekOAYIxN;s z1L|PBG6p)T?Dj|Km}r&p&~XOtIZ(F;J9|PsmXxoBdW|v{Ev>>Lk?(8dK8uPJ#J^0S@NgcrV#~x}dq*E)lR%`~f$uG?#p?TGn{lWH~eAj~= zE(Ny&I|WpGgU9x7H5okaLYOhwJucA@>}jsF9PIV1s|?sTcH21c4CD0MVE;!tJ-~CL z&31r;^5SNKLmuSL0f(umT7n}+O}GeNHZ7$kc=^tk8^Ez=^h?2UHM4!d@qI2EfD?v_ z_XTf`9Nhq%yg7K~9W}3fK!~1OM8tXgY zCs)HK3Q1oyxyPr!qGJm^aOLsKg^7eTuJj~)mXmR6 z=m&1R!sa7b&eZn+xK)Fa!C?8A>V;q>XP7Q~ig5?Wayb z`l+w~&Q`F~$+t*9&8zm91s>l*xEnn2&YS*V&q)pEfqk@t;9u0&ziJS;wy(do&Na4% z^SxIixBkm~;-6BqLDp`^|9?F1xSqfCC;i&{fP47W=z$p4$#X)vq(hX4)UH86c}p~h zJY~>@z!c;uqedPm1v~r33AfZsce3PSit(e>-#nr#p9&eK{Qu3JcG z{Mb~y^R!mSGd@pkG)T>DNL{^Q?~ANuh6IzId#z71<)#!enj z_nO9_9`vPqU9)D-+n(UHS30=i-q(IR)~>Jey*v$3LHNUxir2J1JyY3o@DIKviT~*J zSCoR+7{V9o*Iz*~hk8Q&`YUuJ>epZSm#n|C{x`KzeI27wJ65A!{{drfQ1<<=pX-;t z_+@p<*$$I0>I6`^=@Yw&FBf$hKkJ2ZaZt{U5|oRs+t)n-PdF7|4}M|hwdo#dxu6Rl z{$|%!1BAaHk+vWCxV*PV9_3>+_T8WFhg1sI90OMM6l+bw)SCr)gEfvUeFWBWZ(t1W z*;B0#xYxnhMqq=`)P`VVEzNOY(;EhbU<+Y-53qHYtes$+d@~L_Byh-Fuzkl1SHKPz zZ`**K0vr;-V=etQg2(0D_XE4H{xlWrX%=t>?Dce!3D`GQuo67OxLFghe_5nEcy3hu zU2stDo9*C`2kNQdFy)}x;0VVP0`RhF4F-Uh?;Ma1j?Ic~1CINAZ8JE&k0zh~1Ur}M z;LXd@`Sd4mdfE@X7MOZR!+o<+!uGgrrroKK21urwx?=i(V{`BH;v*h1ZGcdsxv0EHi527I5R3 zWKBK{WScG8{fR}K6v7t!E?c$6DRHk`*gnr|KfZzD&~V{hPbr_2XtS|r$4Z& zdMY?Lq&c7dP@6FL7ma=2FcbSP*&x>h9I4*L5*$;$4*o@B-_u%x*Y@j_3Xae1TMABG zxi=V`WL{tZ-Y#nm|Dv();~IeXjLnCCar+)Uy9GYDcP#vi#=g(m4n84x0{^11@7!$g zS%(?T!RG~WdEmUfhLgaT9aPtYuPd+W4Zg80#T#5St-TbuxZ1!NT#~i(2DofcRwMAU z)_Td{7YT#!fM0u_^e1M^YA)SdTs!Un?V`W7|F3?(U)N9iPrXm%JHM;_{A(L*-8DJ2 zwmP<@vWwbay>qCn`rhAYU$qvzRBu!u!ed^>PXpI}Q?4~rHwUkG66`{F;=$vqz)8Cz z`Te)c444SsrP`67&-UYQ&QT+S-cdNUvy67dyHLOOQ~jEpLh^%O{re2{YjW~!*8iS0 zIbCOeX#@UWe;3qW@1K3&zkK)oaDDQQm?K?N_Nf!!ep);3@})y!2FkRGw-e@2IIT}! zV2v_tsz&e5(CKFu&4A9*ohF9xxk~y8;CagjM1Vu)H0AdT9WWvWoIkB=62h-5x%1(L zci-xR?=)E%gz)>HLju4hx#u2$A0JLD0zXrm-56ZnY@#&y^?bWkC_`tAK530S)L%NF z6L@ZfNi29?&gC+2$o=w6aA?iUNN~!P9tq&RqY6Kga0(d}cEp^ysRqKot+%2oVrqYh zx+&^&s4vr>RGshi)gPl@U|X>BOa418Wa#ldLgKL0+3T=9JZXTC_}h42shUG0zs&9V zdcNB8@^x|QW1_!xbe*4Hep6);W_EFUcaEV#E5M1^&>jSY!cd7nS zV$tA(Z$^P#&GY#+2i!ksoCi-0Xv@#B|GwtHFZUTOJvLGU$9?M~YI6T0_95q3NEfL#`{X^o7-&ymksv7`%3K&h10+<65ebFzn(nNoS3bvu@AV%x3T}) z{`C6&zSM#0@5Ar959D)Eb;{HFF&KZ(F&Hhfmh<_CyiX(KPv6GfDQCtZf1-Yjtq+8x z>djS=KQWD?J@$d?>l5|k`p6Cb=i~Zn#|lOL6iGfqv*O?E~ZMlwbd0 z4M35W+t@5d-&23LHS4xiY+r-#z}hwGK8kuY0M~3YSwQbEt@U;U{bx+#V-DXRS>J#8 z@9w`GU)y*2zt;M`(jWB|e_0c?OPxH5ey4s!CfcO_*}vz@BARmDktZ$fx{z;!T0VY> z2)yE47kq=W+v}>7!FQsw@r_Qu!5B3i{J_a?8Td(fr*q)va#z}ue4DG=MKoQ^l%Grc zC(WDrKWicUI3Acg`eiQF(mKz*N$Q7&D)ih#SvGzLwq{X9xe$4{PUAEyVj8=YPzhc* zSv?B8c!8h_>6cST#+VD%;`D*VV0rHz7;`~ml}sJM$|sIu%mq{RxX>A_-pmSPE@-UM z2>y62x2r?IJ$t;c6_WNkI7S(45E{U*yB9R%zK$uuZ-U z#$3=?CHbXb`;M_CV26vFoxx54%KO1%Ee-j$-ElcP_%g|T^%=D7(pV)kjJcq(N_J?& zX1*(OG3J8JFe+35`s+WUV7rr(G3h>O3Ze76v-G}n&53I_9e{sR{i}>`1+6;kz(O9KPi?IKa^|#?)Tx6%$ zBf&A{ZtyP}t29p?yw?0xCOAG<>ODAd<(z16lKDm(@OD|bhv1znHQIvrjE#qX(O9Ll zXW)Z-)kDFD4NZ=KPYCwGzqpf2bC!V5IvA^g&kM%g1?S~n3jkjp@jMlLUD?GNd}Et` zFu2I?sXVy2T5JfoBx}ZFaM_?`%HU_ML>b^039Vm&UwcL@CT2@TYBTER$Wne=zrMnk za_2wZ2P&`VZ)Tmc$=`dGDYpIF=jvt_tVX{CJJYnuSZH>a4%C6jSnl|>TF^^#cZWgq z52)2Z3ohzAKnpdG4uck}obCjD5MS~`8uJLr?kS6)M(KPg4O1glVWJ_W5b@>PIV z&3qICt&aTo6e@Zigm`db_ZJ?9N|`i3JUAKMrV-G_%VQA_PA+Vn6SU<+WyFJ%|J*YL zs-(9A@!(X391DV~h3g_7ocbo~!B9<6F5f?*Rhi!otTSvR;=$=zjd=+*OuCPFaK^{p zqFs?Qd+)`^!?LNc8F;{e_k28TC&-{(k+X{r;p1U{VC6orWAiqAJVr~agn(V#llXXy zciK4&JTap?9}mwf=3Bu&GKcwi_^Ia20?!;{!N((DmScZ#V15xFkKpW*R^ZT1llXWn zYU^DFj*O8ogvMM5Y+E&f~!wmwfoi zR5o)SPj%YFz(s-EnDZz(6C?z`s_Mp^$Ln@Vcj2mWAvh2EuIg<2CgzO)X5IJey6}I= zY2u&vv-n@d&(8=M&91{9tQf;*%(FqxJu`X+U>#Vnip*AKX{-Oe?s*>ZWpdps&t-SjAJ~JUp~8>nJ=m z-O~j&oHC^ho~12)2cC2I!8v%|?(ksPtkQfdytqWw7G9=XF&ADj;(|78ebyruwu_zV z4?A3a?+CBUyITOecH1x!-k>u7B<#7oeK&Zs`^ydRw)1;lz&<%^X25=8y%OQQirilC zLC>Ui@ZrP$_Hf8U-CQ`VQf@pPsdO_6j@FIp0-to5;|!nPGprasbL+W2d_I7u2d7MM z4ur3~@$L>^-`~m_&d}*x3g0Z-ITpU_7nlU!A0sUWKYF3G1@|sa>xP>h9F8=YhH= zwd?J+;81KEDu%iy?ctz=y5JA^Y&iXf8Kv93U}M%Zie5b*?}NQ(KUYKCZfm&=ac5*$ z}Fa?)88x`($i^YZmpM3D-Ip&w=YtMa#py@``9OJsS?M zAya+9$pscMxqKBC-PK|$EIzhh8@Owm1HrK5&bWuL%v=RqSoXQ*F<3tOgf^^bdJpNR zXTyp9@Sy00NWb9O@IUfwn86!UCC9UW7R1D{7qoZ|bY1)&UTyzVs)qX;vRIwx7#VVF~7M3g&PAJ@YpO^D_V1yv#M+lIyqeJr(pbe$~%N_mQvj z<-d?ebZz}p^CiFZul=dt{W4FX`MTwsIC(3mUdxg`8iBq7R!`?H(j1EBvl52G7WXZx4;P0>^PM0mG&^Yg-CHI+^R`buB|Nr!&=KI(hUh)Vxp~uNp;#=(K5UW3H&=NC> zN34WjQ=s>|W=fHMN11husf(zxgBtJasGHD#^zV7Ia}zt!%8H&*x%Q&1dLlZV*Qlmv z*RT6Exx0sj+{JN!hoOCN{D8}|S8zaOWi}j~5cL`k)eGUl5d(E~;HZFsx8M_QHznXx zL!$KI#JeV#Yr{R8Ih}dGJsH2wb__V=md+^R!71TRi!vzUN?Q$pJ1u+YtO;5 zgGS+edA;`Xw!(co2OWm{i|%;}4_vCe8dh1<3+MZbeq%%7>2q;>mW(pfmN3Wou`)c* zI>8UGH;b^ShZkSgwt$z`zsZ1Cbj}O35&^or`cXp_0N4L<3zwi-UYXU07E z`u<)o;0zrVGx%oN@kIEp-#Hcd{+QvL;72beu;4u3%d_Ev`FS&8L0gNUA0F2#K|j2p zAD%<^w*PfM{BvHnZi44|L3@T^9Ktu!?(?Ny9nh@qnzZQpRjM;^Z-zLdj>EmvbsOq% zyuM*=15k(Px6a-i9{9m;B|P|T`viE{YULDoxXxZLSl=>}sgq3A82%nMJTsfAlg!+9 z`5K%*z3XYb{@Fz~^ZvzqXJ*1LWqEyZyzJ$oQ20$Ni{)^&g)CEtd2jMY1Fqv<8Gz&5 z@H;%(%ZmV%a1b8 z5xU`@M#B^N`vKK$ZV&VK12S6Wz??6CKOoZ-HgmrG{ea9Rwguqz{QZEeEW53Q`TGIc zB!)2W&)*Nou|tjxjyLWHWM-xXH|_^?{{z!s*0>)~e|HNU-#I1$&rLK&urKp|`{Jiw zgab4#FzF9!BetGHKC*ohlm2ki15@D0r`b&UW17`7P<<%eI7U!3 ziy7RR)B{v%bxS&1U<#|2+4qGz*9n!~^8SaVmp?;n<#}VbdJDxyP zoZF6tIDmCb7;(^@keP^T2lbyIs;`_d0I_KdV3ZBqGzL%|>1#Fy@DlTPo5lc|Sm1c$ z7{HVwq_5c+z}3QV;}}58+Iw)*7{HC8aMKt-sam*c3}Dy|xM>Wa%09Sh4B)wCaMKvT z2_xXfF@PC&P`;au0bDi@ZX5&XSIwlqX$+uC1CBS10c?{AH;n<@9RN3t0aSVmH;n;| zPl21p0BZTdjbi}oGR)wnF@VM@aN`)jzyjv;;*S9g;NBhci}oLWn}8tF^ZWm&eN2!q ze?DKntXC%1?B2>>i{ttP@o2ht(Hb0=meRe9TXg@yC%ELzX~wS(&+d+U+t97)Pg(RY zrC~0{MzK=GeV)TNrj^9NnHEx~;akB!+jqKgza%lPRZz;GT8~yRcMSP2m^&s|`;xw1 zzgYWHFn8=5xmSGYr`X@@8v9%GTh+vR%hNTN_RJ8ygYWp9*^}D9^K9g8VY7(458=g^ zW3}O>4fBt|E4paV*hd<-XIIkzH?4oo{a3=9H`?aF z+b+nBhJA7x!eBqKsP^#QC)e7-2fcL6;K0EKkKmApSt{_?^IN0G4Yk7YlP-m~aXh|n zr5=3d_NpNG{GrW<;FO8NHSm?UomRou_xs@aj`qmt-vKT+H8g?WwlHJzqxyJWKlsC} zvS1vq<1MX)xyKxAIrL=^no)Iw0_}Oh)`r`Ly6l5H*zWU(#d~xvhdW>GV+l*H3Oo-> zca0ea%ea|Of%{mEYzZse9Crp*I(H=p9%QLC09I9<+aDg5nBD-7^eFxaYYs9ufVCdj zq`~7j6>=14E>``14n_TJ$G7nG^#Q>X2|@PhHjw!@1uB+V$$ zK1yoyITWq5W0`yE%2yW$!Zv;y9u#QZ=t-Yo$CA{Uu(MBg7VMg(#H8Q7aKRziOE!KP z1)7WXt`gpEr!@lJ8O6B-`(4dw1MlOu+X)AZGM@(r8SGg^Znmy+z6>m`CG?OE(_AHc z_CzPOyB;R&iKyZfqtzK%qDCpVn2@2!PcbqmlnKS1ENRtdTwf z`A_RA8;pi^q%R}?X|7UkdsxqUY8b3vYxx8=xb)B-Hd_5j1vVLBjr^y%N}GvzPN_rw z(>_BIx8Wsup2&Zit8^#`Uais*`A_={^|OKb`wTe^PQD7S|EP-mr}d2{`@tUi7m)un zS1HE=-gG8+o-dV3f7Pji*Zgy93a^O65FSIPb!9PIyc6&$MDp&uO4-VOOr zbCvei!Y3ArApdE7quw{*#FPF^{-4!|-UlZO%MHQtOWQ{*gRdD!vEj59x6Z>^Svs@e z+s3Bb;2ha31^B_S;(72ByV>vH{Cb-KaM9JmRJdenT?1U!ZIwS<5#*T(S6S6sl3DwI zSDz^LhYDWzxnKAX=L7Oikbfv^Oa8)r(;@Ze_q|`eul3txkNXJ!zIHgUXBzx_?`x;` zB#*@F`S-PRrIVTaB>%ql8|ysK{b@o3(%QjxQ8)!^2h78DgyssDJHo%duL1x5)B)Ey z{=NpyuFryX?pV_xFBpI}CFTqwCo3ef{_TI^+ix$mU~H|LC)T zpPJq&)OGj%!`G~@>*5(Yhc$7HyIC&fhriFSo8KQ3W&uBXaVihT^LDBXgAb^qQ6jw{8q2GZ-28b&EAY?f=zMvmv-k_&WkcZ$Bk;^LOJ+aQpYfahG5^pr;4k`PM%`ojcliA=zx53G z8`q*{*P36edkCJbIN$NJmBnveYyRouUtha^RuB0RpN~GbM=k#wuU+3uU-e&X)BiuN zb%MQTQP29`_nuw;PhZRa?EC-PHSp`7|0w=ae-o@fFIazGuz&W?_sLkrJq_`Hzju%-GpI^GJBAteJ$?NJ6P$-p-;vC?Z{u5&Fs6yd zvR!rMP+Tzn>3Vok#`I~h<-X-pU@PsC5P0RQchazppRFCdR@1ELjP^Bm#ko4XXJ-qU*|i_m^r7x@8c*xXsn(U$`_5*+u#m+=+~fp(O!h@4dEYU zUuQ2$H0w9_;rYMzH^SHu%(W22?<*fZKl^WLc27~?Z2Sc6kW5;kcI8Fk_ zv(h-}t5L9iwl>DI(m3hq&aja}#vR!B=3{->)X6v)UNGJ^5MGpCU<+IBZLo%|w4E@X zmF_K@#=-Qj(o?j3;xMkzDt4>DM|20pSqfk}UO$SYkq@|iZ1{@Cst_QG+h z8F?Iv2`k$#h5!Eb*$glan&zYWCUGbh?+X=2EWPvmAO+g9Vic49N_p!QaLuA?!#EUc z9qO3RtN!$imN2hkrw;{Mmoztr%xu}1rDbJ_tNaKA&;pWfAFaO*3jmGuay8Tn@6f)lzT3>_mV05$m z+1=jFh{jBEpWR8CO!r}Kk=Bt1@QdLKcfqBf?vH|Bmt9eZD`#z53cuG~i}fzJwb3JX z!=DZ+>>%@4!llV%o^aLuvvBK0mnOpPr^hhsS%@att${m*FJ#sv>nh$S5SHR~Jb>5B zEW4Kh_cp%7tcM|=dK~A+Q`AtF!tp_4$L@tyPF-98Yd$-k4Ub(c$E>$u@bF_IJWElV zIiI_tOY7lm-`sq>K1XKILHN-rpP%3-YoA_+3)*QNg^MzMtKpaCl~{w0SC-a+Ilnh{ zTbT7as>g;h$KRLsLVoe;b`8FQ*KiiFBadZo&TI>NHxwtr+n!zB3G??d z^HZC?2;TQ;$u-1-HvbM_q_k8|;dp$H?59sUTNDKbs|D67xD}IRbfwiMq z*%mVCIb$<@HfmzNYkdj4L?3OD2MS^WKbCA%mO!}}WbwK;@ne9NsXhoY%d{VI6Dgs#is zMHyb2u;spB4cJO&rW@kQGV_lVs80HHI=oi1A`NzYv0N5*_I271yJp>547(S#u!6nh zOx0lTIR`Gl+wF>5z&npCY=`|)t+U~M+!OQRfYGg)vK%yH)Ij*ij-4Mk6vL0Ceu5(l zq_4xVQuAlPacbdv;e?ehd%;Ows!QPu$##`+N<-Q?I92@9CHThlnOosZi_Oe;>Q?a6 zxo~!3C+0hKzh=2IoI5Zwo&qa>LQOqf$QkAd7w>n=fJ^VD%z`UAc4N|Csi3a_*O&*t z;ZUq~%y|db#}B}Fjm3MtN{<4&Me9T#GSx|IyTc-;ItyXZ-R`BZxK{WPxT}a1J_oks zZq+rg%)GD;ux#-KU06PLa2~8UZzj@Da}%!*fd|DrMf%xlPKLM!u+`bihr=4_Pwv2) zM(99mRkpDHN0D&Y;IfN0Y-Amb^s`MycJ_x&FAqTa*$cM@TEga? z6A!{m@|A|cR!enOz^hf_Tf#O;S)*Y4jbn3QClw1O{p&wI=nuQOzhTnvF=Y|T7qxf% zG#Ibndbm6d-qBB(2k)wMMEPR-|KxKEJ}^$m3J%DU3WS6AY##}S>Yl_kj~&rL9_5Q2 zwP*Ad_{73klrNf_czrjV7^i{q#XdXAa50=Ld==%3<|gK;!PjO_&w|s0*KUHdvR?Fp zZyWPw!#Q%cmGFZIZ$PD4*ZkV+==#ST6U6^cK2Y74%FoGW_pINTo5MX|(SUjQ+<^R{ zGPsuw)U6A}z3kWbr8XFEM#l}$TxI6U{A2Ud;Bo#^eLqKH^zVc-SZs(?dJqR`_hE&+W5{Cy3NU;AZTB@G((*7 zgVq_i5=GfTd!ptRz)SL`Im1>A79$RcK`l*1wAAGeElE6dN=>-dN( zYY1JhsUOF&0gml{#uJWH6Uu}WR_e}zlRUQvz!xswRDe?&-h-ov&fGe**(LN z9}kZGwf%T@|6}({e!t0oJvO_Z|0wsZKbuaJCHf~=?@qAZonXB?!FqSNPYKq$`>)^s z{^|4o@{If=*F$i>rGI~FFT7v=E{z#*Ssp}f)2}S5$H~!mkiSQqFt61rGoI$$XZRid zJ)Zuqc(doz(MM%Q%Tar7=Yq47&@SBd1TPNlyHZ-`N5e9u=}iBAuiX>d!wREkF~dyl zeJ?)b(0e(W3F}Z`t$%4F%%SL-7{8hVYgbLga}GtnD{rPzV4X1Wdke=YJ~pM$IPOB6 zTkQDkue6ykZ7$R|L;sSRrE6o-){~SD?xw0t4LWb`*7jXDz&_(=iy$3e_f0Rn^L~>V z{oFDktPaPm*C@Y2v|FZn5z!$v;Vk01{B#LKS4nMe#0^7c79x5s&oo8cyzyxq;>(?;wa`ku8h`6fUobr#+*lnZhI;CX88=}Jns7MVbXPf z%oFB39=+_vq$_Xd(%yJo!Ga`F#OFnoRfsQ7>gFSso2Cq7QGDCtW(-^%t=|&|yu9 z%+K>XNfglSwS;|$G}H+%xw#Y zAMp`|BcByNf@7unn!|BwHW6^b%9E;al4t!o_(Jk%AvmRB7y4+ao#NVE_{MaZLvW_W zj1c%%@Uc~JcES^8EXMsBHBC5o;F@b3iun^Rc7O}rxO?E@{gd+G(z{!hz!hT8nDkdF zbXJFJ%vZ1}P~U!FE4cpjyE`!NwYn*p`t~`&MC#kWqy&qYwzGvrck9=|;#xNGaM!j) z7&}7i4%d6aGV@kr>UJH@sHshiZ{jKU6ooWU(~m6QUD*&&G-op$SgVu2k$YS42SAEw}d0wSD<`R z-+s#+_{2gdlrQSr_YH#+<65A6QQv+KdpKEm2g(=q?H?TvUz;tN52p!h?uWCoPN>4S zjc-`NIdY?f;0F;#BjG1@sdwT0`uw(VQK~@*TrzFhBe<+vt{q$vR2>0VEnlKTW+nY< zfBUcX!wY`?dCr0$AO1V}Ky_g%>w<9zzukT!7>6L3=R#iZKeL;j`MIwS zjk863ZLF3{&;H!Xns+-TT+ zT?q3!k4Z1$;cVNB%$$%M=?~2D+^3u7;(hYO6oYZR;8SO24oOj_Dbx1w^6Ug2j+e=| z8-n9+#cP$}Do$Y%C4seet`WvlvmD!q=)ulg?j*u4>5K6!!g4QgFM>U|t?{hH^6tnU z3~w9Y`3dIF8QFckKc1CX7wjKlyfZ8Lu_K;sn&yP~-oUdJt5Cvc4F}QGb~{|Ur7y-W zv&zNq^rFx>M?{(Dq89YCc@+Hl6yI9J{Ep9ke9hYo>uR5;Yf*wE9bNZQU@1iGUdExQ zdu*sOJU;C1ZFq9s#d>&3?ff7(IIL$e9Nt1y1&&}#or14f+{EkIEtF2KAhKJ>-LDCh46Y$a1U4>Tq7td&?Ah-*>}ybv9Rj_QQt&N6JK4b^3*1XEt!*F9oh=b_j0 z2##mlzDvOC`E#OjpI)2{=ZP)w!SRAm`rdHkoT#%dui-Lz9a$WID=}CKuG-K&oJ0As zcCNQ6^2M=@_HfwQyUkJ9C4K(~*u5a>F6_Y_gYo{XnA>%6@X0x0?Z}Po#l3j)>Sjz^ zG3NL*p9l26@|SjMI=4S}?V{hOe=VDRrUoyr=>^fx^p`k9;@Gq@C*(n6np^VZru{t4 z^d4Wzyh*e72{atv8o&Sbep-L`c%PD~-Tm-)O0`SZaVS=Z+0BG2<&VyRYZiTyhie^& z^oQ$DZ>oWL6*pYS)Q%=`l}PPqMpI#tIfw9mw9oLDbvTdrvw*u4FRQ@u?!A-Gz%s+D zFTis4ld;wd?KAAU9Tx20_21sV%Y=KN@~>Sh(O>sZ_X+-&-=F3P(HCay&#o2D@8~+s z{fGC}rLltz)9|-*2abM7fu4;w^@I!Et{;O-rW&}zr8;uU;0mz_rfs!SK5r6SV?I=Z zL$TIznIv3)`eGscyVn;wzR-|fPuG&bC-iG{EqTwhh0BakIu6U(Z(!QO<-OzA!-_ZB zGi~7m*i&x9gQfN|ZQ*K04}xIzRlS(DaE+tpHn3(<64Ms0U0b0Ak5?bZw1w;GJ9UJo zY`)2~g&Q0edIlTi&SBcZO*$VK3eO$*oM{VRxKu$HHs@F~ZQ)CjV(-CLA6gyeP+Z+! zBNVpP+sU+r+b>963p)i!kHPWvIrDD7Zm+|cwr~%{7yIDyDJvA{IBP8Tr^#kNxT|#`gN>DofAf# zp1+qCv(GJMe$#${g8gbyCVykU+HY|DK8CT=J@{^qEgXt}dwpI@upcjF>kqRZ?~XrQ z7Z=pu17EwG=7_giRdoWw^<9OzZ=h?_}#Z@T+cC~WU>dayA)7*={dx# zAFO5+bP`ry`KlJyII6q>)+}n^Z4XT&^sNs?1BZ1w)u33zq;7V)sHo+$+y8?<1DC+u{v*Z^Li zqwolJdu<~JdnlfWfcfpBqqvE;&HnK9;lKFojdT9I?oji6wS-JM75h>h>g%zcKiJsB zKfV6UvgM}=P_6{)(EPD=XbiYL``LUGz*->>b75VJ{{7%p#h=!|wiBwJ(=}9Z&-;(w^Cocp zN}l~{+uM&?_oeUCmv%X;X5Xn9x9rwxQCZi2G3+P>mTRVGCWj)wf6z;|V*u=JGNUgA z)^^*2E8(4yx$j}Wt9?hq{IwGUG%onULDMVuP@re;NoH_($oeI4hvwmGvg6t>^+ehln1Sc*yi`r7TgV7ConO!__aCCy>4dn)_z`mKkKsKPt? zo?8a*dOJu6_Fp$)1bjf}%w0Gj<4zkm*k3mU4%JyU4~}T}80Cu{Dc?o=J+wK7PnsF<2IIV@!TR1E8kT-mLR(uMaBiqjuesE07 z7k*-UN(s)d%P@nBu4>l7B~y)6;Igha(&3800v23lWpsedvf`;uPigEI=eGw6B6F>2 zY#;r%^W*avfSb*(dtxV4aL?y@ux{|^p3mJ>`4)8oZuMf#a>RGTBQRcrTNi)hJlxMB*6Vn_F@9o!pAAHdB-bVQF;S)?cLmn

J%`_`@t0CV%U=bvvmgkD7Fn1J4b=l!B*)EEG`u; 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�Yl)!xnH&&CQbr3VrrfG1C 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 From 773076e6e1092f181844b3b9d25b05d8fa013107 Mon Sep 17 00:00:00 2001 From: Chong Gao Date: Thu, 21 May 2026 16:17:36 +0800 Subject: [PATCH 02/17] Add tests for isSupportedTimeZone Cover the named-zone, unknown-id, and offset-style paths in isSupportedTimeZone. The "+25:00" case in particular guards the widened DateTimeException catch added in this PR, which was previously only ZoneRulesException. Signed-off-by: Chong Gao --- .../spark/rapids/jni/GpuTimeZoneDBTest.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/test/java/com/nvidia/spark/rapids/jni/GpuTimeZoneDBTest.java b/src/test/java/com/nvidia/spark/rapids/jni/GpuTimeZoneDBTest.java index c49f5de687..1ee24eba59 100644 --- a/src/test/java/com/nvidia/spark/rapids/jni/GpuTimeZoneDBTest.java +++ b/src/test/java/com/nvidia/spark/rapids/jni/GpuTimeZoneDBTest.java @@ -21,6 +21,8 @@ import org.junit.jupiter.api.Test; import static ai.rapids.cudf.AssertUtils.assertColumnsAreEqual; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.time.LocalDate; import java.time.LocalDateTime; @@ -60,6 +62,23 @@ private static ColumnVector convertOrcTimezonesOnCPU( return ColumnVector.timestampMicroSecondsFromLongs(results); } + @Test + void testIsSupportedTimeZone() { + // Named zones with ZoneRules. + assertTrue(GpuTimeZoneDB.isSupportedTimeZone("UTC")); + assertTrue(GpuTimeZoneDB.isSupportedTimeZone("Asia/Shanghai")); + + // Unknown id. + assertFalse(GpuTimeZoneDB.isSupportedTimeZone("Invalid/Zone")); + + // Offset-style ids: "+05:30" must be accepted; malformed offsets must be + // rejected even when the parser throws DateTimeException rather than the + // narrower ZoneRulesException. This is the regression the widened catch in + // isSupportedTimeZone guards against. + assertTrue(GpuTimeZoneDB.isSupportedTimeZone("+05:30")); + assertFalse(GpuTimeZoneDB.isSupportedTimeZone("+25:00")); + } + @Test void testConvertOrcTimezones() { GpuTimeZoneDB.cacheDatabase(); From ead07f28e3f6c9ba52c745306f822c777ef7e442 Mon Sep 17 00:00:00 2001 From: Chong Gao Date: Thu, 21 May 2026 16:17:59 +0800 Subject: [PATCH 03/17] Mark OrcTimezoneInfo fields final for safe publication rawOffset, transitions, and offsets are only assigned in the constructor. Instances are cached in RUNTIME_TIMEZONE_INFOS (a ConcurrentMap) and shared across threads via the public get(...) method, so the JLS 17.5 final-field semantics are what actually guarantee safe publication without external synchronization. Signed-off-by: Chong Gao --- .../java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java b/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java index a5bba1563e..eda12bc4c4 100644 --- a/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java +++ b/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java @@ -50,13 +50,13 @@ public OrcTimezoneInfo(int rawOffset, long[] transitions, int[] offsets) { } // in milliseconds - int rawOffset; + final int rawOffset; // in milliseconds - long[] transitions; + final long[] transitions; // in milliseconds - int[] offsets; + final int[] offsets; // Lower bound of the range ORC supports (year 0001-01-01 UTC). Computed via // java.time.LocalDate, which uses the proleptic Gregorian calendar, whereas From 03f900a3e4bdf1fd88dec1b7f8328b0213bbec1d Mon Sep 17 00:00:00 2001 From: Chong Gao Date: Thu, 21 May 2026 16:18:21 +0800 Subject: [PATCH 04/17] Document IllegalArgumentException on OrcTimezoneInfo.get The private buildRuntimeOrcTimezoneInfo Javadoc already states "no silent fallback to GMT" on invalid IDs, but the public get(...) entry point did not surface this in its contract. Add the @throws clause so callers do not assume invalid IDs return null or a default. Also normalize the abbreviation "Id" to "ID" in the @param to match the all-caps convention used by java.util.TimeZone. Signed-off-by: Chong Gao --- .../java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java b/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java index eda12bc4c4..ce3b754271 100644 --- a/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java +++ b/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java @@ -87,8 +87,10 @@ public String toString() { * Get timezone info for the specified timezone ID. * Historical transitions are generated at runtime from public JVM APIs and cached per ID. * - * @param timezoneId timezone Id + * @param timezoneId timezone ID * @return timezone info + * @throws IllegalArgumentException if {@code timezoneId} is not a valid zone ID accepted + * by {@link GpuTimeZoneDB#getZoneId(String)}. There is no silent fallback to GMT. */ public static OrcTimezoneInfo get(String timezoneId) { return RUNTIME_TIMEZONE_INFOS.computeIfAbsent( From 6b79bd062593f76f5c8428a5c8608e2f7157330b Mon Sep 17 00:00:00 2001 From: Chong Gao Date: Thu, 21 May 2026 16:19:17 +0800 Subject: [PATCH 05/17] Add OrcTimezoneInfoTest for the runtime build path The end-to-end testConvertOrcTimezones only exercises OrcTimezoneInfo via the GPU pipeline on a curated non-DST list, so the four core behaviours of the new runtime build path have no direct coverage: - fixed-offset zones (+05:30) return ZoneRules-derived rawOffset, not the silent GMT fallback of TimeZone.getTimeZone - computeIfAbsent returns the cached instance on repeated lookups - invalid IDs throw IllegalArgumentException - named historical-transition zones produce parallel, strictly increasing transitions[] and offsets[] arrays Signed-off-by: Chong Gao --- .../spark/rapids/jni/OrcTimezoneInfoTest.java | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 src/test/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfoTest.java diff --git a/src/test/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfoTest.java b/src/test/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfoTest.java new file mode 100644 index 0000000000..8c9c110e8c --- /dev/null +++ b/src/test/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfoTest.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2025-2026, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.nvidia.spark.rapids.jni; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class OrcTimezoneInfoTest { + + @Test + void testGetFixedOffsetZone() { + // Fixed-offset zones must return a non-null OrcTimezoneInfo with the + // offset derived from ZoneRules (not from TimeZone.getTimeZone, which + // would silently map "+05:30" to GMT). +05:30 == 19_800_000 ms. + OrcTimezoneInfo info = OrcTimezoneInfo.get("+05:30"); + assertNotNull(info); + assertEquals(19_800_000, info.rawOffset); + assertNull(info.transitions); + assertNull(info.offsets); + } + + @Test + void testGetCachesByKey() { + // computeIfAbsent must return the same instance on the second call so + // that other threads sharing RUNTIME_TIMEZONE_INFOS see a stable object. + OrcTimezoneInfo a = OrcTimezoneInfo.get("Asia/Kolkata"); + OrcTimezoneInfo b = OrcTimezoneInfo.get("Asia/Kolkata"); + assertSame(a, b); + } + + @Test + void testGetThrowsOnInvalidId() { + // Documented contract: invalid IDs throw IllegalArgumentException. + // There is no silent fallback to GMT. + assertThrows(IllegalArgumentException.class, + () -> OrcTimezoneInfo.get("Invalid/Zone")); + } + + @Test + void testGetHistoricalTransitionsZone() { + // Asia/Shanghai is a non-DST named zone with real historical transitions. + // Verify that the runtime build path populates both arrays consistently. + OrcTimezoneInfo info = OrcTimezoneInfo.get("Asia/Shanghai"); + assertNotNull(info); + assertNotNull(info.transitions, "Asia/Shanghai should have historical transitions"); + assertNotNull(info.offsets); + assertEquals(info.transitions.length, info.offsets.length, + "transitions and offsets must be the same length"); + // Transitions must be strictly increasing so the GPU binary search is well-defined. + for (int i = 1; i < info.transitions.length; i++) { + assertTrue(info.transitions[i] > info.transitions[i - 1], + "transitions must be strictly increasing"); + } + } +} From d932c6ab488ce4bac6dd9b2dcdc990eb6e9853ad Mon Sep 17 00:00:00 2001 From: Chong Gao Date: Thu, 21 May 2026 16:19:41 +0800 Subject: [PATCH 06/17] Use 1-indexed month in utcMillisForDate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The call site utcMillisForDate(1, 0, 1) mixed 0-based month with 1-based day and relied on a +1 adjustment inside the helper. Make all three parameters 1-indexed so the call reads as year=1, month=1 (January), day=1 — matching LocalDate.of and removing the silent asymmetry flagged in PR review thread 3. The computed instant (year 0001-01-01 UTC, MIN_SUPPORTED_ORC_UTC_MILLIS) is unchanged. Signed-off-by: Chong Gao --- .../java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java b/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java index ce3b754271..820b94d576 100644 --- a/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java +++ b/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java @@ -64,11 +64,14 @@ public OrcTimezoneInfo(int rawOffset, long[] transitions, int[] offsets) { // calendar with the 1582 cutover for date-field interpretations. In practice // this difference does not affect offset lookup (which is purely instant-based // for ZoneInfo), so the two calendars agree on the offset at this instant. - private static final long MIN_SUPPORTED_ORC_UTC_MILLIS = utcMillisForDate(1, 0, 1); + private static final long MIN_SUPPORTED_ORC_UTC_MILLIS = utcMillisForDate(1, 1, 1); private static final long HISTORICAL_TRANSITION_SCAN_STEP_MILLIS = 24L * 3600_000L; + // year, month, and day are all 1-indexed, matching LocalDate.of conventions + // (e.g. month=1 is January). This avoids the easy-to-misread mix of 0-based + // month and 1-based day at the call site. private static long utcMillisForDate(int year, int month, int day) { - return LocalDate.of(year, month + 1, day).toEpochDay() * 24L * 3600_000L; + return LocalDate.of(year, month, day).toEpochDay() * 24L * 3600_000L; } @Override From 2a568162907d0e0d8462ae3204cf70ee86f22270 Mon Sep 17 00:00:00 2001 From: Chong Gao Date: Thu, 21 May 2026 16:21:14 +0800 Subject: [PATCH 07/17] Add convertOrcTimezones regression test for invalid IDs The runtime build path documents that invalid timezone IDs fail with an exception rather than silently falling back to GMT, but no test exercised that contract end-to-end through convertOrcTimezones. Add one so a future refactor that broadens a catch block can't quietly re-introduce the silent-GMT regression. Signed-off-by: Chong Gao --- .../spark/rapids/jni/GpuTimeZoneDBTest.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/test/java/com/nvidia/spark/rapids/jni/GpuTimeZoneDBTest.java b/src/test/java/com/nvidia/spark/rapids/jni/GpuTimeZoneDBTest.java index 1ee24eba59..3e4a3ded9b 100644 --- a/src/test/java/com/nvidia/spark/rapids/jni/GpuTimeZoneDBTest.java +++ b/src/test/java/com/nvidia/spark/rapids/jni/GpuTimeZoneDBTest.java @@ -22,6 +22,7 @@ import static ai.rapids.cudf.AssertUtils.assertColumnsAreEqual; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.time.LocalDate; @@ -79,6 +80,23 @@ void testIsSupportedTimeZone() { assertFalse(GpuTimeZoneDB.isSupportedTimeZone("+25:00")); } + @Test + void testConvertOrcTimezonesRejectsInvalidId() { + // Invalid timezone IDs must surface an exception rather than silently + // falling back to GMT. The DST guard at the top of convertOrcTimezones + // calls ZoneId.of(...), so an unknown id will throw before the runtime + // build path or the GPU kernel ever runs. We assert the broad + // RuntimeException type so this stays a regression guard even if the + // exact wrapping (DateTimeException vs IllegalArgumentException vs + // IllegalStateException) is refactored later. + GpuTimeZoneDB.cacheDatabase(); + try (ColumnVector input = + ColumnVector.timestampMicroSecondsFromLongs(new long[] {0L})) { + assertThrows(RuntimeException.class, + () -> GpuTimeZoneDB.convertOrcTimezones(input, "Invalid/Zone", "UTC")); + } + } + @Test void testConvertOrcTimezones() { GpuTimeZoneDB.cacheDatabase(); From 454af914e6aac9b321bfcee7d11853817ce681ac Mon Sep 17 00:00:00 2001 From: Chong Gao Date: Thu, 21 May 2026 16:21:39 +0800 Subject: [PATCH 08/17] Filter getAllTimezoneIds to ids OrcTimezoneInfo.get can build TimeZone.getAvailableIDs() returns POSIX-style names on some JDK distributions (e.g. EST5EDT, SystemV/AST4) that ZoneId.of(id, ZoneId.SHORT_IDS) rejects. Test code that iterates this list via the getOrcSupportedTimezones() bridge and calls OrcTimezoneInfo.get(id) would hit IllegalArgumentException for those entries. Pre-filtering via isSupportedTimeZone aligns the lister with the loader. Signed-off-by: Chong Gao --- .../com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java b/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java index 820b94d576..9168d1f9b5 100644 --- a/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java +++ b/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java @@ -140,7 +140,17 @@ private static OrcTimezoneInfo buildRuntimeOrcTimezoneInfo(String timezoneId) { public static List getAllTimezoneIds() { String[] ids = TimeZone.getAvailableIDs(); Arrays.sort(ids); - return Arrays.asList(ids); + // Filter to ids that get(...) can actually build. TimeZone.getAvailableIDs() + // on some JDK builds includes POSIX-style names (e.g. "EST5EDT", "SystemV/AST4") + // that ZoneId.of(id, ZoneId.SHORT_IDS) rejects, which would make iterating this + // list and feeding entries to OrcTimezoneInfo.get(...) throw IllegalArgumentException. + List result = new ArrayList<>(ids.length); + for (String id : ids) { + if (GpuTimeZoneDB.isSupportedTimeZone(id)) { + result.add(id); + } + } + return result; } private static int getInitialOffset(TimeZone tz) { From 5f88e435025651546c4622dead89e4d68a5ac5df Mon Sep 17 00:00:00 2001 From: Chong Gao Date: Thu, 21 May 2026 16:22:04 +0800 Subject: [PATCH 09/17] Add Javadoc to getAllTimezoneIds Document that the returned list is the intersection of TimeZone.getAvailableIDs() and isSupportedTimeZone, matches the contract of get(...), and is recomputed on every call. Signed-off-by: Chong Gao --- .../nvidia/spark/rapids/jni/OrcTimezoneInfo.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java b/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java index 9168d1f9b5..f60a5049ae 100644 --- a/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java +++ b/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java @@ -137,13 +137,21 @@ private static OrcTimezoneInfo buildRuntimeOrcTimezoneInfo(String timezoneId) { historicalTransitions.transitions, historicalTransitions.offsets); } + /** + * Returns the sorted list of timezone IDs that {@link #get(String)} can build — + * the intersection of {@link TimeZone#getAvailableIDs()} and + * {@link GpuTimeZoneDB#isSupportedTimeZone(String)}. POSIX-style entries (e.g. + * {@code "EST5EDT"}, {@code "SystemV/AST4"}) that some JDK builds expose but + * {@code ZoneId.of(id, ZoneId.SHORT_IDS)} rejects are filtered out. + * + *

The result is computed on every call; callers that need it repeatedly + * should cache it themselves. + * + * @return sorted list of ORC-supported timezone IDs + */ public static List getAllTimezoneIds() { String[] ids = TimeZone.getAvailableIDs(); Arrays.sort(ids); - // Filter to ids that get(...) can actually build. TimeZone.getAvailableIDs() - // on some JDK builds includes POSIX-style names (e.g. "EST5EDT", "SystemV/AST4") - // that ZoneId.of(id, ZoneId.SHORT_IDS) rejects, which would make iterating this - // list and feeding entries to OrcTimezoneInfo.get(...) throw IllegalArgumentException. List result = new ArrayList<>(ids.length); for (String id : ids) { if (GpuTimeZoneDB.isSupportedTimeZone(id)) { From 21b1a095b2b7a076c2b33af959b4e512dccd1d55 Mon Sep 17 00:00:00 2001 From: Chong Gao Date: Thu, 21 May 2026 16:22:42 +0800 Subject: [PATCH 10/17] Test getAllTimezoneIds contract Verify the documented contract: non-empty, sorted, includes the well-known UTC and Asia/Shanghai entries, and every returned id is accepted by isSupportedTimeZone (i.e. the lister and the loader agree). Signed-off-by: Chong Gao --- .../spark/rapids/jni/OrcTimezoneInfoTest.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/test/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfoTest.java b/src/test/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfoTest.java index 8c9c110e8c..f2861ce55e 100644 --- a/src/test/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfoTest.java +++ b/src/test/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfoTest.java @@ -18,7 +18,10 @@ import org.junit.jupiter.api.Test; +import java.util.List; + import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; @@ -56,6 +59,28 @@ void testGetThrowsOnInvalidId() { () -> OrcTimezoneInfo.get("Invalid/Zone")); } + @Test + void testGetAllTimezoneIdsContract() { + List ids = OrcTimezoneInfo.getAllTimezoneIds(); + + assertFalse(ids.isEmpty(), "expected at least one supported timezone id"); + assertTrue(ids.contains("UTC"), "UTC must be present"); + assertTrue(ids.contains("Asia/Shanghai"), "Asia/Shanghai must be present"); + + // Sorted ascending. + for (int i = 1; i < ids.size(); i++) { + assertTrue(ids.get(i - 1).compareTo(ids.get(i)) <= 0, + "list must be sorted: " + ids.get(i - 1) + " > " + ids.get(i)); + } + + // Every id must be one that OrcTimezoneInfo.get can build — i.e. the lister + // and the loader agree. + for (String id : ids) { + assertTrue(GpuTimeZoneDB.isSupportedTimeZone(id), + "getAllTimezoneIds returned an id that isSupportedTimeZone rejects: " + id); + } + } + @Test void testGetHistoricalTransitionsZone() { // Asia/Shanghai is a non-DST named zone with real historical transitions. From d4912a7ec9e0ad58b56f7c7933dd98c3a4d0759a Mon Sep 17 00:00:00 2001 From: Chong Gao Date: Thu, 21 May 2026 16:23:11 +0800 Subject: [PATCH 11/17] Document the lister/loader contract on getOrcSupportedTimezones The test bridge promises "all supported timezones" but used to return the raw TimeZone.getAvailableIDs() output. Now that getAllTimezoneIds filters to ids OrcTimezoneInfo.get can build, surface the alignment in the Javadoc so future readers know the contract matches the name and that callers do not need to pre-filter. Signed-off-by: Chong Gao --- src/main/java/com/nvidia/spark/rapids/jni/GpuTimeZoneDB.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/nvidia/spark/rapids/jni/GpuTimeZoneDB.java b/src/main/java/com/nvidia/spark/rapids/jni/GpuTimeZoneDB.java index 11e9a8fab6..b509ecb77f 100644 --- a/src/main/java/com/nvidia/spark/rapids/jni/GpuTimeZoneDB.java +++ b/src/main/java/com/nvidia/spark/rapids/jni/GpuTimeZoneDB.java @@ -543,7 +543,10 @@ private static Table getTableForUtilTZ(OrcTimezoneInfo info) { /** * Only for testing purpose. - * Get all supported timezones for ORC timezone conversion. + * Get all supported timezones for ORC timezone conversion. The returned list + * is the same as {@link OrcTimezoneInfo#getAllTimezoneIds()}: it is already + * filtered to ids that {@link OrcTimezoneInfo#get(String)} can build, so + * callers do not need to pre-filter via {@link #isSupportedTimeZone(String)}. */ static List getOrcSupportedTimezones() { return OrcTimezoneInfo.getAllTimezoneIds(); From 12c37267b1bd2eb6c2b1ad43e8c2043356e9a1e0 Mon Sep 17 00:00:00 2001 From: Chong Gao Date: Thu, 21 May 2026 16:25:53 +0800 Subject: [PATCH 12/17] Use exponential search in collectTimeZoneTransitionsByScanning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous fixed 24h-step scan walked ~686k probes when called from year 0001 toward a zone's first historical transition (~year 1880 for typical IANA zones). Because the entire pre-history stretch has no transitions, each tz.getOffset call returned the same offset and the loop did no useful work — yet it ran under RUNTIME_TIMEZONE_INFOS.computeIfAbsent, blocking other threads waiting on the same key on cold-cache lookups. Replace it with an exponential-extend-then-bisect strategy: keep the 24h base step, but double the step whenever the offset is unchanged so empty centuries are skipped in O(log N) probes. Once a mismatch is seen, hand the [lo, hi] bracket to the existing binarySearchTransition to pin the transition to 1ms precision. The optimisation assumes at most one offset transition lives inside the expanded bracket, which is true for IANA tzdata; A->B->A pairs narrower than the base step are an independent concern of the step size itself, not of this optimisation. Signed-off-by: Chong Gao --- .../spark/rapids/jni/OrcTimezoneInfo.java | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java b/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java index f60a5049ae..b55d9741f8 100644 --- a/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java +++ b/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java @@ -220,14 +220,32 @@ private static int collectTimeZoneTransitionsByScanning( long cursor = scanStartMs; int currentOffset = startOffset; while (cursor < scanEndMs) { - long probe = Math.min(cursor + HISTORICAL_TRANSITION_SCAN_STEP_MILLIS, scanEndMs); - int probeOffset = tz.getOffset(probe); - if (probeOffset == currentOffset) { - cursor = probe; + // Exponentially expand the probe step while the offset stays equal to + // currentOffset. This collapses long no-transition stretches (e.g. the + // year-0001-to-first-historical-transition gap, ~1880 years for typical + // IANA zones) from O(N) day probes to O(log N). Once the probe lands on + // a different offset, the [lo, hi] bracket contains a transition and we + // hand it to binarySearchTransition. The bracket may be wider than the + // base 24h step, so this assumes at most one offset transition lives in + // the expanded window — which holds for real IANA data; A->B->A pairs + // narrower than the base step are addressed separately by the step size. + long lo = cursor; + long step = HISTORICAL_TRANSITION_SCAN_STEP_MILLIS; + long hi = Math.min(lo + step, scanEndMs); + int hiOffset = tz.getOffset(hi); + while (hiOffset == currentOffset && hi < scanEndMs) { + lo = hi; + step = Math.min(step * 2L, scanEndMs - hi); + hi = lo + step; + hiOffset = tz.getOffset(hi); + } + if (hiOffset == currentOffset) { + // Reached scanEndMs without seeing any transition. + cursor = hi; continue; } - long exactTransition = binarySearchTransition(tz, cursor, probe); + long exactTransition = binarySearchTransition(tz, lo, hi); int offsetAfterTransition = tz.getOffset(exactTransition); transitions.add(exactTransition); offsets.add(offsetAfterTransition); From ad3dbd9063a44dcefdc296a27266f9940fafa248 Mon Sep 17 00:00:00 2001 From: Chong Gao Date: Thu, 21 May 2026 16:27:19 +0800 Subject: [PATCH 13/17] Shrink the historical-transition scan step to 6h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 24h step let a pair of offset transitions A->B->A inside the same window net to zero: tz.getOffset(probe) would still return A and the scanner would silently skip both. Real IANA tzdata does not contain such pairs today, but the previous step left no margin if a future data update added one (or if the upcoming DST work routes DST zones through this scanner). 6h is the largest base step that still sits safely under the minimum spacing of real-world transitions. Combined with the exponential extension in the previous commit, the empty-pre-history sweep remains O(log N) probes — the smaller base step only adds a constant factor at the bottom of the search. Signed-off-by: Chong Gao --- .../com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java b/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java index b55d9741f8..d22ee46796 100644 --- a/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java +++ b/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java @@ -65,7 +65,14 @@ public OrcTimezoneInfo(int rawOffset, long[] transitions, int[] offsets) { // this difference does not affect offset lookup (which is purely instant-based // for ZoneInfo), so the two calendars agree on the offset at this instant. private static final long MIN_SUPPORTED_ORC_UTC_MILLIS = utcMillisForDate(1, 1, 1); - private static final long HISTORICAL_TRANSITION_SCAN_STEP_MILLIS = 24L * 3600_000L; + // Base probe width used by collectTimeZoneTransitionsByScanning. The scanner + // detects a transition by sampling tz.getOffset(probe) and comparing it to + // the running offset; a pair of transitions A->B->A whose two endpoints fall + // inside one probe step will net to zero and slip through. 6 hours is + // smaller than the minimum spacing between any two real transitions in the + // current IANA tzdata (the closest pairs are DST start/end, ~hours apart on + // separate days), so paired transitions cannot hide in a single window. + private static final long HISTORICAL_TRANSITION_SCAN_STEP_MILLIS = 6L * 3600_000L; // year, month, and day are all 1-indexed, matching LocalDate.of conventions // (e.g. month=1 is January). This avoids the easy-to-misread mix of 0-based From 2a5872c35a6d31baf3a248a0a65287b410d7f13b Mon Sep 17 00:00:00 2001 From: Chong Gao Date: Thu, 21 May 2026 17:08:05 +0800 Subject: [PATCH 14/17] Copyright Signed-off-by: Chong Gao --- .../java/com/nvidia/spark/rapids/jni/GpuTimeZoneDBTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/nvidia/spark/rapids/jni/GpuTimeZoneDBTest.java b/src/test/java/com/nvidia/spark/rapids/jni/GpuTimeZoneDBTest.java index 3e4a3ded9b..d35e9826da 100644 --- a/src/test/java/com/nvidia/spark/rapids/jni/GpuTimeZoneDBTest.java +++ b/src/test/java/com/nvidia/spark/rapids/jni/GpuTimeZoneDBTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, NVIDIA CORPORATION. + * Copyright (c) 2025-2026, NVIDIA CORPORATION. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From ae9c045665f5e2806ad00282e1e411476ab38344 Mon Sep 17 00:00:00 2001 From: Chong Gao Date: Fri, 22 May 2026 15:42:04 +0800 Subject: [PATCH 15/17] Address review feedback - Fix copyright year to 2026 (new file) - Document the ZoneRules.getTransitions() completeness invariant the scan algorithm relies on, flagged for DST follow-ups to revisit - Cover the named-fixed-offset case (UTC) in OrcTimezoneInfoTest - Tighten the getAllTimezoneIds sort check to also assert distinctness - Document the empty-getTransitions() coverage gap in testGetHistoricalTransitionsZone Signed-off-by: Chong Gao --- .../spark/rapids/jni/OrcTimezoneInfo.java | 10 +++++++- .../spark/rapids/jni/OrcTimezoneInfoTest.java | 25 ++++++++++++++++--- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java b/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java index d22ee46796..ed1102d47b 100644 --- a/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java +++ b/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025-2026, NVIDIA CORPORATION. + * Copyright (c) 2026, NVIDIA CORPORATION. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -197,6 +197,14 @@ private static HistoricalTransitions buildHistoricalTransitions( long beforeTransitionMs = transitionMs - 1; int offsetBeforeTransition = tz.getOffset(beforeTransitionMs); + // Invariant: between two consecutive entries returned by + // ZoneRules.getTransitions(), the wall offset is constant — no hidden + // paired round-trips (e.g. A->B->A) net to zero between entries. If + // that ever breaks (DST zones, future tzdata revisions), the guard + // below will not fire and both transitions in the pair will be + // silently dropped. The DST guard in + // GpuTimeZoneDB.convertOrcTimezones currently keeps this dormant; + // any follow-up that relaxes it must revisit this code. if (beforeTransitionMs >= scanCursor && offsetBeforeTransition != currentOffset) { currentOffset = collectTimeZoneTransitionsByScanning( tz, scanCursor, beforeTransitionMs, currentOffset, transitions, offsets); diff --git a/src/test/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfoTest.java b/src/test/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfoTest.java index f2861ce55e..c34f2cc9a0 100644 --- a/src/test/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfoTest.java +++ b/src/test/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfoTest.java @@ -42,6 +42,19 @@ void testGetFixedOffsetZone() { assertNull(info.offsets); } + @Test + void testGetFixedOffsetNamedZone() { + // "UTC" is a named zone whose ZoneRules.isFixedOffset() is true. Cover + // it explicitly so a regression that treats "UTC" as a historical zone + // (non-null transitions) — or that silently maps it to GMT via + // TimeZone.getTimeZone — is caught. rawOffset must be 0. + OrcTimezoneInfo info = OrcTimezoneInfo.get("UTC"); + assertNotNull(info); + assertEquals(0, info.rawOffset); + assertNull(info.transitions); + assertNull(info.offsets); + } + @Test void testGetCachesByKey() { // computeIfAbsent must return the same instance on the second call so @@ -67,10 +80,10 @@ void testGetAllTimezoneIdsContract() { assertTrue(ids.contains("UTC"), "UTC must be present"); assertTrue(ids.contains("Asia/Shanghai"), "Asia/Shanghai must be present"); - // Sorted ascending. + // Sorted ascending AND distinct (strict <, not <=, also catches duplicate ids). for (int i = 1; i < ids.size(); i++) { - assertTrue(ids.get(i - 1).compareTo(ids.get(i)) <= 0, - "list must be sorted: " + ids.get(i - 1) + " > " + ids.get(i)); + assertTrue(ids.get(i - 1).compareTo(ids.get(i)) < 0, + "list must be sorted and distinct: " + ids.get(i - 1) + " >= " + ids.get(i)); } // Every id must be one that OrcTimezoneInfo.get can build — i.e. the lister @@ -85,6 +98,12 @@ void testGetAllTimezoneIdsContract() { void testGetHistoricalTransitionsZone() { // Asia/Shanghai is a non-DST named zone with real historical transitions. // Verify that the runtime build path populates both arrays consistently. + // + // Known coverage gap: zones whose ZoneRules.getTransitions() is empty + // but whose historical offset changed cannot exercise the scan-only + // path in collectTimeZoneTransitionsByScanning, because + // buildHistoricalTransitions returns EMPTY early for empty transition + // lists. Covering it would require a synthetic zone. OrcTimezoneInfo info = OrcTimezoneInfo.get("Asia/Shanghai"); assertNotNull(info); assertNotNull(info.transitions, "Asia/Shanghai should have historical transitions"); From 1a3bc9993686799823993d64378de18faecc2b7d Mon Sep 17 00:00:00 2001 From: Chong Gao Date: Fri, 22 May 2026 17:06:43 +0800 Subject: [PATCH 16/17] Fix stale 24h reference in scan loop comment The base step constant was shrunk to 6h but the comment in collectTimeZoneTransitionsByScanning still said "base 24h step". Signed-off-by: Chong Gao --- src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java b/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java index ed1102d47b..18470cf8ed 100644 --- a/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java +++ b/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java @@ -241,7 +241,7 @@ private static int collectTimeZoneTransitionsByScanning( // IANA zones) from O(N) day probes to O(log N). Once the probe lands on // a different offset, the [lo, hi] bracket contains a transition and we // hand it to binarySearchTransition. The bracket may be wider than the - // base 24h step, so this assumes at most one offset transition lives in + // base 6h step, so this assumes at most one offset transition lives in // the expanded window — which holds for real IANA data; A->B->A pairs // narrower than the base step are addressed separately by the step size. long lo = cursor; From 10b463df66a2a02bfc627ffc67a8ec80de48d0ed Mon Sep 17 00:00:00 2001 From: Chong Gao Date: Fri, 22 May 2026 17:38:27 +0800 Subject: [PATCH 17/17] Use canonical zone id when looking up TimeZone GpuTimeZoneDB.getZoneId resolves caller input through ZoneId.SHORT_IDS, so "IST" -> Asia/Kolkata, "CTT" -> Asia/Shanghai, etc. The follow-up TimeZone.getTimeZone(timezoneId) used the raw caller string, and on JVM distributions whose legacy TimeZone database maps "IST"/"BST"/etc. differently the transition list and the tz.getOffset probes targeted different zones, silently producing mixed offset data. Use zoneId.getId() so both APIs always refer to the same zone. Signed-off-by: Chong Gao --- .../com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java b/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java index 18470cf8ed..348b3cfeb2 100644 --- a/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java +++ b/src/main/java/com/nvidia/spark/rapids/jni/OrcTimezoneInfo.java @@ -134,7 +134,14 @@ private static OrcTimezoneInfo buildRuntimeOrcTimezoneInfo(String timezoneId) { int fixedOffsetMs = rules.getOffset(Instant.EPOCH).getTotalSeconds() * 1000; return new OrcTimezoneInfo(fixedOffsetMs, null, null); } - TimeZone tz = TimeZone.getTimeZone(timezoneId); + // Use the canonical ID from the resolved ZoneId (e.g. "Asia/Kolkata" for + // input "IST") so that TimeZone and ZoneRules always refer to the same + // zone, regardless of how the JVM's legacy TimeZone database maps + // 3-letter aliases. ZoneId.SHORT_IDS in getZoneId resolves "IST" to + // "Asia/Kolkata"; TimeZone.getTimeZone("IST") may map to a different + // zone on some JVM distributions, which would silently produce mixed + // offset data with no exception. + TimeZone tz = TimeZone.getTimeZone(zoneId.getId()); List transitionList = rules.getTransitions(); HistoricalTransitions historicalTransitions = buildHistoricalTransitions(tz, transitionList); if (historicalTransitions.transitions == null) {