Некоторые часовые пояса возвращают IllegalArgumentException

В этом примере кода используются 3 часовых пояса (EST, PST, EET). Для каждого часового пояса создается объект Date и запускается toString() для вывода используемого формата. Затем это же строковое значение передается конструктору и используется для создания нового объекта даты. Код выполняет проверку, чтобы убедиться, что используемый часовой пояс действителен.

Все 3 часовых пояса (EST, PST, EET) действительны, но при создании объекта исключение java.lang.IllegalArgumentException возвращается только для EET.

import java.util.*;
import java.text.*;

public class DateTest
{

   public static void main (String[] args)
   {
       
       System.out.println("=======Test 1 : using EST=======");
       isValidTimeZone("EST");
       TimeZone.setDefault(TimeZone.getTimeZone("EST"));
       runTest();
       
       System.out.println("=======Test 2 : using PST=======");
       isValidTimeZone("PST");
       TimeZone.setDefault(TimeZone.getTimeZone("PST"));
       runTest();

       System.out.println("=======Test 3 : using EET=======");
       isValidTimeZone("EET");
       TimeZone.setDefault(TimeZone.getTimeZone("EET"));
       runTest();
       
   }
   private static void isValidTimeZone(String tz)
   {
       String[] validIDs = TimeZone.getAvailableIDs();
       boolean validTZ = false;
       for (String str : validIDs) {
             if (str != null && str.equals(tz)) {
               validTZ = true;
               break;
             }
       }
       
       if (validTZ)
       {
           System.out.println(tz + " is a Valid Time Zone");
       }
       else
       {
           System.out.println(tz + " is **NOT** a Valid Time Zone");
       }
   }
   
   private static void runTest()
   {
       try
       {
        String myDateString = new Date().toString();
        System.out.println("     Default Date String : " + myDateString);
        MyObjectWithADate myObject = new MyObjectWithADate(new Date(myDateString));
       }
       catch(Exception e)
       {
           System.out.println("     Object NOT Created!!!!!");
          e.printStackTrace(System.out);
       }

   }
}

    public MyObjectWithADate (Date eventDate)
    {
        System.out.println("     Passed in Date :      " + eventDate.toString());
//      this.eventDate = eventDate;
        try {
            this.eventDate = DateFormat.getInstance().parse(eventDate.toString());
            System.out.println("     Object Created");
        } catch (ParseException e) {
            System.out.println("     Object NOT Created");
            e.printStackTrace();
        }
        
    }
}   

Вот результат.

введите здесь описание изображения

Основываясь на документах Java 11, он комментирует, что Date устарела и что следует использовать DateFormat.parse().

В качестве теста код объекта был изменен для использования DateFormat.parse, но это только ухудшило ситуацию.

    public MyObjectWithADate (Date eventDate)
    {
        System.out.println("     Passed in Date :      " + eventDate.toString());
 //     this.eventDate = eventDate;
        try {
            this.eventDate = DateFormat.getInstance().parse(eventDate.toString());
            System.out.println("     Object Created");
        } catch (ParseException e) {
            System.out.println("     Object NOT Created");
            e.printStackTrace();
        }
        
    }
}   

Вот новые результаты.

введите здесь описание изображения

Вопросы 1. Из какой переменной среды JVM получает часовой пояс?

Вопрос 2. Почему при использовании исходного кода возникает исключение, когда используется тот же формат, что и в JVM?

Вопрос 3. Что именно такого в EET, что вызывает его сбой, а также EST и PST и их замену без проблем?

Вопрос 4. Если я хочу, чтобы исходный код мог запускаться кем угодно в любом часовом поясе, что нужно изменить?

ОТРЕДАКТИРОВАНО ДЛЯ ДОБАВЛЕНИЯ СЛЕДУЮЩЕГО:

Приведенный выше код представляет собой уменьшенную модель. К сожалению, фактический код не может быть изменен во всех местах, чтобы изменить использование объекта Date.

Я провел еще один тест, используя SimpleDateFormat в объекте MyObjectWithADate. Это снова работает для EST и PST, но не для EET.

class MyObjectWithADate 
{
        private Date        eventDate;
        
        public MyObjectWithADate (Date eventDate)
        {
            System.out.println("     Passed in Date :      " + eventDate.toString());
            String datePattern = new String ("E MMM dd HH:mm:ss z yyyy");
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat(datePattern);
            try {
                this.eventDate = simpleDateFormat.parse(eventDate.toString());
                System.out.println("     Object Created");
            } catch (ParseException e) {
                System.out.println("     Object NOT Created");
                e.printStackTrace();
            }
            System.out.println("     Object Created");
        }
}   

Я начинаю думать, что мой первоначальный вопрос должен был быть, как взять следующую строку

Пт 26 марта 21:42:52 EET 2021

и поместите его в объект Date.


