diff --git a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/DateTimeFunctions.java b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/DateTimeFunctions.java index 47f849e28ba5..cbd3d2e39ceb 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/DateTimeFunctions.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/DateTimeFunctions.java @@ -325,7 +325,16 @@ public static long ago(String periodString) { */ @ScalarFunction public static int timezoneHour(String timezoneId) { - return new DateTime(DateTimeZone.forID(timezoneId).getOffset(null), DateTimeZone.UTC).getHourOfDay(); + return timezoneHour(timezoneId, 0); + } + + /** + * Returns the hour of the time zone offset, for the UTC timestamp at {@code millis}. This will + * properly handle daylight savings time. + */ + @ScalarFunction + public static int timezoneHour(String timezoneId, long millis) { + return (int) TimeUnit.MILLISECONDS.toHours(DateTimeZone.forID(timezoneId).getOffset(millis)); } /** @@ -333,7 +342,16 @@ public static int timezoneHour(String timezoneId) { */ @ScalarFunction public static int timezoneMinute(String timezoneId) { - return new DateTime(DateTimeZone.forID(timezoneId).getOffset(null), DateTimeZone.UTC).getMinuteOfHour(); + return timezoneMinute(timezoneId, 0); + } + + /** + * Returns the minute of the time zone offset, for the UTC timestamp at {@code millis}. This will + * properly handle daylight savings time + */ + @ScalarFunction + public static int timezoneMinute(String timezoneId, long millis) { + return (int) TimeUnit.MILLISECONDS.toMinutes(DateTimeZone.forID(timezoneId).getOffset(millis)) % 60; } /** diff --git a/pinot-core/src/test/java/org/apache/pinot/core/data/function/DateTimeFunctionsTest.java b/pinot-core/src/test/java/org/apache/pinot/core/data/function/DateTimeFunctionsTest.java index b746b570c2b5..ad24fefcc957 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/data/function/DateTimeFunctionsTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/data/function/DateTimeFunctionsTest.java @@ -255,19 +255,32 @@ public Object[][] dateTimeFunctionsDataProvider() { GenericRow row122 = new GenericRow(); row122.putValue("tz", "Pacific/Marquesas"); - inputs.add(new Object[]{"timezone_hour(tz)", expectedArguments, row122, 14}); - inputs.add(new Object[]{"timezone_minute(tz)", expectedArguments, row122, 30}); + inputs.add(new Object[]{"timezone_hour(tz)", expectedArguments, row122, -9}); + inputs.add(new Object[]{"timezone_minute(tz)", expectedArguments, row122, -30}); GenericRow row123 = new GenericRow(); row123.putValue("tz", "Etc/GMT+12"); - inputs.add(new Object[]{"timezone_hour(tz)", expectedArguments, row123, 12}); + inputs.add(new Object[]{"timezone_hour(tz)", expectedArguments, row123, -12}); inputs.add(new Object[]{"timezone_minute(tz)", expectedArguments, row123, 0}); GenericRow row124 = new GenericRow(); row124.putValue("tz", "Etc/GMT+1"); - inputs.add(new Object[]{"timezone_hour(tz)", expectedArguments, row124, 23}); + inputs.add(new Object[]{"timezone_hour(tz)", expectedArguments, row124, -1}); inputs.add(new Object[]{"timezone_minute(tz)", expectedArguments, row124, 0}); + GenericRow row125 = new GenericRow(); + row125.putValue("tz", "America/Toronto"); + inputs.add(new Object[]{"timezone_hour(tz)", expectedArguments, row125, -5}); + inputs.add(new Object[]{"timezone_minute(tz)", expectedArguments, row125, 0}); + + // standard time (2022-01-01 6:23:01) + inputs.add(new Object[]{"timezone_hour(tz, 1641046981000)", expectedArguments, row125, -5}); + inputs.add(new Object[]{"timezone_minute(tz, 1641046981000)", expectedArguments, row125, 0}); + + // daylight savings time (2022-07-01 6:23:01) + inputs.add(new Object[]{"timezone_hour(tz, 1656685381000)", expectedArguments, row125, -4}); + inputs.add(new Object[]{"timezone_minute(tz, 1656685381000)", expectedArguments, row125, 0}); + // Convenience extraction functions expectedArguments = Collections.singletonList("millis"); GenericRow row130 = new GenericRow();