2017-10-09 128 views
-1

我正在使用java.util.Date。表达: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"并得到了同样的“错误”(我想),提前一小时:

太阳10月16日01:00 :00 BRST 2016

+0

我也尝试过从Oracle文档取得的格式字符串:“yyyy-MM-dd'T'HH:mm:ss.SSSZ”和特定日期:“2016-10-16T00:00:00.000-0300”和得到了同样的“错误”(我想):Sun Oct 16 01:00:00 BRST 2016,提前一个小时。 – Leao

+3

该代码不会返回'“2016-10-16 01:00:00”',因为Date.toString()不会返回该格式的字符串。 [mcve]会很有用 - 包括系统所在的时区。我强烈怀疑这只是由于夏令时导致的时区转换问题。 –

+0

@Leao我在问题中添加了评论的代码。您可以随时编辑您的问题以添加更多信息。它比评论中的更好,更易读(特别是代码)。 – 2017-10-09 13:56:00

回答

-1

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

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

Here是ORI金奈回答。

1

这通常是由于​​(也称为“夏令时”)。根据日期/时间和输出结果(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小时向前午夜至上午01点(与从-03:00偏移改为-02:00)。所以当地时间00:0000: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日 2016年在巴西(前一天)下午9点相同午夜 - 也许这不是你想要的)。

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

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


新的Java日期/时间API

老班(DateCalendarSimpleDateFormat)有lots of problemsdesign issues,他们正在被新的API取代。

如果您使用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,在America/Sao_Paulo时区对应于10月15日 2016年,在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

即使我尝试直接创造10月16日 2016年午夜在这个时区,该值将被修正,因为这个地方的时间不在这个时区存在,由于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] 

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


API使用IANA timezones names(总是在格式Region/City,像America/Sao_PauloEurope/Berlin)。 避免使用3字母缩写(如CST或),因为它们是ambiguous and not standard

通过调用ZoneId.getAvailableZoneIds(),您可以获得可用时区列表(并选择最适合您系统的时区)。

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

+0

你绝对是雨果。 DST于去年10月16日开始,今年10月15日开始。我的错。谢谢。 – Leao

+0

@Leao不客气,很高兴帮助!如果你发现答案有帮助,它解决了你的问题(又名“它回答你的问题”),你可以接受它(参见[如何做到这一点](https://stackoverflow.com/help/someone-answers)和[这里](https://stackoverflow.com/help/accepted-answer))。你没有义务这样做(只有当你发现答案有帮助并且解决了你的问题时),但是向未来的访问者表明答案是有用的并且回答问题是一个好习惯。 – 2017-10-10 13:08:33