簡體   English   中英

為什么SimpleDateFormat.format(Date)忽略配置的TimeZone?

[英]Why does SimpleDateFormat.format(Date) ignore the configured TimeZone?

鑒於我的默認時區是歐洲/巴黎:

System.out.println("Current Timezone: " + TimeZone.getDefault());
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");

String dateStrIn = "2016-08-01T00:00:00Z";
Date date = dateFormat.parse(dateStrIn);
String dateStrOut = dateFormat.format(date);

System.out.println("Input date String: "+dateStrIn);
System.out.println("Date.toString() "+date);
System.out.println("Output date String: "+dateStrOut);

輸出為:

Current Timezone: sun.util.calendar.ZoneInfo[id="Europe/Paris",offset=3600000,dstSavings=3600000,useDaylight=true,transitions=184,lastRule=java.util.SimpleTimeZone[id=Europe/Paris,offset=3600000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=2,startMonth=2,startDay=-1,startDayOfWeek=1,startTime=3600000,startTimeMode=2,endMode=2,endMonth=9,endDay=-1,endDayOfWeek=1,endTime=3600000,endTimeMode=2]]
Input date String: 2016-08-01T00:00:00Z
Date.toString() Mon Aug 01 02:00:00 CEST 2016
Output date String: 2016-08-01T02:00:00+02

現在,我重復執行,但是設置了另一個默認的TimeZone(UTC):

TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
System.out.println("Current Timezone: " + TimeZone.getDefault());
...

這是輸出:

Current Timezone: sun.util.calendar.ZoneInfo[id="UTC",offset=0,dstSavings=0,useDaylight=false,transitions=0,lastRule=null]
Input date String: 2016-08-01T00:00:00Z
Date.toString() Mon Aug 01 00:00:00 UTC 2016
Output date String: 2016-08-01T02:00:00+02

Date.toString()已正確考慮TimeZone的更改。 但是,字符串是從dateFormat.format(date);獲得的dateFormat.format(date); 仍然顯示+02而不是Z或+00,為什么?

使用標准Java API,是否有任何方法可以根據選定的TimeZone強制進行格式化?

更新:

Jesper的解決方案幾乎在所有情況下都有效,但是我遇到了這個問題(使用加那利島的夏時區WEST),但它沒有:

SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss z");
dateFormat.setTimeZone(TimeZone.getTimeZone("Europe/Paris"));
TimeZone.setDefault(TimeZone.getTimeZone("Europe/Paris"));
System.out.println("Current Timezone: " + TimeZone.getDefault());

String dateStrIn = "2016-08-01T08:00:00 WEST";
Date date = dateFormat.parse(dateStrIn);
String dateStrOut = dateFormat.format(date);

System.out.println("Input date String: "+dateStrIn);
System.out.println("Date.toString() "+date);
System.out.println("Output date String: "+dateStrOut);

輸出:

Current Timezone: sun.util.calendar.ZoneInfo[id="Europe/Paris",offset=3600000,dstSavings=3600000,useDaylight=true,transitions=184,lastRule=java.util.SimpleTimeZone[id=Europe/Paris,offset=3600000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=2,startMonth=2,startDay=-1,startDayOfWeek=1,startTime=3600000,startTimeMode=2,endMode=2,endMonth=9,endDay=-1,endDayOfWeek=1,endTime=3600000,endTimeMode=2]]
Input date String: 2016-08-01T08:00:00 WEST
Date.toString() Mon Aug 01 09:00:00 CEST 2016
Output date String: 2016-08-01T08:00:00 WEST

您可以看到輸出日期字符串仍以WEST表示。

例如,如果我將初始dateStrIn更改為: String dateStrIn = "2016-08-01T08:00:00 GMT"; ,則輸出日期字符串按預期在CEST中表示:

Output date String: 2016-08-01T10:00:00 CEST

可能是錯誤嗎?

更新2:

另一個例子

Default TimeZone for both Date and SimpleDateFormat: "Europe/Paris"
Input String: "2016-08-01T08:00:00 WET"

輸出:

Date.toString() Mon Aug 01 10:00:00 CEST 2016
Output date String: 2016-08-01T09:00:00 WEST

注意dateFormat.format(date); 產生了WEST日期字符串。 從WET-> WEST,應該是WET-> CEST。

代替設置默認時區,在您的SimpleDateFormat對象上設置時區:

dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));

這將使SimpleDateFormat對象在UTC時區中設置Date對象的格式。

如果您使用的是Java 8,請考慮在包java.time使用新的日期和時間API,而不是舊的java.util.Datejava.text.SimpleDateFormat

,這不是錯誤。

您必須首先了解Date類的工作原理。 epoch以來的毫秒數只是一個包裝,以long表示。 因此,無論是哪個時區,日期對象的基礎值都保持不變。 您永遠無法真正更改Date類的時區。 您只能使用SimpleDateFormat類表示日期實例的String格式。 根據您在創建SimpleDateFormat對象時所使用的時區,此表示形式可能具有不同的時區。

