正在运行的JVM是否检测到计算机时区的更改?

时间:2016-08-12 08:43:57

标签: java jvm

我找不到任何具体的文档来回答这个问题。

我写了一些简单的测试代码来解决OS X 10.12上Java 1.8实际发生的事情:

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实现和操作系统的可靠行为吗?

2 个答案:

答案 0 :(得分:8)

TimeZone.getDefault()规范很清楚如何获得时区:

  

获取Java虚拟机的默认TimeZone。如果缓存了   默认TimeZone可用,返回其克隆。否则,   方法采用以下步骤来确定默认时区。

     
      
  • 使用user.timezone属性值作为默认时区ID(如果可用)。
  •   
  • 检测平台时区ID。平台时区和ID映射的来源可能因实施而异。
  •   
  • 如果给定或检测到的时区ID未知,请使用GMT作为最后的手段。
  •   
     

从ID创建的默认TimeZone被缓存,其克隆是   回。 user.timezone属性值设置为ID   返回。

文档说明该区域已缓存;此方法受 user.timezone 系统属性的影响,反之亦然。

规范还说明TimeZone.setDefault(null)清除了缓存:

  

如果zone为null,则清除缓存的默认TimeZone。

也就是说,为了重新读取系统时区,你必须

  1. 通过调用TimeZone.setDefault(null);
  2. 清除TimeZone缓存
  3. 通过调用user.timezone
  4. 清除System.clearProperty("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 :(得分:3)

我搜索了一些与此问题相关的JVM文档,但是没有提到底层操作系统的更改会传播到JVM。

我认为TimeZone类有答案。如果你查看它,你会看到有一个名为defaultTimeZone的私有变量,它保存了Date / Calendar实例默认使用的时区。此变量在方法setDefaultZonesetDefault中设置。

setDefaultZone方法是私有的,只能从日期/日历构造函数调用的getDefaultRef调用。以下是代码:

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属性中首先读取最新的操作系统时区。如果此属性为空,则它将读取系统的实际TimeZone。

知道这些事情,我认为可以安全地假设对操作系统时区的更改没有传播到默认的TimeZone,因为该值被缓存并且永远不会更新,除非客户端希望这样做。我看到它的方式,有两种可能的方法:

  1. 在将所需时区作为新的默认时区传递时使用TimeZone.setDefault方法
  2. 使用以下代码将其设置为系统使用的值:

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