From 39fd8869e5daa90e719d658c0d65d8a737a31f79 Mon Sep 17 00:00:00 2001 From: Lexert19 Date: Mon, 6 Apr 2026 18:10:03 +0200 Subject: [PATCH 1/3] [IOTDB-17433] Fix timezone offset bug in DateTimeUtils --- .../iotdb/db/utils/DateTimeUtilsTest.java | 83 ++++++++++++++++++- .../queryengine/utils/DateTimeUtils.java | 20 ++++- 2 files changed, 97 insertions(+), 6 deletions(-) diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/DateTimeUtilsTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/DateTimeUtilsTest.java index 056aa7576b1e..7d3917ce8a53 100644 --- a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/DateTimeUtilsTest.java +++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/DateTimeUtilsTest.java @@ -28,12 +28,14 @@ import org.junit.Ignore; import org.junit.Test; +import java.time.DateTimeException; +import java.time.Instant; import java.time.ZoneId; import java.time.ZoneOffset; -import java.time.ZonedDateTime; import java.util.TimeZone; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; public class DateTimeUtilsTest { @@ -48,7 +50,7 @@ public class DateTimeUtilsTest { /** Test convertDatetimeStrToLong() method with different time precision. */ @Test public void convertDatetimeStrToLongTest1() { - zoneOffset = ZonedDateTime.now().getOffset(); + zoneOffset = Instant.ofEpochMilli(timestamp).atZone(ZoneId.systemDefault()).getOffset(); zoneId = ZoneId.systemDefault(); if (zoneOffset.toString().equals("Z")) { delta = 8 * 3600000; @@ -373,4 +375,81 @@ public void testConstructTimeDuration() { timeDuration = DataNodeDateTimeUtils.constructTimeDuration("10000000000ms"); Assert.assertEquals(10000000000L, timeDuration.nonMonthDuration); } + + @Test + public void testToZoneOffsetForWinterTime() { + ZoneId zoneId = ZoneId.of("Europe/Warsaw"); + ZoneOffset offset = DateTimeUtils.toZoneOffset("2024-01-15 12:00:00", zoneId); + Assert.assertEquals(ZoneOffset.ofHours(1), offset); + } + + @Test + public void testToZoneOffsetForSummerTime() { + ZoneId zoneId = ZoneId.of("Europe/Warsaw"); + ZoneOffset offset = DateTimeUtils.toZoneOffset("2024-06-15 12:00:00", zoneId); + Assert.assertEquals(ZoneOffset.ofHours(2), offset); + } + + @Test + public void testToZoneOffsetJustBeforeSpringDST() { + ZoneId zoneId = ZoneId.of("Europe/Warsaw"); + ZoneOffset offset = DateTimeUtils.toZoneOffset("2024-03-31 02:00:00", zoneId); + Assert.assertEquals(ZoneOffset.ofHours(1), offset); + } + + @Test + public void testToZoneOffsetJustAfterSpringDST() { + ZoneId zoneId = ZoneId.of("Europe/Warsaw"); + ZoneOffset offset = DateTimeUtils.toZoneOffset("2024-03-31 03:00:00", zoneId); + Assert.assertEquals(ZoneOffset.ofHours(2), offset); + } + + @Test + public void testToZoneOffsetDuringSpringDSTGap() { + ZoneId zoneId = ZoneId.of("Europe/Warsaw"); + ZoneOffset offset = DateTimeUtils.toZoneOffset("2024-03-31 02:30:00", zoneId); + Assert.assertEquals(ZoneOffset.ofHours(1), offset); + } + + @Test + public void testToZoneOffsetDuringAutumnDSTTransition() { + ZoneId zoneId = ZoneId.of("Europe/Warsaw"); + ZoneOffset offset = DateTimeUtils.toZoneOffset("2024-10-27 02:30:00", zoneId); + Assert.assertEquals(ZoneOffset.ofHours(2), offset); + } + + @Test + public void testToZoneOffsetAfterAutumnDST() { + ZoneId zoneId = ZoneId.of("Europe/Warsaw"); + ZoneOffset offset = DateTimeUtils.toZoneOffset("2024-10-27 03:00:00", zoneId); + Assert.assertEquals(ZoneOffset.ofHours(1), offset); + } + + @Test + public void testToZoneOffsetWithExplicitOffsetInString() { + ZoneId zoneId = ZoneId.of("Europe/Warsaw"); + ZoneOffset offset = DateTimeUtils.toZoneOffset("2024-06-15 12:00:00+02:00", zoneId); + Assert.assertEquals(ZoneOffset.ofHours(2), offset); + offset = DateTimeUtils.toZoneOffset("2024-06-15 12:00:00Z", zoneId); + Assert.assertEquals(ZoneOffset.UTC, offset); + offset = DateTimeUtils.toZoneOffset("2024-06-15 12:00:00-08:00", zoneId); + Assert.assertEquals(ZoneOffset.ofHours(-8), offset); + } + + @Test + public void testToZoneOffsetWithUTCZoneId() { + ZoneId utc = ZoneId.of("UTC"); + Assert.assertEquals(ZoneOffset.UTC, DateTimeUtils.toZoneOffset("2024-06-15 12:00:00", utc)); + } + + @Test + public void testToZoneOffsetWithBrokenDate() { + ZoneId zoneId = ZoneId.of("Europe/Warsaw"); + DateTimeException exception = + assertThrows( + DateTimeException.class, + () -> { + DateTimeUtils.toZoneOffset("2024-12-31 10", zoneId); + }); + } } diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/utils/DateTimeUtils.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/utils/DateTimeUtils.java index ccab91ba0a99..5f966dfe72d6 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/utils/DateTimeUtils.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/utils/DateTimeUtils.java @@ -551,14 +551,14 @@ public static long convertTimestampOrDatetimeStrToLongWithDefaultZone(String tim public static long convertDatetimeStrToLong(String str, ZoneId zoneId) { return convertDatetimeStrToLong( str, - toZoneOffset(zoneId), + toZoneOffset(str, zoneId), 0, CommonDescriptor.getInstance().getConfig().getTimestampPrecision()); } public static long convertDatetimeStrToLong( String str, ZoneId zoneId, String timestampPrecision) { - return convertDatetimeStrToLong(str, toZoneOffset(zoneId), 0, timestampPrecision); + return convertDatetimeStrToLong(str, toZoneOffset(str, zoneId), 0, timestampPrecision); } public static long getInstantWithPrecision(String str, String timestampPrecision) { @@ -662,8 +662,20 @@ public static ZonedDateTime convertToZonedDateTime(long timestamp, ZoneId zoneId return ZonedDateTime.ofInstant(Instant.ofEpochMilli(timestamp), zoneId); } - public static ZoneOffset toZoneOffset(ZoneId zoneId) { - return zoneId.getRules().getOffset(Instant.now()); + public static ZoneOffset toZoneOffset(String str, ZoneId zoneId) { + if (str.endsWith("Z")) { + return ZoneOffset.UTC; + } + + int offsetIndex = Math.max(str.lastIndexOf('+'), str.lastIndexOf('-')); + if (offsetIndex != -1 && str.length() - offsetIndex == 6) { + return ZoneOffset.of(str.substring(offsetIndex)); + } + + long millis = convertDatetimeStrToLong(str, ZoneOffset.UTC, 0, "ms"); + LocalDateTime localDateTime = + LocalDateTime.ofInstant(Instant.ofEpochMilli(millis), ZoneOffset.UTC); + return zoneId.getRules().getOffset(localDateTime); } public static ZonedDateTime convertMillsecondToZonedDateTime(long millisecond) { From a385af03e7214dd60bcfc85249188183ea5611af Mon Sep 17 00:00:00 2001 From: Lexert19 Date: Thu, 9 Apr 2026 18:03:17 +0200 Subject: [PATCH 2/3] truncate seconds in toZoneOffset for DB compatibility --- .../apache/iotdb/db/utils/DateTimeUtilsTest.java | 14 ++++++++++++++ .../commons/queryengine/utils/DateTimeUtils.java | 4 +++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/DateTimeUtilsTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/DateTimeUtilsTest.java index 7d3917ce8a53..f9a022d544a5 100644 --- a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/DateTimeUtilsTest.java +++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/DateTimeUtilsTest.java @@ -452,4 +452,18 @@ public void testToZoneOffsetWithBrokenDate() { DateTimeUtils.toZoneOffset("2024-12-31 10", zoneId); }); } + + @Test + public void testToZoneOffsetHistoricalLMT() { + ZoneId zoneId = ZoneId.of("Europe/Warsaw"); + ZoneOffset offset = DateTimeUtils.toZoneOffset("0001-01-01 00:00:00", zoneId); + Assert.assertEquals(ZoneOffset.ofHoursMinutes(1, 24), offset); + + ZoneId shanghaiId = ZoneId.of("Asia/Shanghai"); + ZoneOffset shanghaiOffset = DateTimeUtils.toZoneOffset("0001-01-01 00:00:00", shanghaiId); + Assert.assertEquals(ZoneOffset.ofHoursMinutes(8, 5), shanghaiOffset); + + ZoneId utcId = ZoneId.of("UTC"); + Assert.assertEquals(ZoneOffset.UTC, DateTimeUtils.toZoneOffset("0001-01-01 00:00:00", utcId)); + } } diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/utils/DateTimeUtils.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/utils/DateTimeUtils.java index 5f966dfe72d6..6e3212dcd1dc 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/utils/DateTimeUtils.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/utils/DateTimeUtils.java @@ -662,6 +662,7 @@ public static ZonedDateTime convertToZonedDateTime(long timestamp, ZoneId zoneId return ZonedDateTime.ofInstant(Instant.ofEpochMilli(timestamp), zoneId); } + /** Converts string to ZoneOffset. Truncates seconds for HH:mm database compatibility. */ public static ZoneOffset toZoneOffset(String str, ZoneId zoneId) { if (str.endsWith("Z")) { return ZoneOffset.UTC; @@ -675,7 +676,8 @@ public static ZoneOffset toZoneOffset(String str, ZoneId zoneId) { long millis = convertDatetimeStrToLong(str, ZoneOffset.UTC, 0, "ms"); LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(millis), ZoneOffset.UTC); - return zoneId.getRules().getOffset(localDateTime); + ZoneOffset offset = zoneId.getRules().getOffset(localDateTime); + return ZoneOffset.ofTotalSeconds((offset.getTotalSeconds() / 60) * 60); } public static ZonedDateTime convertMillsecondToZonedDateTime(long millisecond) { From caedf5e6c48412ca989be236752a012f61f4343d Mon Sep 17 00:00:00 2001 From: Lexert19 Date: Mon, 4 May 2026 19:51:58 +0200 Subject: [PATCH 3/3] Changed solution --- .../iotdb/db/utils/DateTimeUtilsTest.java | 117 +++++++-------- .../queryengine/utils/DateTimeUtils.java | 139 ++++++++++++++---- 2 files changed, 160 insertions(+), 96 deletions(-) diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/DateTimeUtilsTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/DateTimeUtilsTest.java index f9a022d544a5..b03c54e68299 100644 --- a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/DateTimeUtilsTest.java +++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/DateTimeUtilsTest.java @@ -28,14 +28,12 @@ import org.junit.Ignore; import org.junit.Test; -import java.time.DateTimeException; import java.time.Instant; import java.time.ZoneId; import java.time.ZoneOffset; import java.util.TimeZone; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; public class DateTimeUtilsTest { @@ -86,11 +84,12 @@ public void convertDatetimeStrToLongTest3() { public void convertDatetimeStrToLongTest4() { zoneOffset = ZoneOffset.UTC; try { - DateTimeUtils.convertDatetimeStrToLong("1999-02-29T00:00:00.000", zoneOffset, 0, "ms"); + DateTimeUtils.convertDatetimeStrToLong( + "1999-02-29T00:00:00.000", (ZoneId) zoneOffset, 0, "ms"); fail(); } catch (Exception e) { assertEquals( - "Text '1999-02-29T00:00:00.000+00:00' could not be parsed: Invalid date 'February 29' as '1999' is not a leap year", + "Text '1999-02-29T00:00:00.000' could not be parsed: Invalid date 'February 29' as '1999' is not a leap year", e.getMessage()); } } @@ -260,7 +259,8 @@ public void testConvertDatetimeStrToLongWithoutMS( "2019.01.02T15:13:27" + zoneOffset, }; for (String str : timeFormatWithoutMs) { - Assert.assertEquals(res, DateTimeUtils.convertDatetimeStrToLong(str, zoneOffset, 0, "ms")); + Assert.assertEquals( + res, DateTimeUtils.convertDatetimeStrToLong(str, (ZoneId) zoneOffset, 0, "ms")); } for (String str : timeFormatWithoutMs) { @@ -285,7 +285,7 @@ public void testConvertDatetimeStrToLongWithMS(ZoneOffset zoneOffset, ZoneId zon "2019.01.02T15:13:27.689" + zoneOffset, }; for (String str : timeFormatWithoutMs) { - assertEquals(res, DateTimeUtils.convertDatetimeStrToLong(str, zoneOffset, 0, "ms")); + assertEquals(res, DateTimeUtils.convertDatetimeStrToLong(str, (ZoneId) zoneOffset, 0, "ms")); } for (String str : timeFormatWithoutMs) { @@ -324,7 +324,7 @@ public void testConvertDatetimeStrToLongWithMS2(ZoneOffset zoneOffset, ZoneId zo "2019.01.02T15:13:27.68" + zoneOffset, }; for (String str : timeFormatWithoutMs) { - assertEquals(res, DateTimeUtils.convertDatetimeStrToLong(str, zoneOffset, 0, "ms")); + assertEquals(res, DateTimeUtils.convertDatetimeStrToLong(str, (ZoneId) zoneOffset, 0, "ms")); } for (String str : timeFormatWithoutMs) { @@ -338,7 +338,7 @@ public void testConvertDateStrToLong(ZoneOffset zoneOffset, ZoneId zoneId, long "2019-01-02", "2019/01/02", "2019.01.02", }; for (String str : timeFormatWithoutMs) { - assertEquals(res, DateTimeUtils.convertDatetimeStrToLong(str, zoneOffset, 0, "ms")); + assertEquals(res, DateTimeUtils.convertDatetimeStrToLong(str, (ZoneId) zoneOffset, 0, "ms")); } for (String str : timeFormatWithoutMs) { @@ -377,93 +377,82 @@ public void testConstructTimeDuration() { } @Test - public void testToZoneOffsetForWinterTime() { + public void convertWinterTimeShouldUseUtcPlus1() { ZoneId zoneId = ZoneId.of("Europe/Warsaw"); - ZoneOffset offset = DateTimeUtils.toZoneOffset("2024-01-15 12:00:00", zoneId); - Assert.assertEquals(ZoneOffset.ofHours(1), offset); + long winter = DateTimeUtils.convertDatetimeStrToLong("2024-01-15 12:00:00", zoneId, "ms"); + assertEquals(ZoneOffset.ofHours(1), zoneId.getRules().getOffset(Instant.ofEpochMilli(winter))); } @Test - public void testToZoneOffsetForSummerTime() { + public void convertSummerTimeShouldUseUtcPlus2() { ZoneId zoneId = ZoneId.of("Europe/Warsaw"); - ZoneOffset offset = DateTimeUtils.toZoneOffset("2024-06-15 12:00:00", zoneId); - Assert.assertEquals(ZoneOffset.ofHours(2), offset); + long summer = DateTimeUtils.convertDatetimeStrToLong("2024-06-15 12:00:00", zoneId, "ms"); + assertEquals(ZoneOffset.ofHours(2), zoneId.getRules().getOffset(Instant.ofEpochMilli(summer))); } @Test - public void testToZoneOffsetJustBeforeSpringDST() { + public void convertJustBeforeSpringDstShouldKeepWinterOffset() { ZoneId zoneId = ZoneId.of("Europe/Warsaw"); - ZoneOffset offset = DateTimeUtils.toZoneOffset("2024-03-31 02:00:00", zoneId); - Assert.assertEquals(ZoneOffset.ofHours(1), offset); + long before = DateTimeUtils.convertDatetimeStrToLong("2024-03-31 01:59:59", zoneId, "ms"); + assertEquals(ZoneOffset.ofHours(1), zoneId.getRules().getOffset(Instant.ofEpochMilli(before))); } @Test - public void testToZoneOffsetJustAfterSpringDST() { + public void convertJustAfterSpringDstShouldUseSummerOffset() { ZoneId zoneId = ZoneId.of("Europe/Warsaw"); - ZoneOffset offset = DateTimeUtils.toZoneOffset("2024-03-31 03:00:00", zoneId); - Assert.assertEquals(ZoneOffset.ofHours(2), offset); + long after = DateTimeUtils.convertDatetimeStrToLong("2024-03-31 03:00:00", zoneId, "ms"); + assertEquals(ZoneOffset.ofHours(2), zoneId.getRules().getOffset(Instant.ofEpochMilli(after))); } @Test - public void testToZoneOffsetDuringSpringDSTGap() { + public void convertAutumnOverlapShouldResolveToEarlierOffset() { ZoneId zoneId = ZoneId.of("Europe/Warsaw"); - ZoneOffset offset = DateTimeUtils.toZoneOffset("2024-03-31 02:30:00", zoneId); - Assert.assertEquals(ZoneOffset.ofHours(1), offset); + long overlap = DateTimeUtils.convertDatetimeStrToLong("2024-10-27 02:30:00", zoneId, "ms"); + assertEquals(ZoneOffset.ofHours(2), zoneId.getRules().getOffset(Instant.ofEpochMilli(overlap))); } @Test - public void testToZoneOffsetDuringAutumnDSTTransition() { + public void convertAfterAutumnTransitionShouldUseWinterOffset() { ZoneId zoneId = ZoneId.of("Europe/Warsaw"); - ZoneOffset offset = DateTimeUtils.toZoneOffset("2024-10-27 02:30:00", zoneId); - Assert.assertEquals(ZoneOffset.ofHours(2), offset); + long after = DateTimeUtils.convertDatetimeStrToLong("2024-10-27 03:00:00", zoneId, "ms"); + assertEquals(ZoneOffset.ofHours(1), zoneId.getRules().getOffset(Instant.ofEpochMilli(after))); } @Test - public void testToZoneOffsetAfterAutumnDST() { - ZoneId zoneId = ZoneId.of("Europe/Warsaw"); - ZoneOffset offset = DateTimeUtils.toZoneOffset("2024-10-27 03:00:00", zoneId); - Assert.assertEquals(ZoneOffset.ofHours(1), offset); - } - - @Test - public void testToZoneOffsetWithExplicitOffsetInString() { - ZoneId zoneId = ZoneId.of("Europe/Warsaw"); - ZoneOffset offset = DateTimeUtils.toZoneOffset("2024-06-15 12:00:00+02:00", zoneId); - Assert.assertEquals(ZoneOffset.ofHours(2), offset); - offset = DateTimeUtils.toZoneOffset("2024-06-15 12:00:00Z", zoneId); - Assert.assertEquals(ZoneOffset.UTC, offset); - offset = DateTimeUtils.toZoneOffset("2024-06-15 12:00:00-08:00", zoneId); - Assert.assertEquals(ZoneOffset.ofHours(-8), offset); - } - - @Test - public void testToZoneOffsetWithUTCZoneId() { - ZoneId utc = ZoneId.of("UTC"); - Assert.assertEquals(ZoneOffset.UTC, DateTimeUtils.toZoneOffset("2024-06-15 12:00:00", utc)); + public void historicalDateBeforeStandardizedOffsetShouldUseLMT() { + ZoneId shanghaiId = ZoneId.of("Asia/Shanghai"); + long oldTime = DateTimeUtils.convertDatetimeStrToLong("1900-01-01 00:00:00", shanghaiId, "ms"); + ZoneOffset offset = shanghaiId.getRules().getOffset(Instant.ofEpochMilli(oldTime)); + assertEquals(8 * 3600 + 5 * 60 + 43, offset.getTotalSeconds()); } @Test - public void testToZoneOffsetWithBrokenDate() { + public void explicitOffsetInStringOverridesZoneId() { ZoneId zoneId = ZoneId.of("Europe/Warsaw"); - DateTimeException exception = - assertThrows( - DateTimeException.class, - () -> { - DateTimeUtils.toZoneOffset("2024-12-31 10", zoneId); - }); + long withMs = DateTimeUtils.convertDatetimeStrToLong("2024-06-15 12:00:00Z", zoneId, "ms"); + long withoutMs = DateTimeUtils.convertDatetimeStrToLong("2024-06-15 14:00:00", zoneId, "ms"); + assertEquals(withoutMs, withMs); + + long withUs = + DateTimeUtils.convertDatetimeStrToLong("2024-06-15 12:00:00.123456Z", zoneId, "us"); + long withoutUs = + DateTimeUtils.convertDatetimeStrToLong("2024-06-15 14:00:00.123456", zoneId, "us"); + assertEquals(withoutUs, withUs); + + long withNs = + DateTimeUtils.convertDatetimeStrToLong("2024-06-15 12:00:00.123456789Z", zoneId, "ns"); + long withoutNs = + DateTimeUtils.convertDatetimeStrToLong("2024-06-15 14:00:00.123456789", zoneId, "ns"); + assertEquals(withoutNs, withNs); } @Test - public void testToZoneOffsetHistoricalLMT() { + public void springGapShouldShiftForwardInWarsaw() { ZoneId zoneId = ZoneId.of("Europe/Warsaw"); - ZoneOffset offset = DateTimeUtils.toZoneOffset("0001-01-01 00:00:00", zoneId); - Assert.assertEquals(ZoneOffset.ofHoursMinutes(1, 24), offset); - - ZoneId shanghaiId = ZoneId.of("Asia/Shanghai"); - ZoneOffset shanghaiOffset = DateTimeUtils.toZoneOffset("0001-01-01 00:00:00", shanghaiId); - Assert.assertEquals(ZoneOffset.ofHoursMinutes(8, 5), shanghaiOffset); - - ZoneId utcId = ZoneId.of("UTC"); - Assert.assertEquals(ZoneOffset.UTC, DateTimeUtils.toZoneOffset("0001-01-01 00:00:00", utcId)); + long gap = DateTimeUtils.convertDatetimeStrToLong("2024-03-31 02:30:00", zoneId, "ms"); + assertEquals(ZoneOffset.ofHours(2), zoneId.getRules().getOffset(Instant.ofEpochMilli(gap))); + assertEquals( + "2024-03-31T03:30", + Instant.ofEpochMilli(gap).atZone(zoneId).toLocalDateTime().toString().substring(0, 16)); } } diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/utils/DateTimeUtils.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/utils/DateTimeUtils.java index 6e3212dcd1dc..7b7cc4b61449 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/utils/DateTimeUtils.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/utils/DateTimeUtils.java @@ -28,6 +28,7 @@ import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.OffsetDateTime; import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; @@ -36,6 +37,7 @@ import java.time.format.ResolverStyle; import java.time.format.SignStyle; import java.time.temporal.ChronoField; +import java.time.temporal.TemporalAccessor; import java.util.TimeZone; import java.util.concurrent.TimeUnit; import java.util.function.Function; @@ -479,6 +481,57 @@ public static void initTimestampPrecision() { .toFormatter(); } + private static final DateTimeFormatter ISO_LOCAL_DATE_TIME_WITH_NS_T; + private static final DateTimeFormatter ISO_LOCAL_DATE_TIME_WITH_SLASH_NS_T; + private static final DateTimeFormatter ISO_LOCAL_DATE_TIME_WITH_DOT_NS_T; + private static final DateTimeFormatter ISO_LOCAL_DATE_TIME_WITH_NS_SPACE; + private static final DateTimeFormatter ISO_LOCAL_DATE_TIME_WITH_SLASH_NS_SPACE; + private static final DateTimeFormatter ISO_LOCAL_DATE_TIME_WITH_DOT_NS_SPACE; + + static { + ISO_LOCAL_DATE_TIME_WITH_NS_T = + new DateTimeFormatterBuilder() + .append(ISO_LOCAL_DATE_WIDTH_1_2) + .appendLiteral('T') + .append(ISO_LOCAL_TIME_WITH_NS) + .toFormatter(); + + ISO_LOCAL_DATE_TIME_WITH_SLASH_NS_T = + new DateTimeFormatterBuilder() + .append(ISO_LOCAL_DATE_WITH_SLASH) + .appendLiteral('T') + .append(ISO_LOCAL_TIME_WITH_NS) + .toFormatter(); + + ISO_LOCAL_DATE_TIME_WITH_DOT_NS_T = + new DateTimeFormatterBuilder() + .append(ISO_LOCAL_DATE_WITH_DOT) + .appendLiteral('T') + .append(ISO_LOCAL_TIME_WITH_NS) + .toFormatter(); + + ISO_LOCAL_DATE_TIME_WITH_NS_SPACE = + new DateTimeFormatterBuilder() + .append(ISO_LOCAL_DATE_WIDTH_1_2) + .appendLiteral(' ') + .append(ISO_LOCAL_TIME_WITH_NS) + .toFormatter(); + + ISO_LOCAL_DATE_TIME_WITH_SLASH_NS_SPACE = + new DateTimeFormatterBuilder() + .append(ISO_LOCAL_DATE_WITH_SLASH) + .appendLiteral(' ') + .append(ISO_LOCAL_TIME_WITH_NS) + .toFormatter(); + + ISO_LOCAL_DATE_TIME_WITH_DOT_NS_SPACE = + new DateTimeFormatterBuilder() + .append(ISO_LOCAL_DATE_WITH_DOT) + .appendLiteral(' ') + .append(ISO_LOCAL_TIME_WITH_NS) + .toFormatter(); + } + public static final DateTimeFormatter formatter = new DateTimeFormatterBuilder() /** @@ -537,6 +590,12 @@ public static void initTimestampPrecision() { /** such as '2011.12.03 10:15:30+01:00' or '2011.12.03 10:15:30.123456789+01:00'. */ .appendOptional(ISO_OFFSET_DATE_TIME_WITH_DOT_WITH_SPACE_NS) + .appendOptional(ISO_LOCAL_DATE_TIME_WITH_NS_T) + .appendOptional(ISO_LOCAL_DATE_TIME_WITH_SLASH_NS_T) + .appendOptional(ISO_LOCAL_DATE_TIME_WITH_DOT_NS_T) + .appendOptional(ISO_LOCAL_DATE_TIME_WITH_NS_SPACE) + .appendOptional(ISO_LOCAL_DATE_TIME_WITH_SLASH_NS_SPACE) + .appendOptional(ISO_LOCAL_DATE_TIME_WITH_DOT_NS_SPACE) .toFormatter() .withResolverStyle(ResolverStyle.STRICT); @@ -548,22 +607,54 @@ public static long convertTimestampOrDatetimeStrToLongWithDefaultZone(String tim } } + /** + * @deprecated Use {@link #convertDatetimeStrToLong(String, ZoneId, String)} instead. + */ + @Deprecated + public static long convertDatetimeStrToLong( + String str, ZoneOffset offset, String timestampPrecision) { + return convertDatetimeStrToLong(str, (ZoneId) offset, 0, timestampPrecision); + } + + /** + * @deprecated Use {@link #convertDatetimeStrToLong(String, ZoneId, int, String)} instead. + */ + @Deprecated + public static long convertDatetimeStrToLong( + String str, ZoneOffset offset, int depth, String timestampPrecision) { + return convertDatetimeStrToLong(str, (ZoneId) offset, depth, timestampPrecision); + } + public static long convertDatetimeStrToLong(String str, ZoneId zoneId) { return convertDatetimeStrToLong( - str, - toZoneOffset(str, zoneId), - 0, - CommonDescriptor.getInstance().getConfig().getTimestampPrecision()); + str, zoneId, 0, CommonDescriptor.getInstance().getConfig().getTimestampPrecision()); } public static long convertDatetimeStrToLong( String str, ZoneId zoneId, String timestampPrecision) { - return convertDatetimeStrToLong(str, toZoneOffset(str, zoneId), 0, timestampPrecision); + return convertDatetimeStrToLong(str, zoneId, 0, timestampPrecision); } + /** + * @deprecated Use {@link #getInstantWithPrecision(String, String, ZoneId)} instead. + */ + @Deprecated public static long getInstantWithPrecision(String str, String timestampPrecision) { - ZonedDateTime zonedDateTime = ZonedDateTime.parse(str, formatter); - Instant instant = zonedDateTime.toInstant(); + return getInstantWithPrecision(str, timestampPrecision, ZoneId.systemDefault()); + } + + public static long getInstantWithPrecision(String str, String timestampPrecision, ZoneId zoneId) { + TemporalAccessor parsed = formatter.parseBest(str, OffsetDateTime::from, LocalDateTime::from); + Instant instant; + if (parsed instanceof OffsetDateTime) { + instant = ((OffsetDateTime) parsed).toInstant(); + } else { + instant = ((LocalDateTime) parsed).atZone(zoneId).toInstant(); + } + return getInstantWithPrecision(instant, timestampPrecision); + } + + public static long getInstantWithPrecision(Instant instant, String timestampPrecision) { if ("us".equals(timestampPrecision) || "microsecond".equals(timestampPrecision)) { if (instant.getEpochSecond() < 0 && instant.getNano() > 0) { // adjustment can reduce the loss of the division @@ -583,22 +674,19 @@ public static long getInstantWithPrecision(String str, String timestampPrecision /** convert date time string to millisecond, microsecond or nanosecond. */ public static long convertDatetimeStrToLong( - String str, ZoneOffset offset, int depth, String timestampPrecision) { + String str, ZoneId zoneId, int depth, String timestampPrecision) { if (depth >= 2) { throw new DateTimeException( String.format( - "Failed to convert %s to millisecond, zone offset is %s, " + "Failed to convert %s to millisecond, zoneId is %s, " + "please input like 2011-12-03T10:15:30 or 2011-12-03T10:15:30+01:00", - str, offset)); + str, zoneId)); } if (str.contains("Z")) { return convertDatetimeStrToLong( - str.substring(0, str.indexOf('Z')) + "+00:00", offset, depth, timestampPrecision); + str.substring(0, str.indexOf('Z')) + "+00:00", zoneId, depth, timestampPrecision); } else if (str.length() == 10) { - return convertDatetimeStrToLong(str + "T00:00:00", offset, depth, timestampPrecision); - } else if (str.length() - str.lastIndexOf('+') != 6 - && str.length() - str.lastIndexOf('-') != 6) { - return convertDatetimeStrToLong(str + offset, offset, depth + 1, timestampPrecision); + return convertDatetimeStrToLong(str + "T00:00:00", zoneId, depth, timestampPrecision); } else if (str.contains("[") || str.contains("]")) { throw new DateTimeException( String.format( @@ -606,7 +694,7 @@ public static long convertDatetimeStrToLong( + "please input like 2011-12-03T10:15:30 or 2011-12-03T10:15:30+01:00", str)); } - return getInstantWithPrecision(str, timestampPrecision); + return getInstantWithPrecision(str, timestampPrecision, zoneId); } public static TimeUnit timestampPrecisionStringToTimeUnit(String timestampPrecision) { @@ -662,22 +750,9 @@ public static ZonedDateTime convertToZonedDateTime(long timestamp, ZoneId zoneId return ZonedDateTime.ofInstant(Instant.ofEpochMilli(timestamp), zoneId); } - /** Converts string to ZoneOffset. Truncates seconds for HH:mm database compatibility. */ - public static ZoneOffset toZoneOffset(String str, ZoneId zoneId) { - if (str.endsWith("Z")) { - return ZoneOffset.UTC; - } - - int offsetIndex = Math.max(str.lastIndexOf('+'), str.lastIndexOf('-')); - if (offsetIndex != -1 && str.length() - offsetIndex == 6) { - return ZoneOffset.of(str.substring(offsetIndex)); - } - - long millis = convertDatetimeStrToLong(str, ZoneOffset.UTC, 0, "ms"); - LocalDateTime localDateTime = - LocalDateTime.ofInstant(Instant.ofEpochMilli(millis), ZoneOffset.UTC); - ZoneOffset offset = zoneId.getRules().getOffset(localDateTime); - return ZoneOffset.ofTotalSeconds((offset.getTotalSeconds() / 60) * 60); + @Deprecated() + public static ZoneOffset toZoneOffset(ZoneId zoneId) { + return zoneId.getRules().getOffset(Instant.now()); } public static ZonedDateTime convertMillsecondToZonedDateTime(long millisecond) {