DateTimeFormatter несовместим с SimpleDateFormat

import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;


public class Test001 {

    public static void main(String[] args) {
        Date dt = new Date();
        LocalDateTime localDateTime = LocalDateTime.now();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS zzz").withZone(ZoneId.of("America/New_York"));
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS zzz");

        System.out.println(formatter.format(localDateTime));
        System.out.println(sdf.format(dt));
    }

}

Выход

2016-04-19 11:25:34.917 ET
2016-04-19 11:25:34.637 EDT

Часовой пояс моего ноутбука — "Америка/Нью-Йорк" (т. е. восточное время США/Канады).

Интересно, как получить "EDT" вместо "ET" с помощью DateTimeFormatter.
Мне также интересно в целом: почему DateTimeFormatter не совместим с SimpleDataFormat с точки зрения шаблонов синтаксического анализа/форматирования (как показывает этот пример, они не совместимы ).


person peter.petrov    schedule 19.04.2016    source источник
comment
@Mr.Polywhirl Хорошо, но вокруг также довольно много устаревшего кода. И мы можем захотеть сохранить старые форматы.   -  person peter.petrov    schedule 19.04.2016
comment
По сути, это несовместимо, потому что вы должны использовать его дольше. :п   -  person Mr. Polywhirl    schedule 19.04.2016
comment
Технически Америка/Нью-Йорк — это не восточноевропейское время, а восточноевропейское время. На данный момент это также EDT, но зимой это будет EST. Так что они явно не эквивалентны.   -  person assylias    schedule 19.04.2016
comment
@assylias Без обид, но мне все равно, что это технически. Меня волнует, что у меня есть тонны кода Java 6,7, создающего EST/EDT, и теперь я не знаю, как создать то же самое с java.time Java 8. Я думал, что java.time будет совместим со старым способом (по крайней мере, что касается формата и синтаксического анализа).   -  person peter.petrov    schedule 19.04.2016
comment
@peter.petrov Я понимаю - я говорю, что EDT (или EST) на самом деле не являются часовыми поясами - поэтому они недоступны. Таким образом, вам, вероятно, придется либо придерживаться дат, либо вручную изменить часовой пояс (например, проверить, включено ли летнее время). Хотя может у кого есть решение...   -  person assylias    schedule 19.04.2016
comment
Понятно... да, я знаю, что технически это не часовые пояса. Спасибо.   -  person peter.petrov    schedule 19.04.2016
comment
К сожалению, у меня нет времени написать ответ, но по следующей ссылке объясняется, как работать с новым DateTimeFormatter и устаревшим кодом. Форматирование/анализ даты/времени, стиль Java 8. Надеюсь, вы справитесь с этой информацией.   -  person MicSim    schedule 19.04.2016


Ответы (3)


Формат, который вы видите «ET», известен как «Общий формат без указания местоположения» в Язык разметки данных локали Unicode (LDML). Формат, который вам нужен, «EDT», известен как формат «Конкретное не-местоположение».

Соответствующий исходный код в DateTimeFormatterBuilder проверяет чтобы увидеть, может ли объект даты и времени предоставить ChronoField.INSTANT_SECONDS. Если это возможно, то используется формат «конкретный без указания местоположения», если нет, то используется формат «общий без указания местоположения».

Поскольку вы форматируете LocalDateTime, что не поддерживает доступ к ChronoField.INSTANT_SECONDS, вы получаете формат «общий без местоположения». Чтобы получить желаемый результат, используйте ZonedDateTime. или вместо этого OffsetDateTime. (Мгновение необходимо, чтобы определить, летнее сейчас время или зимнее.)

Обратите внимание, что SimpleDateFormat и DateTimeFormatter различаются, и неверно предполагать шаблоны идентичны. Было принято решение повторно синхронизировать DateTimeFormatter со спецификацией LDML, что даст преимущества в будущем.

Этот ответ содержит решение и объяснение, однако я должен отметить, что код JDK здесь слишком суров. Поскольку вы предоставляете как LocalDateTime, так и ZoneId, код должен работать лучше и определять ChronoField.INSTANT_SECONDS на лету и, таким образом, использовать формат "конкретное не-местоположение". Таким образом, я думаю, что здесь есть крайний случай JDK.

