DST更改导致java.text.ParseException:Unparseable date

时间:2017-10-05 08:46:09

标签: java simpledateformat dst datetime-parsing

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

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

上面的代码在凌晨2点之后的所有日期都抛出了异常。它运行良好,直到凌晨01:30。

配置了DayLight节省时间(我正在使用Australia/Sydney时区)。

之后,我可以看到凌晨3点的日志。 凌晨2点之间的时间和凌晨3点也没有记录。

日志:

  

01 Oct 03:02:01错误:无法解析的日期:“201710010200”

     
    

引起:java.text.ParseException:无法解析的日期:“201710010200”         在java.text.DateFormat.parse(DateFormat.java:357)

  

如果指定了正确的日期格式,日期字符串"201710010200"无法解析的问题是什么?

2 个答案:

答案 0 :(得分:6)

您正在尝试解析未发生的日期/时间。

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

  • 1点58
  • 一点59
  • 03:00
  • 03:01

所以在凌晨2点(含)和凌晨3点(独家)之间的任何日期/时间都不会在该时区内发生。我们不知道是什么产生了您要解析的值,但是:

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

答案 1 :(得分:1)

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 [澳大利亚/悉尼]

如果该区域中该日期的某个时间段无效,则ZonedDateTime课程会进行调整。

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

详细

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

java.time

现代解决方案是 java.time 类,它取代了麻烦的旧遗留日期时间类。

解析

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

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

LocalDateTime

您的输入缺少与UTC或时区偏移的指示。因此解析为LocalDateTime

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

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

如果没有时区的上下文或从UTC偏移,则此值没有实际意义。它代表时刻,时间轴上的一个点。关于在大约26-27小时范围内可能发生的瞬间,这只是一个模糊的想法。

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

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

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

2017-10-01T03:00 + 11:00 [澳大利亚/悉尼]

结果是凌晨3点。那个地区的人们使用的offset-from-UTC在那个时候改变了,Daylight Saving Time (DST)愚蠢的切断。当澳大利亚那个地区的时钟即将凌晨2点开始时,时钟跳到凌晨3点。凌晨2点从未在那片土地上存在过。 ZonedDateTime类具有自动调整此类无效时刻的策略。请务必阅读文档,了解您是否理解enter link description here并同意其算法。

因为这个挂钟时间从未存在过,所以我怀疑你对那些代表Australia/Sydney区域中的时刻的日志数据不正确。

UTC

这里更大的解决方案是学习以UTC格式思考,工作,记录和交换数据。将UTC视为 One True Time ,所有其他区域仅仅是该主题的变体。在工作编程/管理时忘掉你自己的狭隘时区。

java.time 中,UTC值的基本类为InstantInstant类代表UTC中时间轴上的一个时刻,分辨率为nanoseconds(小数部分最多九(9)位)。

此类可以捕获UTC中的当前时刻。

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

ISO 8601

Instant类也可以解析/生成标准ISO 8601格式的字符串。

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

“2018-01-23T12:34:56.734591Z”

最后ZZulu的缩写,表示UTC。这是您原始问题的症结所在:您存储的日期时间值没有区域/偏移的指示。

解析:

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

我强烈建议将ISO 8601格式用于文本日期时间值的所有此类存储/交换。一定要:

  • 在存储/交换时刻,时间线上的精确点时包括偏移/区域
  • 使用扩展格式,而不是这些最小化分隔符使用的“基本”格式。这些很难被人阅读,信息类型不太明显,默认情况下不会在 java.time 中使用。 java.time 类默认使用扩展格式。

关于 java.time

java.time框架内置于Java 8及更高版本中。这些类取代了麻烦的旧legacy日期时间类,例如java.util.DateCalendar和& SimpleDateFormat

现在位于Joda-Timemaintenance mode项目建议迁移到java.time类。

要了解详情,请参阅Oracle Tutorial。并搜索Stack Overflow以获取许多示例和解释。规范是JSR 310

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

从哪里获取java.time类?

ThreeTen-Extra项目使用其他类扩展java.time。该项目是未来可能添加到java.time的试验场。您可以在此处找到一些有用的课程,例如IntervalYearWeekYearQuartermore