【问题标题】:DST changes caused an java.text.ParseException: Unparseable dateDST 更改导致 java.text.ParseException: Unparseable date
【发布时间】:2018-03-16 19:44:42
【问题描述】:

以下是引发异常的代码sn-p:

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

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

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

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

日志:

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

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

如果指定了正确的日期格式,日期字符串"201710010200" 没有被解析,有什么办法可以解决?

【问题讨论】:

  • 你在哪个时区?如果它是跳过 10 月 1 日凌晨 2 点的时间,那是有道理的 - 当地时间不存在。
  • (如果这些是时间戳,它们应该以 UTC 记录,然后以 UTC 解析......)
  • 没有日期......它的悉尼(澳大利亚)
  • 我不知道您所说的“没有日期”是什么意思,但正如我预测的那样,悉尼时区确实在 10 月 1 日凌晨 2 点向前推进:timeanddate.com/time/zone/australia/sydney。所以基本上,如果你在悉尼看一个正确的时钟,你永远不会看到凌晨 2 点或凌晨 2 点 30 分。时钟会走 1:58、1:59、3:00。
  • 它们是日期和时间值,格式为“yyyyMMddHHmm”

标签: java simpledateformat dst datetime-parsing


【解决方案1】:

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

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

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

因此,凌晨 2 点(含)和凌晨 3 点(不含)之间的任何日期/时间都不会出现在该时区。我们不知道是什么产生了您尝试解析的值,但是:

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

【讨论】:

  • 它们是日期和时间值,格式为“yyyyMMddHHmm”
  • @RaghavAgarwal:但它表示的日期/时间值根本没有出现在您解析它的时区中。没有Date 值它可以在格式化时返回它返回使用相同的SimpleDateFormat,将给出相同的字符串。
  • @RaghavAgarwal:换句话说:您期望它会返回什么?请记住 a Date value represents an instant in time - 您希望它在哪个时刻返回?
  • @RaghavAgarwal:这根本不能回答我的问题。这并没有说明该值要代表什么时刻 - 它只是重复问题中的代码。
  • @RaghavAgarwal:我不清楚你是否阅读过我写的任何东西。您希望该毫秒数是多少?那将表明一个瞬间。 没有即时的时间是悉尼当地时间 2017 年 10 月 1 日凌晨 2 点。
【解决方案2】:

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.DateCalendarSimpleDateFormat

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

要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310

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

从哪里获得 java.time 类?

ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可以在这里找到一些有用的类,例如IntervalYearWeekYearQuartermore

【讨论】:

  • 我认为我们可以为此写一本书;答案不能延长。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-12-09
  • 1970-01-01
  • 1970-01-01
  • 2018-02-27
相关资源
最近更新 更多