我正在使用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
答案 0 :(得分:1)
这通常是由于Daylight Saving Time (DST)(也称为“夏令时”)而发生的。根据日期/时间以及您得到的输出(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点移动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和时区有很多奇怪和非直观的行为,但就是这样)。
旧类(Date
,Calendar
和SimpleDateFormat
)有lots of problems和design 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_Paulo
或Europe/Berlin
。
避免使用3个字母的缩写(例如CST
或PST
),因为它们是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是最初的答案。