ZonedDateTime.parse(...) vs ZonedDateTime.of(...)

Viewed 453

I see a difference I don't understand between ZonedDateTime.parse(...) and ZonedDateTime.of(...). Here is the example:

    private static final DateTimeFormatter DATE_TIME_FORMAT = new DateTimeFormatterBuilder()
            .parseCaseInsensitive()
            .appendPattern("dd-MMM-yyyy HH:mm:ss z")
            .toFormatter();

    @Test
    public void dates() {
        assertEquals(
                ZonedDateTime.of(2001, 10, 1, 17, 7, 15, 0, ZoneId.of("EST", ZoneId.SHORT_IDS)),
                ZonedDateTime.parse("01-OCT-2001 17:07:15 EST", DATE_TIME_FORMAT));
    }

The result to my surprise is not passing test but rather:

java.lang.AssertionError: 
Expected :2001-10-01T17:07:15-05:00
Actual   :2001-10-01T17:07:15-04:00[America/New_York]

Any tips on what is going on please?

Edit: It seems that the problem is that in parse(...) method the EST gets resolves as ZoneRegion where the ZoneId.SHORT_IDS maps EST to ZoneOffset. I would be interested if there is a reason behind this inconsistency.

3 Answers

The problem is simple.

EST doesn't mean what you think it means. That's because nobody really knows what it means; it is ambiguous and context must provide what you mean.

There are 3 major meanings and layers to it:

  1. It's a three letter acronym, tons of places use this. Australia has an Eastern Standard Time. They call this EST. The rest of the world, if the use an acronym at all, use AEST. At least this one is (probably) not the problem here, but note that if you parse that string using an australian locale, I wouldn't be too surprised if it saw EST and decided that this means 'Australia/Sydney'.

  2. It is a fixed zone offset of UTC-5: "Eastern Standard Time", as in, the timezone observed by New York (amongst other locations) in winter. In contrast to EDT, which is a fixed offset of UTC-4, called "Eastern Daylight savings Time", and is what New York is on, but in summer. Note that New York went from EDT to EST on Nov 1st, so at the time you've provided, New York was not on EST as far as this definition is concerned.

  3. It is the time in New York regardless of year: An zone and not an offset; it is UTC-5 during winter, and UTC-4 in summer, switching according to USA political rules.

I'd say definition #2 is the most sensible, but both others are common enough that you can't just disregard them as wrong.

Parsing a timezone string involves a locale and that means you have no idea what it is going to do, when you provide something so very ambiguous.

Turns out that on your system's default locale, EST ends up parsing as that 3rd definition, so it gets turned into the ZoneId America/New_York. I wonder if you meant UTC-5 (you probably didn't; almost no location on the planet observes UTC-5 at the time you provided!), it sounds more plausible you actually meant the third definition.

ZoneId.of("EST", ZoneId.SHORT_IDS) uses the second definition.

Thus, you have two different concepts here, even though they are both using the acronym EST.

I concur with Arvind's conclusion: Stop using TLAs for this stuff, they are just far too unclear. Use the full names, like America/New_York.

I have provided this answer to explain to you why you are seeing non-equal objects here.

Avoid using the three-letter timezone name and use the full name e.g. America/New_York.

import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.util.Locale;

public class Main {
    public static void main(String[] args) {
        final DateTimeFormatter DATE_TIME_FORMAT = new DateTimeFormatterBuilder()
                                                    .parseCaseInsensitive()
                                                    .appendPattern("dd-MMM-yyyy HH:mm:ss z")
                                                    .toFormatter(Locale.ENGLISH);// Do not miss Locale
        System.out.println(ZonedDateTime.of(2001, 10, 1, 17, 7, 15, 0, ZoneId.of("America/New_York"))
                .equals(ZonedDateTime.parse("01-OCT-2001 17:07:15 America/New_York", DATE_TIME_FORMAT)));
    }
}

Output:

true

Also, you should always use Locale when using DateTimeFormatter because different locales may have different texts for the same pattern.

There are two great answers already. I have just a slight bit to add. How would you go about getting the result that you said you expected?

I don’t think you can persuade ZonedDateTime.parse() to parse EST into the -05:00 that ZoneId.SHORT_IDS translates it to. But you can at least parse it into a time zone that uses offset -05:00:

private static final DateTimeFormatter DATE_TIME_FORMAT = new DateTimeFormatterBuilder()
        .parseCaseInsensitive()
        .appendPattern("dd-MMM-yyyy HH:mm:ss ")
        .appendZoneText(TextStyle.SHORT, Set.of(ZoneId.of("America/Coral_Harbour")))
        .toFormatter(Locale.ENGLISH);

Now your string is parsed into:

2001-10-01T17:07:15-05:00[America/Coral_Harbour]

You notice that the offset is now the expected -05:00. But the time zone is America/Coral_Harbour, which was not the time zone of your expected ZonedDateTime, so they still are not equal.

Coral Harbour is one of a few places in North America that use Eastern Standard Time all year — so no summer time/DST.

The two-arg appendZoneText method that I used takes a set of preferred time zones. If it can fit the abbreviation in the string to one of those time zones, it does. If it cannot, it just takes a time zone that fits. So it doesn’t work to pass a set containing, say, ZoneId.of("-05:00") because this isn’t abbreviated EST.

Related