Проблема с часовым поясом: преобразовать в UTC, если часовой пояс JVM находится в UTC

Я сталкиваюсь с некоторыми проблемами из-за часового пояса в наших приложениях.

У нас есть несколько зависимых услуг, которые отправляют нам время EST. Мы были в часовом поясе EST, поэтому никаких проблем раньше не было. Недавно мы переехали в часовой пояс UTC и начали видеть проблему. Для нас мы могли бы снова пойти туда-сюда (EST/UTC).

Поэтому я хочу проверить часовой пояс JVM, и если мой часовой пояс - UTC, я хочу преобразовать время зависимой службы в UTC и ничего не делать, если мы находимся в EST, поскольку зависимая служба всегда отвечает EST. Как мне этого добиться?

Data1.java

XMLGregorianCalendar date;
XMLGregorianCalendar time;
//getter & setter

Util.java

String convertDate(Date d){
    SimpleDateFormat date = new SimpleDateFormat("yyyy-MM-dd");
            try {
                 String converted= dateFormat.format(d);
                return converted;
            } catch (ParseException e) {
                throw new RuntimeException(e);
            }
    }

    String convertTime(Date d){
            SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
            String Time = "";
            Time =  sdf.format(d);
            return Time;
    }
}

MyApp.java

String date = Util.convertDate(data1.getDate.toGregorianCalendar().getTime())
String time = Util.convertTime(data1.getDate.toGregorianCalendar().getTime())

Здесь, строка даты и времени здесь имеет дату и время EST, которые я хочу изменить на UTC, если часовой пояс JVM - UTC, и оставить его как часовой пояс JVM - EST.

Существующий пример вывода:

дата: 2017-08-02
время: 21:04:04

Ожидаемое:

дата: 2017-08-03
время: 01:04:04

2 ответа

java.util.Date Объект не имеет ни формата, ни информации о часовом поясе. Это просто держит long значение, представляющее количество миллисекунд с начала эпохи Unix (1970-01-01T00:00Z).

Когда вы форматируете Date с помощью SimpleDateFormat средство форматирования использует часовой пояс системы по умолчанию для преобразования этого значения в миллисекундах в удобочитаемые значения (день / месяц / год / час / минута / секунда).

Если вы получаете 2017-08-02 21:04:04 это означает, что часовой пояс по умолчанию в JVM, где выполняется код, - EST (технически говоря, EST на самом деле это не часовой пояс, подробнее об этом ниже). Если вы хотите, чтобы вывод был преобразован в UTC, вы должны установить его в форматере:

Date date = // Date corresponding to 2017-08-03 01:04:04 UTC (or 2017-08-02 21:04:04 EDT)

// date format
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
// set UTC
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
System.out.println(dateFormat.format(date)); // 2017-08-03

// time format
SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm:ss");
// set UTC
timeFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
System.out.println(timeFormat.format(date)); // 01:04:04

Выход будет:

2017-08-03
1:04:04

Чтобы получить значения для EST, просто измените часовой пояс:

// date format
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
// set to EST
dateFormat.setTimeZone(TimeZone.getTimeZone("America/New_York"));
System.out.println(dateFormat.format(date));

// time format
SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm:ss");
// set to EST
timeFormat.setTimeZone(TimeZone.getTimeZone("America/New_York"));
System.out.println(timeFormat.format(date));

Выход будет:

2017-08-02
21:04:04

Обратите внимание, что я использовал America/New_York так как EST дает неверные результаты. В идеале всегда следует использовать имена часовых поясов IANA (всегда в формате Region/City, лайк America/New_York или же Europe/Berlin). Избегайте использования трехбуквенных сокращений (например, EST или же PST) потому что они неоднозначны и не стандартны.

Есть много разных часовых поясов, которые используют EST как "короткое имя", так что вы можете изменить America/New_York в часовой пояс, который лучше всего подходит для вашей системы. Вы можете получить список доступных часовых поясов, позвонив TimeZone.getAvailableIDs(),

Чтобы проверить часовой пояс по умолчанию, вы можете использовать TimeZone.getDefault().getID() и проверьте, если это UTC или же GMT (поэтому вы знаете, какой формат использовать - хотя рекомендуется внутренне работать с UTC и преобразовывать его в часовой пояс только при отображении значений для пользователей, например).

В зависимости от часового пояса по умолчанию он не идеален, потому что его можно изменить в любое время, даже во время выполнения, или из-за неправильной настройки, сделанной кем-то другим (я сталкивался с подобными ситуациями раньше, и это не очень хорошая вещь). Но, как кажется, у вас нет выбора, просто проверьте значение, возвращаемое getID() и решить, что делать, основываясь на этом.


Java новый API даты и времени

Старые занятия (Date, Calendar а также SimpleDateFormat) есть много проблем и проблем дизайна, и они заменяются новыми API.

Если вы используете Java 8, рассмотрите возможность использования нового API java.time. Это проще, менее прослушивается и менее подвержено ошибкам, чем старые API.

Если вы используете Java <= 7, вы можете использовать ThreeTen Backport, отличный бэкпорт для новых классов даты / времени в Java 8. А для Android есть ThreeTenABP (подробнее о том, как его использовать здесь).