person Unhandled Exception    schedule 26.03.2021    source источник
comment
1) baeldung.com/java-jvm-time-zone   -  person Andy Turner    schedule 26.03.2021
comment
Совершенно не связанный и не решает ни один из вышеперечисленных вопросов. Не заинтересован ни в установке часового пояса в коде, ни в передаче в качестве настройки.   -  person Unhandled Exception    schedule 26.03.2021
comment
Вы говорите, что раздел 3.1. Установка переменной среды не говорит вам, какая переменная среды устанавливает часовой пояс? Пожалуйста, не будьте так сразу пренебрежительны.   -  person Andy Turner    schedule 26.03.2021
comment
1 - Поскольку TZ не установлен в моей среде, мне было любопытно, откуда взялся этот формат, но с тех пор я обнаружил, что на самом деле это формат объектов Date по умолчанию.   -  person Unhandled Exception    schedule 26.03.2021
comment
2 - В приведенном выше коде он явно устанавливает часовой пояс для EST и PST и ведет себя так, как ожидалось. Но эта ссылка не дает никаких указаний на то, почему сделать то же самое с EET не удается. Во всех 3 сценариях он использует формат по умолчанию и распечатывает формат по умолчанию, но распознает только 2 или 3 при использовании этого значения для создания новой даты.   -  person Unhandled Exception    schedule 26.03.2021
comment
Я рекомендую вам не использовать TimeZone, Date и DateFormat. Эти классы плохо спроектированы и давно устарели, последний, в частности, печально известен своими трудностями. Вместо этого используйте java.time, современный API даты и времени Java.   -  person Ole V.V.    schedule 26.03.2021
comment
Реальные часовые пояса имеют имена в формате Continent/Region, например America/Los_Angeles, Africa/Tunis и Pacific/Auckland. EST, PST, EET не являются часовыми поясами.   -  person Basil Bourque    schedule 26.03.2021


Ответы (2)


Вопрос 1:

Вот соответствующий документ

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

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

Вопросы 2 , 3 и 4:

Исключение для EET возникает при использовании устаревшей функции new Date(myString). Я не уверен во внутренней работе этого метода. Но зачем беспокоиться? Он устарел по какой-то причине.

Если вы хотите создать объект даты из входной строки. На StackOverflow уже есть масса ответов по этому поводу. Вам действительно не нужно использовать new Date(string) в любом случае. Вот два быстрых решения, чтобы получить то, что вы хотите. Я предполагаю, что вы хотите, чтобы ваша строка даты была отформатирована так, как java.util.Date напечатает ее. Если это не так, используйте эту ссылку для создания собственного формата.

<сильный>1. Использование java.util.Date (старое, и его лучше избегать):

SimpleDateFormat oldJavaDateFormat = new SimpleDateFormat("EEE LLL dd HH:mm:ss z yyyy");
TimeZone.setDefault(TimeZone.getTimeZone("EET"));

//Surround with try/catch or throw
Date eventDate = oldJavaDateFormat.parse(new Date().toString());
//or if you need a date from some given date string
oldJavaDateFormat.parse("Thu Jan 13 15:13:13 EET 2033");

Результат:

Fri Mar 26 20:52:40 EET 2021

<сильный>2. Использование нового java.time (предпочтительно):

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("EEE LLL dd HH:mm:ss z yyyy");
ZonedDateTime eventDate = ZonedDateTime.parse(new Date().toString(), formatter).withZoneSameLocal(ZoneId.of("EET"));
//or directly from a string
eventDate = ZonedDateTime.parse("Thu Jan 13 15:13:13 EET 2033", formatter);

Результат:

2021-03-26T20:52:40+02:00[EET]

или для второго случая (данная строка):

2033-01-13T15:13:13+02:00[Europe/Bucharest]

ИЗМЕНИТЬ:

Используя первое решение выше, нужно быть осторожным с часовыми поясами. Вызов parse() для объекта SimpleDateFormat может работать не так, как предполагалось. Вы можете потерять информацию о часовом поясе.

person jrook    schedule 26.03.2021
comment
Спасибо, я добавил приведенный выше пример для использования SimpleDateFormat и включил ожидаемый формат. И снова это сработало для EST и PST, но не для EET. Может быть, за кулисами EET есть что-то, что вызывает конфликт? - person Unhandled Exception; 26.03.2021
comment
Я считаю, что частично рассмотрел это в ответе. new Date(string) устарел, а это означает, что программистам настоятельно не рекомендуется его использовать. Если бы не было абсолютно никаких других альтернатив, исследование, чтобы найти причину странности для случая EET, стоило того. Я не понимаю смысла делать это, когда есть гораздо, намного, гораздо лучшие решения. - person jrook; 26.03.2021
comment
К сожалению, из-за ограничений объект Date по-прежнему необходимо использовать. Я попытался выполнить ваш пункт 1 выше, и он все еще не работает для EET. - person Unhandled Exception; 26.03.2021
comment
Спасибо. Возникла проблема с моим кодом. У меня сейчас работает. - person Unhandled Exception; 26.03.2021
comment
@UnhandledException, на самом деле есть проблема с первым решением. java.util.Date удаляет информацию о часовом поясе после метода parse, поэтому нам все равно придется использовать setTimeZone(). Я обновил свой ответ. Но нет необходимости в этом изменении с помощью метода java.time. - person jrook; 26.03.2021

