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 с точки зрения шаблонов разбора/формата (как показано в этом примере, они несовместимы).

Ответ 1

Формат, который вы видите "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.

Ответ 2

Вы не должны работать с 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

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

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

Ответ 3

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

    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));
    }