Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@
import org.junit.Ignore;
import org.junit.Test;

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;
Expand All @@ -48,7 +48,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;
Expand Down Expand Up @@ -84,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());
}
}
Expand Down Expand Up @@ -258,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) {
Expand All @@ -283,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) {
Expand Down Expand Up @@ -322,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) {
Expand All @@ -336,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) {
Expand Down Expand Up @@ -373,4 +375,84 @@ public void testConstructTimeDuration() {
timeDuration = DataNodeDateTimeUtils.constructTimeDuration("10000000000ms");
Assert.assertEquals(10000000000L, timeDuration.nonMonthDuration);
}

@Test
public void convertWinterTimeShouldUseUtcPlus1() {
ZoneId zoneId = ZoneId.of("Europe/Warsaw");
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 convertSummerTimeShouldUseUtcPlus2() {
ZoneId zoneId = ZoneId.of("Europe/Warsaw");
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 convertJustBeforeSpringDstShouldKeepWinterOffset() {
ZoneId zoneId = ZoneId.of("Europe/Warsaw");
long before = DateTimeUtils.convertDatetimeStrToLong("2024-03-31 01:59:59", zoneId, "ms");
assertEquals(ZoneOffset.ofHours(1), zoneId.getRules().getOffset(Instant.ofEpochMilli(before)));
}
Comment on lines +393 to +398
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

testToZoneOffsetDuringSpringDSTGap asserts an offset for 2024-03-31 02:30:00 in Europe/Warsaw, but this local time does not exist due to the DST spring-forward gap. The current expectation relies on how gaps are resolved by ZoneRules and can become brittle across JDK/tzdb changes. It would be more robust to either (a) use ZoneRules#getValidOffsets/transition logic in production code and assert that documented behavior here, or (b) change the test to expect a failure/explicit resolution rather than asserting a specific offset for a nonexistent local time.

Copilot uses AI. Check for mistakes.

@Test
public void convertJustAfterSpringDstShouldUseSummerOffset() {
ZoneId zoneId = ZoneId.of("Europe/Warsaw");
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 convertAutumnOverlapShouldResolveToEarlierOffset() {
ZoneId zoneId = ZoneId.of("Europe/Warsaw");
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 convertAfterAutumnTransitionShouldUseWinterOffset() {
ZoneId zoneId = ZoneId.of("Europe/Warsaw");
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 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 explicitOffsetInStringOverridesZoneId() {
ZoneId zoneId = ZoneId.of("Europe/Warsaw");
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 springGapShouldShiftForwardInWarsaw() {
ZoneId zoneId = ZoneId.of("Europe/Warsaw");
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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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()
/**
Expand Down Expand Up @@ -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);

Expand All @@ -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(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(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
Expand All @@ -583,30 +674,27 @@ 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(
"%s with [time-region] at end is not supported now, "
+ "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) {
Expand Down Expand Up @@ -662,6 +750,7 @@ public static ZonedDateTime convertToZonedDateTime(long timestamp, ZoneId zoneId
return ZonedDateTime.ofInstant(Instant.ofEpochMilli(timestamp), zoneId);
}

@Deprecated()
public static ZoneOffset toZoneOffset(ZoneId zoneId) {
return zoneId.getRules().getOffset(Instant.now());
}
Expand Down