java.util.Date错误解析/在特定日期格式化
我正在使用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
你的问题很可能是时区,所以你可以使用:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
Here是ORI金奈回答。
这通常是由于(也称为“夏令时”)。根据日期/时间和输出结果(Sun Oct 16 01:00:00 BRST 2016
),我想它是Brazil's DST(BRST
是巴西夏令时的缩写)。
SimpleDateFormat
使用JVM的缺省时区(如果你没有指定一个),所以可能您的默认区域是America/Sao_Paulo
或Brazil/East
(您可以检查通过调用TimeZone.getDefault().getID()
)。
在America/Sao_Paulo
时区,DST started at October 16th 2016:在午夜,时钟偏移1小时向前午夜至上午01点(与从-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日日 2016年在巴西(前一天)下午9点相同午夜 - 也许这不是你想要的)。
在更改时区之前请注意:如果您想要特定时刻(精确的时间点),更改时区将影响最终结果。如果您只想考虑日期/时间值并且不在意它处于什么时区(将值视为“本地日期/时间”),则只需将格式化程序设置为使用UTC即可避免DST效应(一个丑陋的解决方法,国际海事组织,但只是因为java.util.Date
API没有特定类型的本地日期/时间)。
但无论如何,这不是一个错误。这是预期的行为(DST和时区有很多奇怪和不直观的行为,但事实就是这样)。
新的Java日期/时间API
老班(Date
,Calendar
和SimpleDateFormat
)有lots of problems和design 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_Paulo
或Europe/Berlin
)。 避免使用3字母缩写(如CST
或),因为它们是ambiguous and not standard。
通过调用ZoneId.getAvailableZoneIds()
,您可以获得可用时区列表(并选择最适合您系统的时区)。
您也可以使用系统的默认时区ZoneId.systemDefault()
,但即使在运行时也可以在不通知的情况下对其进行更改,因此最好使用特定的时区。
你绝对是雨果。 DST于去年10月16日开始,今年10月15日开始。我的错。谢谢。 – Leao
@Leao不客气,很高兴帮助!如果你发现答案有帮助,它解决了你的问题(又名“它回答你的问题”),你可以接受它(参见[如何做到这一点](https://stackoverflow.com/help/someone-answers)和[这里](https://stackoverflow.com/help/accepted-answer))。你没有义务这样做(只有当你发现答案有帮助并且解决了你的问题时),但是向未来的访问者表明答案是有用的并且回答问题是一个好习惯。 – 2017-10-10 13:08:33
我也尝试过从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
该代码不会返回'“2016-10-16 01:00:00”',因为Date.toString()不会返回该格式的字符串。 [mcve]会很有用 - 包括系统所在的时区。我强烈怀疑这只是由于夏令时导致的时区转换问题。 –
@Leao我在问题中添加了评论的代码。您可以随时编辑您的问题以添加更多信息。它比评论中的更好,更易读(特别是代码)。 – 2017-10-09 13:56:00