From 2a53108c1c6a472df9f60d825df9c98636e27520 Mon Sep 17 00:00:00 2001 From: Timothy Elgersma Date: Fri, 29 May 2026 16:49:03 -0400 Subject: [PATCH] Add more timezone IDs and add tests to validate timezone compatibility Appends 44 timezone entries to `pinot-common/src/main/resources/zone-index.properties`. The entries cover: - `America/Coyhaique` - `Etc/GMT`, `Etc/GMT+0` through `Etc/GMT+12`, `Etc/GMT-0` through `Etc/GMT-14`, `Etc/GMT0` - `Etc/Greenwich`, `Etc/UCT`, `Etc/UTC`, `Etc/Universal`, `Etc/Zulu` - `GMT`, `GMT+0`, `GMT-0`, `GMT0`, `Greenwich` - `UCT`, `Universal`, `Zulu` `EST`, `HST`, `MST`, `Factory`, and `US/Pacific-New` remain excluded, consistent with the existing comments in the file noting they are absent from `java.time` or removed from tzdata. Adds a new `TimeZoneKeyTest` class that validates all timezones listed in `zone-index.properties` are recognized by three timezone libraries: - `java.util.TimeZone` - `java.time.ZoneId` - Joda-Time's `DateTimeZone` The tests collect all unsupported timezones before failing, so a single test run surfaces every incompatible entry rather than stopping at the first failure. Also includes basic sanity checks that the zone index file is non-empty and that the UTC key is present with key 0. Stripe's timezone allowlist includes these timezone IDs, which are supported by all three required Java timezone libraries (`java.util.TimeZone`, `java.time.ZoneId`, and Joda-Time). However, they were absent from `zone-index.properties`, meaning Pinot would reject them as unrecognized timezone identifiers when used in queries. The timezones in this file are supposed to be compatible with `java.util.Timezone`, `java.time.ZoneId`, and `jodatime`, but nothing actually ensured that. I could manually check, but it's nice to have real validation. --- .../src/main/resources/zone-index.properties | 45 +++++++ .../common/function/TimeZoneKeyTest.java | 122 ++++++++++++++++++ 2 files changed, 167 insertions(+) create mode 100644 pinot-common/src/test/java/org/apache/pinot/common/function/TimeZoneKeyTest.java diff --git a/pinot-common/src/main/resources/zone-index.properties b/pinot-common/src/main/resources/zone-index.properties index e158637a75fa..58228e7e2b25 100644 --- a/pinot-common/src/main/resources/zone-index.properties +++ b/pinot-common/src/main/resources/zone-index.properties @@ -25,6 +25,7 @@ # Joda time library. This is needed by the function SqlCompatibleDateTrunc function. # # suppress inspection "UnusedProperty" for whole file +# 0 - UTC 1 -14:00 2 -13:59 3 -13:58 @@ -2258,3 +2259,47 @@ 2231 Pacific/Kanton 2232 Europe/Kyiv 2233 America/Ciudad_Juarez +2234 America/Coyhaique +2235 Etc/GMT +2236 Etc/GMT+0 +2237 Etc/GMT+1 +2238 Etc/GMT+2 +2239 Etc/GMT+3 +2240 Etc/GMT+4 +2241 Etc/GMT+5 +2242 Etc/GMT+6 +2243 Etc/GMT+7 +2244 Etc/GMT+8 +2245 Etc/GMT+9 +2246 Etc/GMT+10 +2247 Etc/GMT+11 +2248 Etc/GMT+12 +2249 Etc/GMT-0 +2250 Etc/GMT-1 +2251 Etc/GMT-2 +2252 Etc/GMT-3 +2253 Etc/GMT-4 +2254 Etc/GMT-5 +2255 Etc/GMT-6 +2256 Etc/GMT-7 +2257 Etc/GMT-8 +2258 Etc/GMT-9 +2259 Etc/GMT-10 +2260 Etc/GMT-11 +2261 Etc/GMT-12 +2262 Etc/GMT-13 +2263 Etc/GMT-14 +2264 Etc/GMT0 +2265 Etc/Greenwich +2266 Etc/UCT +2267 Etc/UTC +2268 Etc/Universal +2269 Etc/Zulu +2270 GMT +2271 GMT+0 +2272 GMT-0 +2273 GMT0 +2274 Greenwich +2275 UCT +2276 Universal +2277 Zulu diff --git a/pinot-common/src/test/java/org/apache/pinot/common/function/TimeZoneKeyTest.java b/pinot-common/src/test/java/org/apache/pinot/common/function/TimeZoneKeyTest.java new file mode 100644 index 000000000000..bdb95848747d --- /dev/null +++ b/pinot-common/src/test/java/org/apache/pinot/common/function/TimeZoneKeyTest.java @@ -0,0 +1,122 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.pinot.common.function; + +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.TimeZone; +import java.util.regex.Pattern; +import org.joda.time.DateTimeZone; +import org.testng.Assert; +import org.testng.annotations.Test; + + +/** + * Tests that all timezones in zone-index.properties are supported by java.util.TimeZone, + * java.time.ZoneId, and Joda-Time's DateTimeZone. + */ +public class TimeZoneKeyTest { + + // Matches bare numeric UTC offset strings like +01:00, -05:30, +00:01. + // java.util.TimeZone does not support this format; it requires GMT+HH:MM. + private static final Pattern MINUTE_GRANULARITY_OFFSET = + Pattern.compile("[+-]\\d{2}:[0-5]\\d"); + + @Test + public void testAllTimeZonesSupportedByJavaUtilTimeZone() { + List unsupported = new ArrayList<>(); + for (TimeZoneKey timeZoneKey : TimeZoneKey.getTimeZoneKeys()) { + String zoneId = timeZoneKey.getId(); + if (MINUTE_GRANULARITY_OFFSET.matcher(zoneId).matches()) { + continue; + } + TimeZone timeZone = TimeZone.getTimeZone(zoneId); + // TimeZone.getTimeZone returns GMT for unrecognized IDs, so verify the ID matches + // unless the original was already GMT/UTC + if (!zoneId.equals("GMT") && !zoneId.equals("UTC") && timeZone.getID().equals("GMT")) { + unsupported.add(zoneId); + } + } + Assert.assertTrue(unsupported.isEmpty(), + "java.util.TimeZone does not recognize the following timezones: " + unsupported); + } + + @Test + public void testAllTimeZonesSupportedByJavaTimeZoneId() { + List unsupported = new ArrayList<>(); + for (TimeZoneKey timeZoneKey : TimeZoneKey.getTimeZoneKeys()) { + String zoneId = timeZoneKey.getId(); + try { + ZoneId.of(zoneId); + } catch (Exception e) { + unsupported.add(zoneId + " (" + e.getMessage() + ")"); + } + } + Assert.assertTrue(unsupported.isEmpty(), + "java.time.ZoneId does not recognize the following timezones: " + unsupported); + } + + @Test + public void testAllTimeZonesSupportedByJodaTime() { + List unsupported = new ArrayList<>(); + for (TimeZoneKey timeZoneKey : TimeZoneKey.getTimeZoneKeys()) { + String zoneId = timeZoneKey.getId(); + try { + DateTimeZone.forID(zoneId); + } catch (Exception e) { + unsupported.add(zoneId + " (" + e.getMessage() + ")"); + } + } + Assert.assertTrue(unsupported.isEmpty(), + "Joda-Time DateTimeZone does not recognize the following timezones: " + unsupported); + } + + @Test + public void testNoDuplicateTimezoneIds() { + Set seen = new HashSet<>(); + Set duplicates = new LinkedHashSet<>(); + for (TimeZoneKey timeZoneKey : TimeZoneKey.getTimeZoneKeys()) { + String id = timeZoneKey.getId(); + if (!seen.add(id.toLowerCase(Locale.ENGLISH))) { + duplicates.add(id); + } + } + Assert.assertTrue(duplicates.isEmpty(), + "zone-index.properties has duplicate timezone IDs: " + duplicates); + } + + @Test + public void testZoneIndexPropertiesIsNotEmpty() { + Assert.assertFalse(TimeZoneKey.getTimeZoneKeys().isEmpty(), + "zone-index.properties should contain at least one timezone"); + } + + @Test + public void testUtcKeyIsPresent() { + Assert.assertNotNull(TimeZoneKey.getTimeZoneKey("UTC"), + "UTC should be a recognized timezone key"); + Assert.assertEquals(TimeZoneKey.UTC_KEY.getKey(), (short) 0, + "UTC should have key 0"); + } +}