2017-10-09 130 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