EST, PST и EET не являются часовыми поясами

  • EST is an abbreviation of Eastern Standard Time, which in turn may refer to either Australian Eastern Standard Time and North American Eastern Standard Time.
    • In most of the places in North America using EST during standard time, Eastern Daylight Time (EDT) is used during summer time (DST), which is the greater part of the year. So EST in this sense may be regarded as half a time zone.
    • Также в некоторых часовых поясах в Австралии большую часть года используется EST, EDT или AEDT.
  • PST может быть аббревиатурой стандартного времени Питкэрна, стандартного тихоокеанского времени и стандартного времени Филиппин. Все места в Северной Америке, использующие стандартное тихоокеанское время, в настоящее время используют тихоокеанское летнее время.
  • EET не может быть двусмысленным и может просто относиться к восточноевропейскому времени, общему названию нескольких часовых поясов, большинство из которых большую часть года используют восточноевропейское летнее время.

Допустимые часовые пояса в Java

В Java вы должны использовать идентификаторы часовых поясов, такие как Австралия/Сидней, Тихий океан/Питкэрн и Европа/Бухарест. Всегда используйте формат регион/город. Они обозначают недвусмысленные, реальные часовые пояса. В некоторых случаях вы можете убедить Java интерпретировать трехбуквенную аббревиатуру как часовой пояс, но, поскольку они так часто неоднозначны и часто не соответствуют реальным часовым поясам, это не рекомендуется. Кроме того, когда они распознаются как идентификаторы часовых поясов, они устаревают как таковые.

Чтобы определить, распознает ли Java данный идентификатор часового пояса как действительный, используйте класс ZoneId из java.time, современный API даты и времени Java и его метод of. Например:

    try {
        ZoneId.of("EST");
        System.out.println("EST is a valid time zone");
    } catch (ZoneRulesException zre) {
        System.out.println("EST is *not* a valid time zone");
    }

Выход:

EST is *not* a valid time zone

Это скажет вам, что Австралия/Сидней, Тихоокеанский регион/Питкэрн, Европа/Бухарест, а также EET являются действительными часовыми поясами. Он также скажет вам, что PST не является.

Для использования со старым кодом, который использует устаревшие сокращения часовых поясов, ZoneId имеет дополнительную поддержку некоторых из них через свою константу SHORT_IDS.

    System.out.println("" + ZoneId.SHORT_IDS.containsKey("EST")
            + " -> " + ZoneId.SHORT_IDS.get("EST"));
    System.out.println("" + ZoneId.SHORT_IDS.containsKey("PST")
            + " -> " + ZoneId.SHORT_IDS.get("PST"));
    System.out.println("" + ZoneId.SHORT_IDS.containsKey("EET")
            + " -> " + ZoneId.SHORT_IDS.get("EET"));
true -> -05:00
true -> America/Los_Angeles
false -> null

Мы заметили, что EST непоследовательно понимается как североамериканское восточное стандартное время в течение всего года, в то время как PST понимается как североамериканское тихоокеанское стандартное время в стандартное время и тихоокеанское летнее время летом. EET поддерживался без SHORT_IDS, поэтому здесь он не повторяется.

Почему вы наблюдали удивительные результаты

Сокращения, напечатанные Date.toString(), отличаются от тех, которые распознаются ZoneId и TimeZone. Вы заметили, что PDT было напечатано для Date, когда PST было установлено в качестве часового пояса по умолчанию. Изменить: другой пример: PRC - это устаревший идентификатор часового пояса. TimeZone узнает его. Date печатает как CST. КНР означало Китайскую Народную Республику. По центральному поясному времени Date означало стандартное время Китая, а TimeZone означало бы CST как центральное североамериканское стандартное время.

Единственными документально подтвержденными часовыми поясами, распознаваемыми устаревшим конструктором Date(String), являются GMT, UT, UTC, EST, CST, MST, PST, EDT, CDT, MDT и PDT. Это объясняет, почему EET не сработало. Я полагаю, что это отвечает на ваши вопросы 2. и 3.

Аббревиатуры часовых поясов, распознаваемые SimpleDateFormat, — это совсем другая история. Они зависят от локали, поскольку каждая локаль может иметь свою собственную аббревиатуру для данного часового пояса. То, как интерпретируются неоднозначные сокращения часовых поясов, не задокументировано, но известно, что оно нестабильно. На разных установках Java наблюдались разные результаты для одной и той же аббревиатуры.

