DST 更改导致 java.text.ParseException:无法解析的日期

DST changes caused an java.text.ParseException: Unparseable date

以下是抛出异常的代码片段:

SimpleDateformat dateFormatter = new SimpleDateFormat("yyyyMMddHHmm");
Date date = dateFormatter.parse("201710010200");

上面的代码对 2:00 A.M 之后的所有日期都抛出了异常。它 运行 直到 01:30 A.M.

已配置夏令时(我使用的是 Australia/Sydney 时区)。

之后,我可以看到 3:00 A.M 的日志。 2:00 A.M 之间的时间。和 3:00 A.M。也没有记录。

日志:

01 Oct 03:02:01 ERROR : Unparseable date: "201710010200"

Caused by: java.text.ParseException: Unparseable date: "201710010200" at java.text.DateFormat.parse(DateFormat.java:357)

如何解决日期字符串 "201710010200" 无法解析且指定了正确的日期格式的问题?

您正在尝试解析未发生的 date/time。

我们现在知道这是在悉尼时区。 2017 年 10 月 1 日凌晨 2 点,悉尼的时钟拨快至凌晨 3 点。如果你每分钟都在看一个时钟,你会看到:

  • 01:58
  • 01:59
  • 03:00
  • 03:01

所以凌晨 2 点(含)和凌晨 3 点(不含)之间的任何 date/time 根本不会出现在该时区。我们不知道是什么产生了您要解析的值,但是:

  • 如果它们是时间戳,几乎可以肯定,在 UTC 中格式化和解析会更好。如果生成它们的时区对未来分析很重要,请保留与 UTC 的偏移量和可能的时区 ID。
  • 如果它们是未链接到任何特定时区的 date/time 值,请不要将它们解析为 时区。理想情况下,使用 Java 8 的 java.time 包并将它们解析为 LocalDateTime

tl;博士

LocalDateTime.parse(                               // Parse a string lacking any indicator of time zone or offset-from-UTC into a `LocalDateTime` object.
    "201710010200" ,                               // Parse string in “basic” ISO 8601 format.
    DateTimeFormatter.ofPattern( "uuuuMMddHHmm" )  // Define a formatting pattern to match the input. If you used expanded ISO 8601 format instead of “basic” you would not need to bother with this step.
).atZone(                                          // Place the inexact unzoned value (`LocalDateTime` object) into the context of a time zone. Produces a `ZonedDateTime` object.
    ZoneId.of( "Australia/Sydney" )
).toString()                                       // Generate a string in standard expanded ISO 8601 format. This class extends the standard to wisely append the name of the zone in square brackets.

2017-10-01T03:00+11:00[Australia/Sydney]

如果那个time-of-day那个日期在那个区是无效的,ZonedDateTimeclass调整。

如果您担心输入错误,请陷阱 DateTimeParseException

详情

是正确的。根据他的评论:悉尼没有当地时间 2017 年 10 月 1 日凌晨 2 点的瞬间。

java.time

现代解决方案是 java.time classes 取代麻烦的旧遗留 date-time classes。

正在解析

定义格式模式以匹配您的输入。

String input = "201710010200" ;
DateTimeFormatter f = DateTimeFormatter.ofPattern( "uuuuMMddHHmm" ) ;

LocalDateTime

您输入的内容缺少 offset-from-UTC 或时区指示符。所以解析为 LocalDateTime

LocalDateTime ldt = LocalDateTime.parse( input , f ) ;

ldt.toString(): 2017-10-01T02:00

没有时区或 offset-from-UTC 的上下文,这个值没有实际意义。它代表一个时刻,时间轴上的一个点。对于大约 26-27 小时范围内的可能时刻,这只是一个模糊的想法。

要确定时刻,我们需要将此值置于时区或偏移量的上下文中。

您声称知道该值旨在表示 Australia/Sydney 时区的时刻。

ZoneId z = ZoneId.of( "Australia/Sydney" ) ;

2017-10-01T03:00+11:00[Australia/Sydney]

结果是 3 AMoffset-from-UTC used by the people of that region changed on that date at that time, a cut-over in Daylight Saving Time (DST) silliness. When the clock in that part of Australia was about to strike 2 AM, the clock jumped to 3 AM. The 2 AM hour never existed in that land. The ZonedDateTime class has a policy for automatically adjusting such invalid moment. Be sure to read the doc to see if you understand enter link description here并同意其算法。

因为这个 wall-clock 时间从未存在过,我怀疑你关于代表 Australia/Sydney 区域中的时刻的日志数据是不正确的。

UTC

这里更大的解决方案是学习在 UTC 中思考、工作、记录和交换数据。将 UTC 视为 One True Time,所有其他时区都只是该主题的变体。在工作时忘记你自己狭隘的时区 programming/administrating.

java.time 中,UTC 值的基本 class 是 InstantInstant class represents a moment on the timeline in UTC with a resolution of nanoseconds(最多九 (9) 位小数)。

这个class可以捕获UTC中的当前时刻。

Instant instant = Instant.now() ;  // Capture the current moment in UTC.

ISO 8601

Instant class 也可以 parse/generate 标准 ISO 8601 格式的字符串。

String output = Instant.now().toString() ;

"2018-01-23T12:34:56.734591Z"

最后的ZZulu的缩写,表示UTC。这是您原来问题的症结所在:您存储了 date-time 值而没有 zone/offset 这样的指标。

正在解析:

Instant instant = Instant.parse( "2018-01-23T12:34:56.734591Z" ) 

我强烈建议对所有此类 storage/exchange 文本 date-time 值使用 ISO 8601 格式。请务必:

  • 包括offset/zone当storing/exchanging时刻,时间轴上的精确点。
  • 使用扩展格式,而不是这些“基本”格式,尽量减少分隔符的使用。这些很难被人类阅读,在信息类型方面不太明显,并且在 java.time 中默认不使用。 java.time class默认使用扩展格式。

关于java.time

java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

要了解更多信息,请参阅 Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310

使用 JDBC driver compliant with JDBC 4.2 或更高版本,您可以直接与数据库交换 java.time 对象。不需要字符串,也不需要 java.sql.* classes.

从哪里获得java.time classes?

  • Java SE 8, Java SE 9,及以后
    • Built-in。
    • 标准 Java API 的一部分,带有捆绑实施。
    • Java 9 添加了一些小功能和修复。
  • Java SE 6 and Java SE 7
    • java.time 的大部分功能是 back-ported 到 Java ThreeTen-Backport 中的 6 和 7。
  • Android
    • Android java.time classes.
    • 捆绑实施的更高版本
    • 对于较早的Android,ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See

ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.