person JodaStephen    schedule 19.04.2016
comment
Спасибо. Я посмотрю поближе, основываясь на вашем ответе. - person peter.petrov; 19.04.2016
comment
Если вы внимательно изучите спецификацию LDML, то обнаружите что символ шаблона z предназначен для обозначения определенного формата без указания местоположения, а не общего варианта. Вместо этого LDML требует символ v для общего формата без указания местоположения. Очевидно, что не существует библиотеки, включающей Java-8, которая поддерживает v. Если предполагается, что Java-8 точно следует за LDML, то z НЕ ДОЛЖЕН поддерживать общий вариант. Следовательно, от комбинации локальных типов, таких как LocalDateTime, с символом шаблона z лучше отказаться. - person Meno Hochschild; 20.04.2016
comment
Конечно, «z» - это действительно только конкретный формат. но LDML написан на основе концепции работы только с объектами типа java.util.Date/Instant/ZonedDateTime. В JSR-310 у нас есть больше типов, которые LDML не учитывал, например LocalDateTime. JSR-310 также не претендует на точное соответствие LDML — он используется в качестве руководства. Таким образом, обобщение спецификации для использования общей формы при недостаточном количестве данных вполне уместно. - person JodaStephen; 20.04.2016
comment
Я полностью разделяю ваш анализ намерения LDML сосредоточиться на мгновенных типах, но кажется, что ваш вывод не является единственно возможным. Даже в контексте локальных типов (таких как LocalDateTime) было бы ИМХО понятнее для пользователей, если бы они просто понимали z только как определенный формат без местоположения. Почему вы не поддерживаете символ v (= повторная синхронизация с LDML) вместо того, чтобы смешивать поведение z (обобщая, иначе изменяя поведение LMDL)? По крайней мере, документация ваших рассуждений была бы в порядке. Кстати, я еще не видел ни одного API-документа об универсальном формате без местоположения. - person Meno Hochschild; 21.04.2016

Вы не должны работать на LocalDateTime. (Почти) эквивалентом Date является Instant. Вы захотите отформатировать Instant так, как если бы он находился в вашем часовом поясе. Так

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS zzz").withZone(ZoneId.of("America/New_York"));
Instant now = Instant.now();
System.out.println(formatter.format(now));

который печатает

2016-04-19 12:07:57.684 EDT

Кроме того, вы можете использовать ZonedDateTime, не устанавливая ZoneId для DateTimeFormatter.

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS zzz");

ZonedDateTime now = ZonedDateTime.now(ZoneId.of("America/New_York")))
System.out.println(formatter.format(now));

который также печатает

2016-04-19 12:11:01.667 EDT

У меня нет документации о том, почему все это работает, но суть в том, что эти временные значения (объект даты, который вы пытаетесь отформатировать) имеют смещения, которые имеют правила зоны, которые могут быть летним или нет. Форматтер использует это при печати.

LocalDateTime не имеет смещения или часового пояса. Ваш модуль форматирования переопределяет его, но со смещением, отличным от летнего времени.

person Savior    schedule 19.04.2016
comment
Вы очень правильно заметили, сказав, что java.util.Date в первую очередь соответствует java.time.Instant. Поэтому, если OP жалуется на несовместимость между обоими механизмами форматирования, он должен по крайней мере позаботиться об использовании правильного эквивалентного типа для java.util.Date. В противном случае это утверждение о несовместимости не совсем справедливо (в данном контексте - не говоря уже о том, что есть несовместимости в других деталях - например, в разных символах узора). - person Meno Hochschild; 20.04.2016

Самый простой подход (и, вероятно, также вызывающий некоторые проблемы с летним/зимним временем) выглядит следующим образом:

    public static void main(String[] args) {
        Date dt = new Date();
        LocalDateTime localDateTime = LocalDateTime.now();

        String dateFormat;
        if (Calendar.getInstance().get(Calendar.DST_OFFSET) != 0) {
            dateFormat = "yyyy-MM-dd HH:mm:ss.SSS 'EDT'";
        } else {
            dateFormat = "yyyy-MM-dd HH:mm:ss.SSS 'EST'";
        }
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat).withZone(ZoneId.of("America/New_York"));
        SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);

        System.out.println(formatter.format(localDateTime));
        System.out.println(sdf.format(dt));
    }
person uniknow    schedule 19.04.2016
comment
Значит, когда наступит летнее время, бедному разработчику позвонят посреди ночи и изменят код мухи? - person Tunaki; 19.04.2016
comment
Уточненный ответ, учитывающий летнее время. - person uniknow; 20.04.2016
comment
@Tunaki Нет, Calendar.getInstance() использует часовой пояс по умолчанию с учетом летнего времени. Так что этот ответ, несомненно, очень неудобный и ограниченный (не прямолинейный для меня), но не совсем неправильный (хотя работает только в зоне Нью-Йорка и на английском языке). Скажем, недостаточно. - person Meno Hochschild; 20.04.2016