2016-08-12 47 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);

    相关问题