Путаница с разбором времени Java в формате UTC

Меня смущает обработка времени во времени Java. Я так долго работал в предположении, что если временная метка указана как время зулу, java позаботится о смещении относительно местного времени.

Проиллюстрировать. В настоящее время я нахожусь в BST со смещением UTC +1. Имея это в виду, я бы ожидал, что это время зулу:

2016-09-12T13:15:17.309Z

to be

2016-09-12T14:15:17.309 

LocalDateTime после его разбора. Это связано с тем, что мое системное время по умолчанию установлено на BST, а указанная выше временная метка (время зулу) указывает, что это время UTC.

Однако вместо этого рассмотрим этот образец:

        String ts = "2016-09-12T13:15:17.309Z";
        LocalDateTime parse = LocalDateTime.parse(ts, DateTimeFormatter.ISO_DATE_TIME);
        System.out.println(parse);

Это напечатает:

2016-09-12T13:15:17.309

Таким образом, отметка времени, проанализированная как LocalDateTime, не распознается как время UTC и вместо этого обрабатывается непосредственно как местное время. Поэтому я подумал, может быть, мне нужно разобрать его как ZonedDateTime и преобразовать в LocalDateTime специально для того, чтобы получить правильное местное время. С этим тестом:

        String ts = "2016-09-12T13:15:17.309Z";
        ZonedDateTime parse = ZonedDateTime.parse(ts, DateTimeFormatter.ISO_DATE_TIME);
        System.out.println(parse);
        System.out.println(parse.toLocalDateTime());

Я получаю результаты:

2016-09-12T13:15:17.309Z
2016-09-12T13:15:17.309

Тот же вывод для обеих дат.

Единственный способ правильно разобрать это, что я смог найти, это:

    String ts = "2016-09-12T13:15:17.309Z";
    Instant instant = Instant.parse(ts); // parses UTC
    LocalDateTime ofInstant = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
    System.out.println(instant);
    System.out.println(ofInstant);

Это печатает:

2016-09-12T13:15:17.309Z
2016-09-12T14:15:17.309

Что правильно.

Итак, вопрос(ы):

  • Разве время Java не должно распознавать временную метку UTC и анализировать ее по правильному системному умолчанию?
  • Как я могу использовать подход LocalDateTime#parse для получения правильного результата?
  • Должен ли я теперь использовать Instant для всего и отказаться от синтаксического анализа?

Проблема в том, что java-модули времени jersey/jackson анализируют метки времени, используя формат ISO и обычные методы LocalDateTime#parse. Я понял, что мое время не изменилось, так как к ним относятся как к LocalTime, хотя на самом деле они находятся в зулусском времени.


person pandaadb    schedule 12.09.2016    source источник


Ответы (2)


Вы неправильно понимаете назначение LocalDateTime.

Чтобы процитировать документацию класса:

Дата-время без часового пояса в календарной системе ISO-8601, например {@code 2007-12-03T10:15:30}.

Этот класс не хранит и не представляет часовой пояс. Вместо этого это описание даты, используемой для дней рождения, в сочетании с местным временем, которое видно на настенных часах. Он не может представлять мгновение на временной шкале без дополнительной информации, такой как смещение или часовой пояс.

Таким образом, его явная цель - просто представить дату и время без часового пояса. Он не предназначен для представления даты и времени в местном часовом поясе.

Поэтому каждое преобразование просто удаляет часовой пояс.

Итак, для ваших целей вам нужен ZonedDateTime с ZoneId.systemDefault(), как вы уже использовали в своем третьем примере.

Для вашего второго примера это может быть:

String ts = "2016-09-12T13:15:17.309Z";
ZonedDateTime parse = 
    ZonedDateTime.parse(ts, DateTimeFormatter.ISO_DATE_TIME)
        .withZoneSameInstant(ZoneId.systemDefault());
System.out.println(parse);
System.out.println(parse.toLocalDateTime());
person Michaela Maura Elschner    schedule 12.09.2016
comment
Ваш пример выглядит так, как будто он сделает мгновенный разговор моего примера 3. Вы правы, я неправильно понял, что означает LocalDateTime. тьфу .. часовые пояса :) Спасибо - person pandaadb; 12.09.2016
comment
Ах, так ZonedDateTime распознает часовой пояс как Z, который он автоматически интерпретирует как UTC (именно то, что я хочу) - это правильно? - person pandaadb; 12.09.2016
comment
@pandaadb Да, наверное, это просто еще один способ написать это. - person Michaela Maura Elschner; 12.09.2016
comment
@pandaadb Да, парсер правильно распознает его. Просто преобразование в LocalDateTime лишает его. - person Michaela Maura Elschner; 12.09.2016

tl;dr

Пример:

Instant.parse( "2016-09-12T13:15:17.309Z" )
       .atZone( ZoneId.of( "Europe/London" ) )
       .toString();

2016-09-12T14:15:17.309+01:00[Европа/Лондон]

Запускайте в IdeOne.com.

Подробности

Правильный ответ Krüske. Вы неправильно понимаете значение класса LocalDateTime. Он не представляет дату-время конкретной местности. Как раз наоборот, он не вообще не отражает реальный момент.

Я предлагаю рассматривать Instant как базовый класс строительных блоков в java.time. Класс Instant представляет момент на временной шкале. в UTC с разрешением наносекунды (до девяти (9) цифр десятичной дроби).

Ваша входная строка соответствует формату ISO 8601, используемому по умолчанию в классе Instant как для синтаксического анализа, так и для создания строковых представлений. Z в конце является сокращением от Zulu и означает UTC. Нет необходимости указывать шаблон форматирования.

Instant instant = Instant.parse( "2016-09-12T13:15:17.309Z" );

Как программист, вы должны научиться думать и работать в первую очередь в формате UTC. Забудьте о собственном часовом поясе. Думайте о UTC как об одном истинном времени. Применяйте часовой пояс в качестве варианта и только по мере необходимости.

Укажите правильное название часового пояса в формате continent/region, например America/Montreal, Africa/Casablanca или Pacific/Auckland. Никогда не используйте сокращения из 3-4 букв, такие как BST, EST или IST, так как они не являются настоящими часовыми поясами, не стандартизированы и даже не уникальны (!). Если под BST вы имели в виду Британское летнее время, то фактическое название часового пояса будет Europe/London. Классы java.time будут определять, как корректировать любые аномалии, включая переход на летнее время (DST).

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

О java.time

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

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

Чтобы узнать больше, см. учебник по Oracle. И поищите множество примеров и пояснений в Stack Overflow. Спецификация: JSR 310.

Где получить классы java.time?

  • Java SE 8 and SE 9 and later
    • Built-in.
    • Часть стандартного Java API со встроенной реализацией.
    • Java 9 добавляет некоторые незначительные функции и исправления.
  • Java SE 6 and SE 7
    • Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
  • Android

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

person Basil Bourque    schedule 23.10.2016