Код ниже работает для обоих. Разница лишь в именах пакетов (в Java 8 java.time и в ThreeTen Backport (или Android в ThreeTenABP) является org.threeten.bp), но имена классов и методов совпадают.

Сначала вы конвертируете GregorianCalendar для Instant:

// convert calendar
Instant inst = Instant.ofEpochMilli(data1.getDate.toGregorianCalendar().getTimeInMillis());

В Java 8 GregorianCalendar есть метод, чтобы сделать преобразование напрямую:

Instant inst = data1.getDate.toGregorianCalendar().toInstant();

Затем вы можете преобразовать момент в часовой пояс:

// convert to UTC
ZonedDateTime zdt = inst.atZone(ZoneOffset.UTC);

// or convert to a timezone
ZonedDateTime zdt = inst.atZone(ZoneId.of("America/New_York"));

Тогда вы можете использовать DateTimeFormatter отформатировать это:

DateTimeFormatter dateFmt = DateTimeFormatter.ofPattern("yyyy-MM-dd");
DateTimeFormatter timeFmt = DateTimeFormatter.ofPattern("HH:mm:ss");

System.out.println(dateFmt.format(zdt));
System.out.println(timeFmt.format(zdt));

Это даст тот же результат, что и выше.

Вы также можете проверить часовой пояс JVM по умолчанию, используя ZoneId.systemDefault().getId() и проверка, если это UTC или же GMT,

EST это не часовой пояс

3-4-буквенные псевдозоны, видимые в СМИ, не являются настоящими часовыми поясами. Они не стандартизированы и даже не уникальны (!).

Укажите правильное название часового пояса в формате continent/region, такие как America/Montreal, Africa/Casablanca, или же Pacific/Auckland,

ZoneId z = ZoneId.of( "America/New_York" );

избежать XMLGregorianCalendar

По возможности избегайте использования XMLGregorianCalendar, Как наследие Date & Calendar & GregorianCalendar классы, теперь они вытесняются вышестоящими классами java.time.

Никогда не зависеть от часового пояса по умолчанию

Плохая практика - зависеть от часового пояса операционной системы по умолчанию, так как он находится вне вашего контроля программиста.

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

Похоже, что ваши люди ожидают использовать текущий часовой пояс по умолчанию хост-ОС в качестве сигнала для изменения поведения вашего приложения. Очень странная и неуклюжая стратегия. У вас есть так много других доступных маршрутов: файлы конфигурации, JMX, JNDI и так далее.

Получение текущего часового пояса по умолчанию

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

Вы можете получить текущее значение по умолчанию для JVM. Большинство реализаций Java, с которыми я знаком, по умолчанию устанавливают для JVM хост-ОС по умолчанию.

ZoneId z = ZoneId.systemDefault() ;

Конвертировать в java.time

Если вам дают XMLGregorianCalendar объект, преобразовать в java.time.ZonedDateTime с помощью GregorianCalendar,

GregorianCalendar gc = myXMLGregorianCalendar.toGregorianCalendar() ;
ZonedDateTime zdt = gc.toZonedDateTime() ;

Отрегулируйте в желаемый / ожидаемый часовой пояс

От этого ZonedDateTime извлечь Instant, Instant класс представляет момент на временной шкале в UTC с разрешением наносекунд (до девяти (9) цифр десятичной дроби).

Instant instant = zdt.toInstant() ;

Применяйте любой часовой пояс по вашему желанию. Для UTC используйте OffsetDateTime,

OffsetDateTime odt = instant.atOffset( ZoneOffset.UTC ) ;

Получите ваши строки даты и времени в стандартном формате ISO 8601.

String outputDate = odt.toLocalDate().toString() ;
String outputTime = odt.toLocalTime().toString() ;

Для часового пояса, а не просто смещение от UTC, используйте ZonedDateTime, Для восточного побережья США, возможно, вы хотите America/New_York,

ZonedDateTime zdt = instant.atZone( ZoneId.of( "America/New_York" ) ) ;

Генерация строк в стандартном формате ISO 8601.

String outputDate = zdt.toLocalDate().toString() ;
String outputTime = zdt.toLocalTime().toString() ;

О java.time

Инфраструктура java.time встроена в Java 8 и более поздние версии. Эти классы вытесняют проблемные старые классы даты и времени, такие как java.util.Date, Calendar & SimpleDateFormat,

Проект Joda-Time, находящийся сейчас в режиме обслуживания, рекомендует перейти на классы java.time.

Чтобы узнать больше, смотрите Oracle Tutorial. И поиск переполнения стека для многих примеров и объяснений. Спецификация JSR 310.

Вы можете обмениваться объектами java.time напрямую с вашей базой данных. Используйте драйвер JDBC, соответствующий JDBC 4.2 или более поздней версии. Нет необходимости в строках, нет необходимости в java.sql.* классы.

Где взять классы java.time?

Проект ThreeTen-Extra расширяет java.time дополнительными классами. Этот проект является полигоном для возможных будущих дополнений к java.time. Вы можете найти некоторые полезные классы здесь, такие как Interval, YearWeek, YearQuarter и многое другое.

Другие вопросы по тегам