2016-08-12 59 views
13

我找不到任何具體的文檔來回答這個問題。正在運行的JVM是否檢測到計算機時區的更改?

我寫了一些簡單的測試代碼,以找出究竟發生在的Java 1.8在OS X 10.12:

public static void main(String[] _args) throws InterruptedException { 
    while (true) { 
     int calendarTimezoneOffset = Calendar.getInstance().get(Calendar.ZONE_OFFSET); 
     System.out.println("calendarTimezoneOffset = " + calendarTimezoneOffset); 

     ZoneOffset offset = ZonedDateTime.now().getOffset(); 
     System.out.println("offset = " + offset); 

     Thread.sleep(1000); 
    } 
} 

無論是舊的方式(日曆)和新方法(Java 8的日期和時間庫)在JVM運行時,不會檢測到我對操作系統時區做出的任何更改。我需要停止並開始代碼以獲取更改的時區。

這是設計嗎?這是跨JVM實現和操作系統的可靠行爲嗎?

+0

哇,另一路時區如何刺傷我們在後面......我認爲這是電腦不運行期間時區之間移動的假設的一個程序。雖然這不符合法案......我認爲當前的JVM不會改變操作系統的時區。 (不是JVM,但可能值得檢查Android是如何做到的......) – ppeterka

+0

我認爲這可以歸類爲JVM錯誤。 – Mac70

+1

JVM不知道有關時區的事情(規範從未提及它們,虛擬機也不需要這些信息),與時區有關的所有事情都是平臺API的一部分,也就是說,它在像java這樣的類中實現。 util.Timezone','java.util.Calendar'或'java.time'-packages'。但是在改變時區時,我沒有發現定義這些類的行爲,所以在VM運行期間不改變默認時區似乎是目前Java實現的行爲,但它絕不是保證。 –

回答

6

TimeZone.getDefault()規範是它是如何得到的時間段非常清楚:

獲取Java虛擬機的默認時區。如果高速緩存的 默認TimeZone可用,則返回其克隆。否則, 方法將採取以下步驟來確定默認時區。

  • 如果可用,使用user.timezone屬性值作爲默認時區ID。
  • 檢測平臺時區ID。平臺時區和ID映射的來源可能因實現而有所不同。
  • 如果給定或檢測到的時區標識未知,則使用GMT作爲最後的手段。

從ID創建的默認時區進行緩存,其克隆 返回。在返回 時,user.timezone屬性值被設置爲ID。

該文檔指出該區域是緩存;此方法受user.timezone系統屬性的影響,反之亦然。

該規範還表示,高速緩存由TimeZone.setDefault(null)清零:

如果區域爲空,緩存的默認時區將被清除。

也就是說,爲了重新讀取系統時區,你必須

  1. 清除緩存時區通過調用TimeZone.setDefault(null);通過調用System.clearProperty("user.timezone");

  • 清除user.timezone系統屬性試試下面的測試:

    while (true) { 
         TimeZone.setDefault(null); 
         System.clearProperty("user.timezone"); 
    
         System.out.println("Offset = " + TimeZone.getDefault().getRawOffset()/3600); 
         System.out.println("Zone ID = " + System.getProperty("user.timezone")); 
    
         Thread.sleep(1000); 
        } 
    
  • 1

    我尋找一些JVM文檔與此問題相關的,但沒有的事其中提到對底層操作系統的更改會傳播到JVM。

    我認爲TimeZone類持有答案。如果你仔細看看它,你會發現有一個叫做defaultTimeZone的私有變量,它保存了Date/Calendar實例默認使用的時區。該變量在方法setDefaultZonesetDefault中設置。

    setDefaultZone方法是私有的,只從getDefaultRef調用,該方法從Date/Calendar構造函數中調用。下面是它的代碼:

    static TimeZone getDefaultRef() { 
        TimeZone defaultZone = defaultTimeZone; 
        if (defaultZone == null) { 
         // Need to initialize the default time zone. 
         defaultZone = setDefaultZone(); 
         assert defaultZone != null; 
        } 
        // Don't clone here. 
        return defaultZone; 
    } 
    

    從這個方法,顯然,日期的任何新的實例/日曆將使用已經設置日曆實例,並不會檢查它是否是同一個操作系統使用。

    setDefault方法很直接,它只是將defaultTimeZone變量設置爲一個新值。

    public static void setDefault(TimeZone zone) { 
        SecurityManager sm = System.getSecurityManager(); 
        if (sm != null) { 
         sm.checkPermission(new PropertyPermission("user.timezone", write")); 
        } 
        defaultTimeZone = zone; 
    } 
    

    閱讀此方法的文檔,您將閱讀以下聲明:If zone is null, the cached default TimeZone is cleared。這意味着將來撥打getDefaultgetDefaultRef的電話將從user.timezone屬性中首先讀取最新的操作系統時區。如果此屬性爲空,它將讀取系統的實際時區。

    瞭解了這一點後,我認爲可以安全地假設對操作系統時區的更改不會傳播到默認時區,因爲該值已被緩存,並且從未更新,除非客戶端希望這樣做。就我看來,有兩種可能的方式來做到這一點:

    1. 同時傳遞所需的時區爲新的默認時區
    2. 使用下面的代碼將它的值設置爲使用TimeZone.setDefault方法你係統採用:

      System.setProperty("user.timezone", ""); TimeZone.setDefault(null);

    相關問題