解析 RFC 3339 日期时间时不考虑区域调整
Zone adjustment not taken into account when parsing RFC 3339 date time
(迁移自 CodeReview)
我正在试验并试图更好地理解 Java 时间。我的代码没有按预期工作,我想问问原因,可能是因为我对 JSR-310 时区处理有误解。
我有一个要用 DateTimeFormatter
解析的时间戳字符串
String text = "2019-08-28T10:39:57+02:00";
DateTimeFormatter ISO_RFC_3339_PARSER = DateTimeFormatter.ISO_OFFSET_DATE_TIME.withZone(ZoneId.of("Europe/Paris"));
以上值是当服务器的本地时间为中欧10:39上午时生成的。由于夏令时的原因,UTC 时间是 8:39 早上。您可以自行计算找出您所在区域的时间。
当计算机的时区与时间戳中显示的时区相同时,以下测试有效(+2)
@Test
public void testTimestamp()
{
LocalDateTime time = ZonedDateTime.from(ISO_RFC_3339_PARSER.parse(text)).toLocalDateTime();
errorCollector.checkThat(time.getYear(), is(2019));
errorCollector.checkThat(time.getMonthValue(), is(8));
errorCollector.checkThat(time.getDayOfMonth(), is(28));
errorCollector.checkThat(time.getHour(), is(10));
errorCollector.checkThat(time.getMinute(), is(39));
errorCollector.checkThat(time.getSecond(), is(57));
}
我已经尝试 修补 输入字符串以更好地理解 Java 时间如何调整时区。但是结果却大错特错!
我试图反复更改输入字符串中的时区,因此测试应该会失败。例如,如果我将字符串更改为 2019-08-28T10:39:57+08:00
,则表示现在是巴黎凌晨 2 点。但是上面的测试代码在检查一天中的小时时继续通过。 IE。结果 LocalDateTime
当天的时间仍然是上午 10 点。
问题
为什么我的代码仍然返回 10 作为 local 一天中的时间,而不管我不断更改源字符串中的时区这一事实?
那么解析 RFC 3339 字符串(表示嵌入偏移量中的一个瞬间)并将其转换为可能的区域调整的 LocalDateTime
对象的正确方法是什么?假设机器在CE[S]T时区运行.
环境
我需要将一个时间戳与计算机时间进行比较,看看它是不是太旧了。这意味着如果在欧洲评估美国时间,它可能不会“太旧”。
String text = "2019-08-28T10:39:57+02:00";
OffsetDateTime time = OffsetDateTime.parse(text);
System.out.println("Hour of day is " + time.getHour());
输出是(如您所料):
Hour of day is 10
尝试不同的偏移量:
String text = "2019-08-28T10:39:57+08:00";
Hour of day is 10
所以还是一样,仍然是字符串中给定的小时。这就是 10 的来源。我从你的代码中选择了一个稍微简单的代码示例,但它演示的行为与你的代码相同,没有区别,也足以解释。
需要注意的一点是代码不依赖于 JVM 的时区设置(您报告为 Europe/Paris)。那挺好的。这意味着如果我们相信代码是正确的,我们也可以相信它 运行 在所有时区的所有 JVM 上都是正确的。
在您的代码中,您通过调用 toLocalDateTime()
转换为 LocalDateTime
。由于 OffsetDateTime
或 ZonedDateTime
很好地实现了目的,因此这种转换通常没有任何充分的理由。转换的作用是保留 OffsetDateTime
或 ZonedDateTime
中的日期和小时(挂钟时间)并丢弃偏移量或时区信息。因此,如果一天中的小时在转换前为 10,则转换后也将为 10。造成您混淆的一个可能原因是您是否期望转换为 您的 当地时间 (Europe/Paris)。不执行此类转换。 java.time names 中的 Local
表示:没有时区或偏移量。有人认为使用 Local
这个词来表达这个意思是误导(它是历史性的,因为它是从 Joda-Time 接管的,运行 是 java.time).
Then what is the correct way to parse a RFC 3339 string (which
represents an instant in the embedded offset) and convert it into a
LocalDateTime
object with regards of possible zone adjustments?
Assume the machine is running in CE[S]T time zone.
解析已经为您正常工作。您可能不需要转换。你可以直接比较。 OffsetDateTime
和 ZonedDateTime
的 isBefore
、isEqual
和 isAfter
方法在比较时会考虑不同的偏移量或时区,并会为您提供所需的结果.如果你确实想转换:
String text = "2019-08-28T10:39:57+08:00";
OffsetDateTime time = OffsetDateTime.parse(text);
System.out.println("Hour of day is " + time.getHour());
ZoneId myTimeZone = ZoneId.of("Europe/Paris");
ZonedDateTime timeInFrance = time.atZoneSameInstant(myTimeZone);
System.out.println("Hour of day in France is " + timeInFrance.getHour());
Hour of day is 10
Hour of day in France is 4
同样,您可以转换为 LocalDateTime
并保留 4 小时,但我不明白您为什么要这样做。
关于我对您的代码的简化:您的 RFC 3339 字符串包含与 UTC (+02:00) 的偏移量,而不是时区(如 Europe/Paris 或 Europe/Rome)。所以使用 ZonedDateTime
来解析它是矫枉过正的; OffsetDateTime
更合适。字符串格式符合 ISO 8601。java.time 类 无需任何显式格式化程序即可开箱即用地解析最常见的 ISO 8601 格式变体,因此我们在这里不需要格式化程序。顺便说一句,您分配给格式化程序的区域没有任何区别,因为使用了字符串中的偏移量。
从 ZonedDateTime
得到 LocalDateTime
只是削减了时区。它的价值无关紧要。您需要做的是将您的 ZonedDateTime
从 +8 区转换为 +2 区,这将相应地更改时间。然后你可以从你修改的 ZonedDateTime
中得到 LocalDateTime
以获得想要的效果。这是一个演示它的例子:
private static void testDateParsing() {
ZonedDateTime zdt = ZonedDateTime.parse("2019-08-28T10:39:57+08:00", DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZZZZZ"));
ZonedDateTime modifiedZdt = zdt.withZoneSameInstant(ZoneId.systemDefault());
System.out.println(zdt);
System.out.println(modifiedZdt);
System.out.println(LocalDateTime.from(zdt));
System.out.println(LocalDateTime.from(modifiedZdt));
}
输出为:
2019-08-28T10:39:57+08:00
2019-08-28T05:39:57+03:00[Asia/Jerusalem]
2019-08-28T10:39:57
2019-08-28T05:39:57
请注意,我的本地时区是 +03,而不是你的 +02。
(迁移自 CodeReview)
我正在试验并试图更好地理解 Java 时间。我的代码没有按预期工作,我想问问原因,可能是因为我对 JSR-310 时区处理有误解。
我有一个要用 DateTimeFormatter
String text = "2019-08-28T10:39:57+02:00";
DateTimeFormatter ISO_RFC_3339_PARSER = DateTimeFormatter.ISO_OFFSET_DATE_TIME.withZone(ZoneId.of("Europe/Paris"));
以上值是当服务器的本地时间为中欧10:39上午时生成的。由于夏令时的原因,UTC 时间是 8:39 早上。您可以自行计算找出您所在区域的时间。
当计算机的时区与时间戳中显示的时区相同时,以下测试有效(+2)
@Test
public void testTimestamp()
{
LocalDateTime time = ZonedDateTime.from(ISO_RFC_3339_PARSER.parse(text)).toLocalDateTime();
errorCollector.checkThat(time.getYear(), is(2019));
errorCollector.checkThat(time.getMonthValue(), is(8));
errorCollector.checkThat(time.getDayOfMonth(), is(28));
errorCollector.checkThat(time.getHour(), is(10));
errorCollector.checkThat(time.getMinute(), is(39));
errorCollector.checkThat(time.getSecond(), is(57));
}
我已经尝试 修补 输入字符串以更好地理解 Java 时间如何调整时区。但是结果却大错特错!
我试图反复更改输入字符串中的时区,因此测试应该会失败。例如,如果我将字符串更改为 2019-08-28T10:39:57+08:00
,则表示现在是巴黎凌晨 2 点。但是上面的测试代码在检查一天中的小时时继续通过。 IE。结果 LocalDateTime
当天的时间仍然是上午 10 点。
问题
为什么我的代码仍然返回 10 作为 local 一天中的时间,而不管我不断更改源字符串中的时区这一事实?
那么解析 RFC 3339 字符串(表示嵌入偏移量中的一个瞬间)并将其转换为可能的区域调整的 LocalDateTime
对象的正确方法是什么?假设机器在CE[S]T时区运行.
环境
我需要将一个时间戳与计算机时间进行比较,看看它是不是太旧了。这意味着如果在欧洲评估美国时间,它可能不会“太旧”。
String text = "2019-08-28T10:39:57+02:00";
OffsetDateTime time = OffsetDateTime.parse(text);
System.out.println("Hour of day is " + time.getHour());
输出是(如您所料):
Hour of day is 10
尝试不同的偏移量:
String text = "2019-08-28T10:39:57+08:00";
Hour of day is 10
所以还是一样,仍然是字符串中给定的小时。这就是 10 的来源。我从你的代码中选择了一个稍微简单的代码示例,但它演示的行为与你的代码相同,没有区别,也足以解释。
需要注意的一点是代码不依赖于 JVM 的时区设置(您报告为 Europe/Paris)。那挺好的。这意味着如果我们相信代码是正确的,我们也可以相信它 运行 在所有时区的所有 JVM 上都是正确的。
在您的代码中,您通过调用 toLocalDateTime()
转换为 LocalDateTime
。由于 OffsetDateTime
或 ZonedDateTime
很好地实现了目的,因此这种转换通常没有任何充分的理由。转换的作用是保留 OffsetDateTime
或 ZonedDateTime
中的日期和小时(挂钟时间)并丢弃偏移量或时区信息。因此,如果一天中的小时在转换前为 10,则转换后也将为 10。造成您混淆的一个可能原因是您是否期望转换为 您的 当地时间 (Europe/Paris)。不执行此类转换。 java.time names 中的 Local
表示:没有时区或偏移量。有人认为使用 Local
这个词来表达这个意思是误导(它是历史性的,因为它是从 Joda-Time 接管的,运行 是 java.time).
Then what is the correct way to parse a RFC 3339 string (which represents an instant in the embedded offset) and convert it into a
LocalDateTime
object with regards of possible zone adjustments? Assume the machine is running in CE[S]T time zone.
解析已经为您正常工作。您可能不需要转换。你可以直接比较。 OffsetDateTime
和 ZonedDateTime
的 isBefore
、isEqual
和 isAfter
方法在比较时会考虑不同的偏移量或时区,并会为您提供所需的结果.如果你确实想转换:
String text = "2019-08-28T10:39:57+08:00";
OffsetDateTime time = OffsetDateTime.parse(text);
System.out.println("Hour of day is " + time.getHour());
ZoneId myTimeZone = ZoneId.of("Europe/Paris");
ZonedDateTime timeInFrance = time.atZoneSameInstant(myTimeZone);
System.out.println("Hour of day in France is " + timeInFrance.getHour());
Hour of day is 10 Hour of day in France is 4
同样,您可以转换为 LocalDateTime
并保留 4 小时,但我不明白您为什么要这样做。
关于我对您的代码的简化:您的 RFC 3339 字符串包含与 UTC (+02:00) 的偏移量,而不是时区(如 Europe/Paris 或 Europe/Rome)。所以使用 ZonedDateTime
来解析它是矫枉过正的; OffsetDateTime
更合适。字符串格式符合 ISO 8601。java.time 类 无需任何显式格式化程序即可开箱即用地解析最常见的 ISO 8601 格式变体,因此我们在这里不需要格式化程序。顺便说一句,您分配给格式化程序的区域没有任何区别,因为使用了字符串中的偏移量。
从 ZonedDateTime
得到 LocalDateTime
只是削减了时区。它的价值无关紧要。您需要做的是将您的 ZonedDateTime
从 +8 区转换为 +2 区,这将相应地更改时间。然后你可以从你修改的 ZonedDateTime
中得到 LocalDateTime
以获得想要的效果。这是一个演示它的例子:
private static void testDateParsing() {
ZonedDateTime zdt = ZonedDateTime.parse("2019-08-28T10:39:57+08:00", DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZZZZZ"));
ZonedDateTime modifiedZdt = zdt.withZoneSameInstant(ZoneId.systemDefault());
System.out.println(zdt);
System.out.println(modifiedZdt);
System.out.println(LocalDateTime.from(zdt));
System.out.println(LocalDateTime.from(modifiedZdt));
}
输出为:
2019-08-28T10:39:57+08:00
2019-08-28T05:39:57+03:00[Asia/Jerusalem]
2019-08-28T10:39:57
2019-08-28T05:39:57
请注意,我的本地时区是 +03,而不是你的 +02。