diff --git a/processing/src/main/java/org/apache/druid/java/util/common/granularity/PeriodGranularity.java b/processing/src/main/java/org/apache/druid/java/util/common/granularity/PeriodGranularity.java index 909e80f72c95..0900050638b1 100644 --- a/processing/src/main/java/org/apache/druid/java/util/common/granularity/PeriodGranularity.java +++ b/processing/src/main/java/org/apache/druid/java/util/common/granularity/PeriodGranularity.java @@ -593,7 +593,10 @@ private long truncateMillisPeriod(final long t) { // toStandardDuration assumes days are always 24h, and hours are always 60 minutes, // which may not always be the case, e.g if there are daylight saving changes. - if (chronology.days().isPrecise() && chronology.hours().isPrecise()) { + // However, if the period only contains hours, minutes, seconds, and millis, + // their duration is always precise even across daylight saving transitions. + if ((chronology.days().isPrecise() && chronology.hours().isPrecise()) + || (period.getYears() == 0 && period.getMonths() == 0 && period.getWeeks() == 0 && period.getDays() == 0)) { final long millis = period.toStandardDuration().getMillis(); long offset = t % millis - origin % millis; if (offset < 0) { diff --git a/processing/src/test/java/org/apache/druid/java/util/common/granularity/PeriodGranularityBugTest.java b/processing/src/test/java/org/apache/druid/java/util/common/granularity/PeriodGranularityBugTest.java new file mode 100644 index 000000000000..e7d19fe3b931 --- /dev/null +++ b/processing/src/test/java/org/apache/druid/java/util/common/granularity/PeriodGranularityBugTest.java @@ -0,0 +1,47 @@ +/* + * 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.druid.java.util.common.granularity; + +import org.apache.druid.java.util.common.DateTimes; +import org.joda.time.Period; +import org.junit.Assert; +import org.junit.Test; + +public class PeriodGranularityBugTest +{ + @Test(timeout = 5000) + public void testCompoundPeriodWithTimeZone() + { + // America/New_York has DST, so days are imprecise + PeriodGranularity pg = new PeriodGranularity(new Period("PT1M1S"), null, DateTimes.inferTzFromString("America/New_York")); + long time = 1704067200000L; // 2024-01-01T00:00:00Z + long start = System.currentTimeMillis(); + long bucket = pg.bucketStart(time); + long end = System.currentTimeMillis(); + + Assert.assertTrue("Should run quickly", (end - start) < 1000); + + // Let's also check if it returns the same as a non-compound period of same duration (PT61S) + PeriodGranularity pg2 = new PeriodGranularity(new Period("PT61S"), null, DateTimes.inferTzFromString("America/New_York")); + long bucket2 = pg2.bucketStart(time); + Assert.assertEquals(bucket2, bucket); + } +} +