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

2022-09-03 15:19:21

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

我写了一些简单的测试代码来计算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 实现和操作系统中是否可靠?


答案 1

TimeZone.getDefault() 规范非常清楚它如何获取时区:

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

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

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

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

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

如果区域为空,则清除缓存的默认时区。

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

  1. 通过调用清除时区缓存TimeZone.setDefault(null);
  2. 通过调用清除系统属性user.timezoneSystem.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);
    }

答案 2

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

我认为这门课有答案。如果您查看它,您将看到一个名为的私有变量,该变量保存日期/日历实例默认使用的时区。此变量在 方法和 中设置。TimeZonedefaultTimeZonesetDefaultZonesetDefault

该方法是私有的,并且仅从从日期/日历构造函数调用的函数中调用。下面是它的代码:setDefaultZonegetDefaultRef

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;
}

从此方法中,很明显,Date/Calendar的任何新实例都将使用已设置的日历实例,并且不会检查它是否与操作系统使用的日历实例相同。

该方法非常直接,它只是将变量设置为新值。setDefaultdefaultTimeZone

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

阅读此方法的文档,您将阅读以下语句:。这意味着将来对 or 的调用将首先从属性中读取它来检索最新的操作系统时区。如果此属性为空,它将读取系统的实际时区。If zone is null, the cached default TimeZone is clearedgetDefaultgetDefaultRefuser.timezone

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

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

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


推荐