Подтвердить что ты не робот

Выполняет ли запущенная 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 Date and Time library) не обнаруживают никаких изменений, которые я вношу в часовой пояс ОС во время работы JVM. Мне нужно остановить и запустить код, чтобы получить измененный часовой пояс.

Это по дизайну? Является ли это надежным поведением в реализациях JVM и операционных системах?

4b9b3361

Ответ 1

TimeZone.getDefault() спецификация довольно ясно о том, как она получает часовой пояс:

Получает значение по умолчанию TimeZone виртуальной машины Java. Если кешированный доступен TimeZone по умолчанию, возвращается его клон. В противном случае метод принимает следующие шаги для определения часового пояса по умолчанию.

  • Используйте значение свойства user.timezone как идентификатор часового пояса по умолчанию, если оно доступно.
  • Обнаружение идентификатора часового пояса платформы. Источник отображения часового пояса платформы и сопоставления идентификаторов может отличаться в зависимости от реализации.
  • Использовать GMT в качестве последнего средства, если данный или обнаруженный идентификатор часового пояса неизвестен.

По умолчанию TimeZone, созданный из идентификатора, кэшируется, а его клон вернулся. Значение свойства user.timezone устанавливается равным ID вернуться.

В документации указано, что зона кэширована; на этот метод влияет системное свойство user.timezone и наоборот.

В спецификации также говорится, что кеш очищается TimeZone.setDefault(null):

Если зона имеет значение NULL, кешированный по умолчанию TimeZone очищается.

То есть, чтобы перечитать системный часовой пояс, вы должны

  • Очистить кеш тайм-кода, вызывая TimeZone.setDefault(null);
  • Очистить системное свойство user.timezone, вызвав 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);
    }

Ответ 2

Я ищу некоторые из документации JVM, связанные с этой проблемой, но нет ничего, что упоминает, что изменения в базовой ОС распространяются на JVM.

Я думаю, что класс TimeZone содержит ответ. Если вы посмотрите на него, вы увидите, что есть приватная переменная с именем defaultTimeZone, которая содержит часовой пояс, который по умолчанию используется экземплярами Date/Calendar. Эта переменная устанавливается в методах setDefaultZone и setDefault.

Метод 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;
}

Из этого метода очевидно, что любые новые экземпляры Date/Calendar будут использовать уже установленный экземпляр календаря и не будут проверять, совпадает ли он с тем, который использует ОС.

Метод 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. Это означает, что будущие вызовы getDefault или getDefaultRef будут извлекать последнюю часовую зону ОС из ее первой записи из свойства user.timezone. Если это свойство пустое, оно будет считывать фактический часовой пояс системы.

Зная это, я думаю, что можно с уверенностью предположить, что изменения в часовом поясе OS не распространяются на TimeZone по умолчанию, поскольку это значение кэшируется и никогда не обновляется, если клиент не хочет этого делать. Как я вижу это, есть два возможных способа сделать это:

  • Используйте метод TimeZone.setDefault при передаче желаемого часового пояса в качестве нового часового пояса по умолчанию
  • Используйте следующий код, чтобы установить его значение, которое использует ваша система:

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