Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DateTimeFormatter not compatible with 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));
    }

}

Output

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

The timezone of my laptop is "America/New York" (i.e. Eastern Time US/Canada).

I wonder how to get "EDT" instead of "ET" by using DateTimeFormatter.
I wonder also in general: why is the DateTimeFormatter not compatible with SimpleDataFormat in terms of parse/format patterns (as this example shows they are not compatible).

like image 794
peter.petrov Avatar asked Apr 19 '16 15:04

peter.petrov


2 Answers

The format you are seeing "ET" is known as the "Generic non-location" format in Unicode Locale Data Markup Language (LDML). The format you want, "EDT", is known as the "Specific non-location" format.

The relevant source code in DateTimeFormatterBuilder checks to see if the date-time object can supply ChronoField.INSTANT_SECONDS. If it can, then the "specific non-location" format is used, if not then the "generic non-location" format is used.

Since you are formatting a LocalDateTime, which does not support access to ChronoField.INSTANT_SECONDS, you are getting the "generic non-location" format. To get the output you want, use ZonedDateTime or OffsetDateTime instead. (The instant is needed to determine whether it is summer or winter time.)

Note that SimpleDateFormat and DateTimeFormatter do differ and it is not correct to assume the patterns are identical. The decision was taken to resynchronize DateTimeFormatter with the LDML specification, which will have benefits in the future.

This answer provides a solution and an explanation, however I must note that the JDK code is being overly harsh here. Since you are supplying both a LocalDateTime and a ZoneId, the code should be able to do better, and determine ChronoField.INSTANT_SECONDS on the fly and thus use the "specific non-location" format. Thus, I think there is an edge case JDK issue here.

like image 64
JodaStephen Avatar answered Nov 07 '22 15:11

JodaStephen


You shouldn't be operating on a LocalDateTime. The (almost)equivalent of Date is Instant. You'll want to format an Instant as if it was in your timezone. So

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

which prints

2016-04-19 12:07:57.684 EDT

Alternatively, you can use a ZonedDateTime without setting a ZoneId for your 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));

which also prints

2016-04-19 12:11:01.667 EDT

I don't have the documentation for why this all works, but the gist is that these temporals (the date object you're trying to format) have offsets, which have zone rules, which can be daylight saving time or not. The formatter uses that in its printing.

A LocalDateTime does not have an offset or timezone. Your formatter overrides it but with an offset that is not daylight saving time.

like image 40
Savior Avatar answered Nov 07 '22 15:11

Savior