同樣,您需要檢查Date類的toString方法。 它始終以默認時區打印日期。

編輯

您還應該查看SimpleDateFormat.parse()定義。 JDK說,

根據給定的模式和文本中的時區值,TimeZone值可能會被覆蓋。 可能需要恢復以前通過調用setTimeZone設置的任何TimeZone值,以進行進一步的操作。

這個謎的答案在Javadoc上:

DateFormat.parse(String source, ParsePosition pos)

此解析操作使用日歷生成日期。 因此,取決於子類的實現,日歷的日期時間字段和TimeZone值可能已被覆蓋。 可能需要恢復以前通過調用setTimeZone設置的任何TimeZone值,以進行進一步的操作。

解析后設置TimeZone即可:

SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss z");
TimeZone.setDefault(TimeZone.getTimeZone("Europe/Paris"));

System.out.println("Current Timezone: " + TimeZone.getDefault());

String dateStrIn = "2016-08-01T08:00:00 WET";
Date date = dateFormat.parse(dateStrIn);
dateFormat.setTimeZone(TimeZone.getTimeZone("Europe/Paris"));
String dateStrOut = dateFormat.format(date);

System.out.println("Input date String: "+dateStrIn);
System.out.println("Date.toString() "+date);
System.out.println("Output date String: "+dateStrOut);

正確的輸出:

Current Timezone: sun.util.calendar.ZoneInfo[id="Europe/Paris",offset=3600000,dstSavings=3600000,useDaylight=true,transitions=184,lastRule=java.util.SimpleTimeZone[id=Europe/Paris,offset=3600000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=2,startMonth=2,startDay=-1,startDayOfWeek=1,startTime=3600000,startTimeMode=2,endMode=2,endMonth=9,endDay=-1,endDayOfWeek=1,endTime=3600000,endTimeMode=2]]
Input date String: 2016-08-01T08:00:00 WET
Date.toString() Mon Aug 01 10:00:00 CEST 2016
Output date String: 2016-08-01T10:00:00 CEST

您通過使用麻煩的舊的舊式日期時間類來折磨自己,這些類現在已被java.time框架淘汰。

tl; dr

ZonedDateTime.ofInstant( Instant.parse( "2016-08-01T00:00:00Z" ) , ZoneId.of( "Europe/Paris" ) )

java.time

java.time框架內置於Java 8及更高版本中。 這些類取代了麻煩的舊日期時間類,例如java.util.Date.Calendarjava.text.SimpleDateFormat Joda-Time小組還建議遷移到java.time。

要了解更多信息,請參見Oracle教程 並在Stack Overflow中搜索許多示例和說明。

多的java.time功能后移植到Java 6和7在ThreeTen-反向移植並且還適於在到Android ThreeTenABP

Instant

輸入字符串2016-08-01T00:00:00Z末尾的ZZulu縮寫,表示UTC Instant類在java.time中表示,分辨率最高為ns。

Instant instant = Instant.parse ( "2016-08-01T00:00:00Z" );

ZonedDateTime

通過ZoneId指定一個時區以獲取ZonedDateTime

在夏天, 巴黎時間比UTC提前兩個小時。 因此,在8月1日的同一天,與巴黎牆上的時鍾所看到的同一時刻是凌晨2點,而不是午夜。

ZoneId zoneId_Paris = ZoneId.of ( "Europe/Paris" );
ZonedDateTime zdt_Paris = ZonedDateTime.ofInstant ( instant , zoneId_Paris );

您可以調整到另一個時區,例如Atlantic/Canary 對於夏季的這個日期,時間比UTC提前一小時。 結果是1 AM,而不是午夜。

ZoneId zoneId_Canary = ZoneId.of ( "Atlantic/Canary" );
ZonedDateTime zdt_Canary = zdt_Paris.withZoneSameInstant ( zoneId_Canary );

轉儲到控制台。

System.out.println ( "instant: " + instant + " | zdt_Paris: " + zdt_Paris + " | zdt_Canary: " + zdt_Canary );

即時:2016-08-01T00:00:00Z | zdt_Paris:2016-08-01T02:00 + 02:00 [歐洲/巴黎] | zdt_Canary:2016-08-01T01:00 + 01:00 [大西洋/金絲雀]

所有這三個對象(UTC,巴黎,加那利)的代表在歷史上是相同的同時時刻,在時間軸上同一個點。 每個鏡頭都是通過不同的時鍾時間觀看的。

實時區

避免使用3-4個字母的區域縮寫,例如WETWEST 這些不是實時區域,不是標准化的,甚至不是唯一的(!)。

Europe/ParisAtlantic/Canary正確的時區名稱 ,格式為continent/region

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM