在更改時區之前請注意:如果您想要特定時刻(精確的時間點),更改時區將影響最終結果。如果您只想考慮日期/時間值並且不在意它處於什麼時區(將值視爲「本地日期/時間」),則只需將格式化程序設置爲使用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()
,但即使在運行時也可以在不通知的情況下對其進行更改,因此最好使用特定的時區。
我也嘗試過從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