2017-02-10 20 views
0

我正在使用java日曆在午夜(GMT)創建某個日期,然後將時區更改爲我的本地時區以確保請求的時間爲1:00(GMT + 1)。下面的代碼工作,包括。 2成功斷言。Java日曆時區更改無法按預期工作

TimeZone TIMEZONE_GMT = TimeZone.getTimeZone("GMT"); 
TimeZone TIMEZONE_LOCAL = TimeZone.getDefault(); // GMT + 1 

private Calendar getMidnightGmtCalendarWithLocalTimezone() { 
    Calendar calendar = Calendar.getInstance(Locale.GERMAN); 
    calendar.set(2017, Calendar.JANUARY, 1, 0, 0, 0); 

    calendar.setTimeZone(TIMEZONE_GMT); // Probably not required 
    assertEquals(0, calendar.get(Calendar.HOUR_OF_DAY)); // Assert 1 

    // Log.i("TAG", "" + calendar.get(Calendar.HOUR_OF_DAY)); 

    calendar.setTimeZone(TIMEZONE_LOCAL); 
    assertEquals(1, calendar.get(Calendar.HOUR_OF_DAY)); // Assert 2 
    return calendar; 
} 

現在我刪除第1個斷言,我預計沒有任何變化。現實:第二個斷言現在失敗!我試圖理解.get()的實現,顯然它也計算一些時間,所以它不僅僅是收集值。但我不明白爲什麼我的第二個斷言失敗。

第二屆斷言重新獲得成功時,我去掉日誌行(只是要確定從calendar.get(問題出現),而不是斷言!)

所以,第一個問題:爲什麼會發生這種情況? 第二個問題:我如何確保在爲Timezone GMT的午夜時間設置時間後,我有本地時區的Calendar實例? (換句話說,我怎樣才能正確地轉換時區?)

編輯: 我在Android上使用這個,所以不能使用Java8。喬達時間(如下所示)是一個很好的選擇,我肯定會研究這一點。但是,作爲我的問題的答案,我想知道它如何與Java7 Calendar配合使用,因爲我現在已經綁定了該代碼。

+3

我建議放棄日曆並嘗試新的java.time包,如果您在JDK 8上。您應該是 - 它是唯一未通過支持生命週期的JDK。 – duffymo

+0

@duffymo我完全同意你,只是想知道,你能否提供任何關於*爲什麼要使用Calendar類應該被放棄的權威性描述? – Andremoniy

+0

權威性? java.util.Calendar是JDK 1.0年份。自從一開始就承認它很複雜且難以使用。 (如果我沒有記錯的話,由IBM撰寫。)JODA的開發旨在解決這些缺陷。它非常成功,因此Oracle將它作爲java.time包裝到JDK 8中。日曆是一個20歲以上的故障。它被一些被認爲是巨大改進的東西所取代。我基於歷史聲稱權威。這對你來說足夠好嗎? – duffymo

回答

0

所以,我已經運行下面的代碼片段:

public static void main(String[] args) throws Exception { 
    TimeZone TIMEZONE_GMT = TimeZone.getTimeZone("GMT"); 

    Calendar calendar = new GregorianCalendar(TimeZone.getTimeZone("Europe/Berlin")); 
    calendar.set(2017, Calendar.JANUARY, 1, 0, 0, 0); 

    System.out.println(calendar.get(Calendar.HOUR_OF_DAY)); 
    calendar.setTimeZone(TIMEZONE_GMT); // Probably not required 
    System.out.println(calendar.get(Calendar.HOUR_OF_DAY)); 
} 

上執行它,我這是正確的控制檯得到了023

然後我發表了第一條Sysout聲明,並打印0(我預計它會打印23)。

在探索更多內容時,我發現this SO答案正確地解釋了日曆對象只存儲毫秒,TimeZone未考慮在內。如果我是你,我不會太擔心asserts,我會確保我得到一個Calendar實例所需的時區(或默認時區,如果默認時區設置爲你需要的時區),例如:

Calendar calendar = new GregorianCalendar(TimeZone.getTimeZone("Europe/Berlin")); 
//OR 
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("Europe/Berlin")); 
//OR 
Calendar calendar = Calendar.getInstance(); //If default timezone is Europe/Berlin 
3

TL;博士

使用java.time,不java.util.Calendar

Instant.parse("2017-01-01T00:00:00Z")   // UTC. 
     .atZone(ZoneId.of("Africa/Algiers")) // Time zone with offset +01:00. 

避免Calendar

舊日期,時間類,包括Calendar很麻煩,設計不佳,令人目不暇接,和有缺陷的。避免它們。嘗試瞭解他們的過度API是沒有意義的。

遺留的日期 - 時間類被內置於Java 8及更高版本的類的java.time package替代。

Instant

Instant類表示UTC時間軸與納秒的分辨率上一會兒。

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

instant.toString():2017-01-01T00:00:00Z

ZonedDateTime

你問地看到,在這是提前一小時時區世界標準時間。所以讓我們嘗試應用時區Africa/Algiers以獲得ZonedDateTime

ZoneId z = ZoneId.of("Africa/Algiers"); 
ZonedDateTime zdt = instant.atZone(z); 

zdt.toString():2017-01-01T01:00 + 01:00 [非洲/阿爾及爾]

OffsetDateTime

如果你想構建你UTC日期通過參數而不是解析字符串(如上所示),將參數傳遞給OffsetDateTime的工廠方法。指定方便的常量,ZoneOffset.UTC。月份的編號是明智的,1月至12月爲1-12,與Calendar不同。

OffsetDateTime odt = OffsetDateTime.of(2017 , 1 , 1 , 0 , 0 , 0 , 0 , ZoneOffset.UTC) ; 

odt.toString():2017-01-01T00:00Z

與上述類似,我們可以調整到一個時區。對異常進行調整,如Daylight Saving Time (DST)

ZoneId z = ZoneId.of("Africa/Algiers"); 
ZonedDateTime zdt = odt.atZoneSameInstant(z); 

zdt.toString():2017-01-01T01:00 + 01:00 [非洲/阿爾及爾]

見本code run line in IdeOne.com

Locale

就問題的代碼中使用的Locale ... Locale是完全正交時區,獨立和獨特。 A Locale對象僅影響正在生成的字符串的格式以表示日期時間值。


關於java.time

java.time框架是建立在Java 8和更高版本。這些類取代了日期時間類legacy,如java.util.Date,Calendar,& SimpleDateFormat

Joda-Time項目現在位於maintenance mode,建議遷移到java.time類。請參閱Oracle Tutorial。並搜索堆棧溢出了很多例子和解釋。規格是JSR 310

從何處獲取java.time類?

ThreeTen-Extra項目與其他類擴展java.time。這個項目是未來可能增加java.time的一個試驗場。您可以在這裏找到一些有用的類,如Interval,YearWeek,YearQuartermore