java.util.Date在特定日期解析/格式化错误

时间:2017-10-09 12:51:38

标签: java date format simpledateformat datetime-parsing

我正在使用java.util.Date。表达式:

new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2016-10-16 00:00:00").toString()

正在为此特定日期返回"Sun Oct 16 01:00:00 BRST 2016"(错误的日期),但在大多数其他日期都会返回正确的回复。

我还尝试了从Oracle文档中获取的格式字符串:"yyyy-MM-dd'T'HH:mm:ss.SSSZ"和特定日期:"2016-10-16T00:00:00.000-0300"并得到相同的"错误" (我想),提前一小时:

  

Sun Oct 16 01:00:00 BRST 2016

2 个答案:

答案 0 :(得分:1)

这通常是由于Daylight Saving Time (DST)(也称为“夏令时”)而发生的。根据日期/时间以及您得到的输出(Sun Oct 16 01:00:00 BRST 2016),我认为它是Brazil's DSTBRST是巴西夏令时的缩写)。

SimpleDateFormat使用JVM的默认时区(如果您没有指定),因此可能您的默认区域为America/Sao_PauloBrazil/East(您可以通过调用TimeZone.getDefault().getID())来检查。

America/Sao_Paulo时区,DST started at October 16th 2016:在午夜,时钟从午夜向凌晨1点移动1小时前进(偏移从-03:00更改为-02:00)。因此,此时区中不存在 00:00 00:59 之间的所有本地时间(您还可以认为时钟从23:59:59.999999999直接更改为01 :00)

这就是为什么这个午夜的特定日期(在这个时区中不存在)会自动转移到下一个有效时刻(凌晨1点)。但是如果我在格式化程序中设置一个特定的时区,则不会发生这种情况:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// set formatter to use UTC (instead of JVM default timezone)
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
// parse it as midnight (no shift to 01:00)
Date date = sdf.parse("2016-10-16 00:00:00");

在这种情况下,我使用 UTC ,它没有DST效果。但请注意,上面创建的日期相当于午夜UTC (与 10月15日 th 2016年晚上9点在巴西相同(前一天) - 也许这不是你想要的。)

在更改时区之前请注意:如果您想要特定时刻(精确时间点),更改时区将影响最终结果。如果您只是想考虑日期/时间值而不关心它是什么时区(将值视为“本地日期/时间”),只需将格式化程序设置为使用UTC,以避免DST效应(一个丑陋的解决方法,IMO,但仅仅因为java.util.Date API没有本地日期/时间的特定类型。)

但无论如何,这不是错误。这是预期的行为(DST和时区有很多奇怪和非直观的行为,但就是这样)。

Java新日期/时间API

旧类(DateCalendarSimpleDateFormat)有lots of problemsdesign issues,它们将被新API取代。< / p>

如果您使用的是 Java 8 ,请考虑使用new java.time API。它更容易,less bugged and less error-prone than the old APIs

如果您使用的是 Java 6或7 ,则可以使用ThreeTen Backport,这是Java 8新日期/时间类的绝佳后端。对于 Android ,您还需要ThreeTenABP(更多关于如何使用它here)。

以下代码适用于两者。 唯一的区别是包名称(在Java 8中为java.time,在ThreeTen Backport(或Android的ThreeTenABP)中为org.threeten.bp),但类和方法名称是相同的

这个新API的lots of new types最适合不同的用例。在您的情况下,如果您只想要日期和时间字段而不关心时区,则可以使用LocalDateTime。要解析它,只需使用DateTimeFormatter

DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime dt = LocalDateTime.parse("2016-10-16 00:00:00", fmt);
System.out.println(dt); // 2016-10-16T00:00

这将忽略DST效果,因为LocalDateTime没有时区信息。

但是如果您想考虑时区,可以使用ZonedDateTime将其转换为ZoneId来获取时区:

// convert to a timezone
ZonedDateTime z = dt.atZone(ZoneId.of("America/Sao_Paulo"));
System.out.println(z); // 2016-10-16T01:00-02:00[America/Sao_Paulo]

在这种情况下,请注意时间已调整为凌晨1点,因为我转换为America/Sao_Paulo时区,因此考虑了DST效果,如上所述。

使用这个新API,我们可以仔细查看此特定时区内发生的特定日期/时间。首先,我将创建一个ZonedDateTime,对应于2016年10月15日 th ,时间为America/Sao_Paulo时区的23:59:59,然后我将添加1秒钟:

// October 15th 2016, at 23:59:59 in Sao Paulo timezone
ZonedDateTime z = ZonedDateTime.of(2016, 10, 15, 23, 59, 59, 0, ZoneId.of("America/Sao_Paulo"));
System.out.println(z); // 2016-10-15T23:59:59-03:00[America/Sao_Paulo]
System.out.println(z.plusSeconds(1)); // 2016-10-16T01:00-02:00[America/Sao_Paulo]

请注意,原始日期位于偏移-03:00(比UTC低3小时,这是America/Sao_Paulo时区的标准偏移量)。一秒钟之后,它应该是午夜,但由于DST的变化,时钟直接转移到凌晨1点,偏移量变为-02:00

即使我尝试在此时区的午夜直接创建2016年10月16日 th ,也会更正该值,因为此时区中由于DST转换而不存在此本地时间:

// Try to create October 16th 2016, at midnight in Sao Paulo timezone
ZonedDateTime z = ZonedDateTime.of(2016, 10, 16, 0, 0, 0, 0, ZoneId.of("America/Sao_Paulo"));
System.out.println(z); // 2016-10-16T01:00-02:00[America/Sao_Paulo]

所以,这不是错误。 2016年10月16日 th 2016年午夜America/Sao_Paulo时区由于DST更改而不存在,并且API会自动将其更正为下一个有效时刻(在本例中为1) AM)。

API使用IANA timezones names(始终采用Region/City格式,如America/Sao_PauloEurope/Berlin。 避免使用3个字母的缩写(例如CSTPST),因为它们是ambiguous and not standard

您可以致电ZoneId.getAvailableZoneIds()获取可用时区列表(并选择最适合您系统的时区)。

您也可以将系统的默认时区与ZoneId.systemDefault()一起使用,但即使在运行时也可以在不事先通知的情况下进行更改,因此最好明确使用特定的时区。

答案 1 :(得分:-1)

你的问题可能是时区,所以你可以使用:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));

Here是最初的答案。