Почему момент времени UTC не конвертируется в BST?

У меня есть следующий код:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.UK);
Instant inst = DateTimeUtils.toInstant(sdf.parse("2019-08-13T18:00:00Z"));
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm").withLocale(Locale.UK).withZone(ZoneId.of("Europe/London"));
System.out.println(formatter.format(inst));
//prints 18:00

Это удивительно для меня, как я думал inst будет время GMT/UTC и formatter отформатировал бы его по лондонскому времени (которое является BST (UTC+1:00) для этой даты), производя 19:00,

Что мне здесь не хватает?

Я предполагаю, что это общая проблема с моим кодом, но если это имеет значение, это использование org.threeten.bp.* классы из проекта ThreeTen-Backport, дополнительно адаптированные для ранней версии Android в проекте ThreeTenABP.

1 ответ

Решение

ТЛ; др

Instant                                    // Represent a moment in UTC.
.parse(                                    // Generate a `Instant` object from the content of text input.
    "2019-08-13T18:00:00Z"                 // String in standard ISO 8601 format.
)                                          // Returns a `Instant` object.
.atZone(                                   // Adjust from UTC to the wall-clock time used by the people of a particular region (a time zone). 
    ZoneId.of( "Europe/London" )           // Specify a time zone using name in proper `Continent/Region` format. Never use 2-4 letter pseudo-zones such as `BST`.
)                                          // Returns a `ZonedDateTime` object.
.toLocalTime()                             // Extract the time-of-day, without a date and without a time zone or offset. Returns a `LocalTime` object.
.format(                                   // Generate text representing the content of this `LocalTime` object. 
    DateTimeFormatter
    .ofLocalizedTime ( FormatStyle.SHORT ) // Automatically localize while generating a `String`.
    .withLocale ( Locale.UK )              // Locale determines the human language and cultural norms to use in localizing.
)                                          // Returns a `String` object.

19:00

Избегайте устаревших классов даты и времени

Вы смешиваете ужасные классы наследия (SimpleDateFormat, Date) с современными классами java.time. Не делай этого. Используйте только java.time.

Instant = момент в UTC

Пропустите первые две строки кода. Ваша входная строка "2019-08-13T18:00:00Z" в стандартном формате ISO 8601. Эти стандартные форматы по умолчанию используются классами java.time при разборе / генерации строк. Поэтому нет необходимости указывать шаблон форматирования.

String input = "2019-08-13T18:00:00Z" ;
Instant instant = Instant.parse( input ) ;

instant.toString (): 2019-08-13T18: 00: 00Z

Instant не гибкий

Я подозреваю, что ваша проблема была в вашей попытке отформатировать значение в Instant, Instant класс - это базовый класс строительных блоков в java.time. Это просто представляет момент в UTC. Он не предназначен для таких вещей, как гибкая генерация строк.

Более гибкие классы OffsetDateTime & ZonedDateTime классы.

ZonedDateTime

Применить ZoneId на ваш Instant настроить в часовой пояс, оказывая ZonedDateTime объект.

ZoneId z = ZoneId.of( "Europe/London" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;

zdt.toString (): 2019-08-13T19: 00 + 01: 00 [Европа / Лондон]

Вы, кажется, хотите сосредоточиться только на времени суток. Извлечь LocalTime объект.

LocalTime lt = zdt.toLocalTime ();

lt.toString (): 19:00

Для лондонского региона в летнее время (DST) на эту дату смещение от UTC на один час вперед. Итак, мы видим, что время дня 7 вечера против 6 вечера UTC.

Правильный часовой пояс

Кстати BST это не часовой пояс. Я предлагаю вам избегать использования этих псевдо-зон.

Укажите правильное название часового пояса в формате Continent/Region, такие как America/Montreal, Africa/Casablanca, или же Pacific/Auckland, Никогда не используйте 2-4 буквенное сокращение, такое как BST или же EST или же IST поскольку они не являются истинными часовыми поясами, не стандартизированы и даже не уникальны (!).

ZoneId z = ZoneId.of( "Africa/Tunis" ) ;  

Используйте умные объекты, а не тупые строки

Ваш пример кода предполагает, что вы слишком сосредоточены на строках. Используйте смарт-объекты, а не тупые строки.

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

локализация

И если говорить о локализации:

Locale locale = Locale.UK;
DateTimeFormatter f = DateTimeFormatter.ofLocalizedTime ( FormatStyle.MEDIUM ).withLocale ( locale );
String output = lt.format ( f );

19:00:00

Переключите язык на Locale.US для другого вида результата:

7:00:00 вечера


Весь приведенный выше код был запущен в раннем доступе Java 13 с библиотекой ThreeTen-Backport в соответствии с вашими потребностями, указанными в Вопросе.

import org.threeten.bp.* ;
import org.threeten.bp.format.* ;

Примечание для читателя: библиотека ThreeTen-Backport дополнительно адаптирована для ранних версий Android в библиотеке ThreeTenABP. См. Как использовать ThreeTenABP в Android. Если вы используете Android 26 и более поздние версии, классы java.time связаны, поэтому вам вообще не нужен бэк-порт.

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