Наконец, синтаксис, ожидаемый (или созданный) DateFormat.getInstance(), также зависит от локали, поэтому я бы не ожидал, что он распознает любую из ваших строк. Чего также не было в вашем коде. В моей локали DateFormat.getInstance() ожидает, например, строку типа "27/03/2021 15.04".

В любом случае, я рекомендую вам забыть о классах TimeZone, Date, SimpleDateFormat и DateFormat. Все они плохо спроектированы и давно устарели. И я рекомендую вам принять это как частичный ответ на вопрос 4.

Кроме того, динамическая установка часового пояса по умолчанию для вашей JVM очень опасна. Это изменение затрагивает все части ваших программ и другие программы, работающие в той же JVM, поэтому вы рискуете получить сюрпризы, которые будет очень сложно отследить до вашего кода. Я рекомендую вам не делать этого.

Вопрос 4: Если я хочу, чтобы исходный код мог запускаться кем угодно в любом часовом поясе, что нужно изменить?

Прежде всего, я рекомендую вам использовать java.time для всей вашей работы с датой и временем.

Если вы можете контролировать, какие идентификаторы часовых поясов используются:

public static void test(String timeZoneId) {
    try {
        ZonedDateTime now = ZonedDateTime.now(ZoneId.of(timeZoneId));
        System.out.println(now);
    } catch (ZoneRulesException zre) {
        System.out.println("Not a valid time zone ID: " + timeZoneId);
    }
}

Попробуйте:

    test("Australia/Sydney");
    test("Pacific/Pitcairn");
    test("Europe/Bucharest");
2021-03-27T07:49:27.095653+11:00[Australia/Sydney]
2021-03-26T12:49:27.100300-08:00[Pacific/Pitcairn]
2021-03-26T22:49:27.101219+02:00[Europe/Bucharest]

Если вам нужно приспособить устаревшие сокращения часовых поясов, я очень не уверен, что предложить, поскольку очень неясно, что пользователь ожидает от них. Если есть какой-то способ, отклоните их. Если вам нужно сделать сомнительное предположение, вы можете попробовать добавить SHORT_IDS в качестве второго аргумента к ZoneId.of():

        ZonedDateTime now = ZonedDateTime.now(ZoneId.of(timeZoneId, ZoneId.SHORT_IDS));

Держите пальцы скрещенными и попробуйте:

    test("EST");
    test("PST");
    test("EET");
2021-03-26T15:55:15.225727-05:00
2021-03-26T13:55:15.363421-07:00[America/Los_Angeles]
2021-03-26T22:55:15.367676+02:00[EET]

По крайней мере, результат на 100 % задокументирован и воспроизводим и не зависит от локали и часового пояса по умолчанию. Поэтому, если пользователь хочет что-то еще, должна быть возможность изменить это.

Если вам нужно toString результат и проанализировать его, ZonedDateTime также может сделать это стабильно. И без всяких форматтеров. И независимо от локали и часового пояса по умолчанию. Например:

    ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("America/Atikokan"));
    System.out.println("Original:    " + zdt);
    String asString = zdt.toString();
    ZonedDateTime parsedBack = ZonedDateTime.parse(asString);
    System.out.println("Parsed back: " + parsedBack);
Original:    2021-03-26T16:00:27.867880-05:00[America/Atikokan]
Parsed back: 2021-03-26T16:00:27.867880-05:00[America/Atikokan]

Если вам необходимо проанализировать вывод из Date.toString(), какой бы сомнительной ни была такая попытка, см. вторую ссылку внизу. Хороший ответ, кажется, интерпретирует EST как Америка/Нью-Йорк, PST как Америка/Лос-Анджелес и EET как Европа/Бухарест.

Ссылки

  1. учебное пособие по Oracle: дата и время, объясняющее, как использовать java.time.
  2. Как преобразовать тип даты Java String «EEE MMM dd HH:mm:ss zzz yyyy» в Java util.Date «yyyy-MM-dd» [дубликат]
person Ole V.V.    schedule 26.03.2021
comment
Я считаю, что основная проблема в этом вопросе заключается в использовании устаревшего метода new Date(string). Он не генерирует исключение для строк, содержащих PST, но делает это для EET. Кто знает почему, и кого это действительно волнует? - person jrook; 26.03.2021
comment
@jrook Я согласен, что никому не нужно заботиться. Тем не менее я отредактировал и отметил, что это задокументированное поведение. - person Ole V.V.; 26.03.2021
comment
Отличное объяснение. Еще одна причина избегать java.util.Date, если он кому-то еще нужен. - person jrook; 26.03.2021
comment
Выдающийся. Спасибо. - person Unhandled Exception; 27.03.2021