DateTimeFormatter and SimpleDateFormat produce different strings [duplicate]
Asked Answered
D

1

1

This is not a duplicate as some people think. It is about two standard Java classes for formatting dates that produce different strings for the same value of milliseconds since the epoch.

For values of milliseconds since the epoch that occur before some point in the year 1883, SimpleDateFormat and DateTimeFormatter will produce different results. For reasons I don't understand, DateTimeFormatter will produce strings that differ from what I expect by almost four minutes.

This is important because I am changing some code to use DateTimeFormatter instead of SimpleDateFormat. Our input is always milliseconds since the epoch, and I need the values to be the same after I change the code.

The previous code would create a Date from the milliseconds, then use SimpleDateFormat to format it.

The new code creates an Instant from the milliseconds, then a ZonedDateTime from the Instant, then a DateTimeFormatter to format it.

Here's a test I wrote using JUnit4 and Hamcrest. The test finds the milliseconds since the epoch for May 13, 15:41:25, for each year starting at 2019 and working backwards one year at a time.

For each year, it formats the milliseconds using SimpleDateFormat and DateTimeFormatter then compares the results.

@Test
  public void testSimpleDateFormatVersusDateTimeFormatter() throws Exception {
    String formatString = "EEE MMM dd HH:mm:ss zzz yyyy";
    String timeZoneCode = "America/New_York";

    ZoneId zoneId = ZoneId.of(timeZoneCode);

    SimpleDateFormat simpleDateFormat = new SimpleDateFormat(formatString);
    simpleDateFormat.setTimeZone(TimeZone.getTimeZone(timeZoneCode));

    DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(formatString);

    for (int year = 0; year < 200; year++) {

      long millis = getMillisSinceEpoch(2019 - year, 5, 13, 15, 41, 25, timeZoneCode);

      System.out.printf("%s%n", new Date(millis));

      // Format using a DateTimeFormatter;
      Instant instant = Instant.ofEpochMilli(millis);
      ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(instant, zoneId);
      String dateTimeFormatterString = dateTimeFormatter.format(zonedDateTime);

      // Format using a SimpleDateFormat
      Date date = new Date(millis);
      String simpleDateFormatString = simpleDateFormat.format(date);

      System.out.println("dateTimeFormatterString = " + dateTimeFormatterString);
      System.out.println("simpleDateFormatString = " + simpleDateFormatString);
      System.out.println();

      assertThat(simpleDateFormatString, equalTo(dateTimeFormatterString));
    }
  }

  private long getMillisSinceEpoch(int year, int month, int dayOfMonth, int hours, int minutes, int seconds, String timeZoneId) {
    TimeZone timeZone = TimeZone.getTimeZone(timeZoneId);
    Calendar calendar = Calendar.getInstance(timeZone);
    calendar.set(Calendar.YEAR, year);
    calendar.set(Calendar.MONTH, month-1);
    calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth);
    calendar.set(Calendar.HOUR_OF_DAY, hours);
    calendar.set(Calendar.MINUTE, minutes);
    calendar.set(Calendar.SECOND, seconds);
    return calendar.getTimeInMillis();
  }

Running this you can see it passes for all years from 2019 back to 1884. So for any given year you see output like this:

Mon May 13 12:41:25 PST 1895
dateTimeFormatterString = Mon May 13 15:41:25 EST 1895
simpleDateFormatString = Mon May 13 15:41:25 EST 1895

But once it gets to 1883 it inexplicably fails:

Sun May 13 12:41:25 PST 1883
dateTimeFormatterString = Sun May 13 15:45:23 EST 1883
simpleDateFormatString = Sun May 13 15:41:25 EST 1883


java.lang.AssertionError: 
Expected: "Sun May 13 15:45:23 EST 1883"
     but: was "Sun May 13 15:41:25 EST 1883"```

The hours and seconds are obviously wrong.

By the way, if I change the time zone to "UTC", then the test passes.

Dunigan answered 13/5, 2019 at 23:26 Comment(3)
Possible duplicate of Why when year is less than 1884, it remove few milliseconds? (Once I found the answer, it was easier to find a duplicate target here on SO.)Italicize
This is not a duplicate. It is about the fact that the old SimpleDateFormat and the newer DateTimeFormatter produce different results for the same value of milliseconds since the epoch.Dunigan
I agree with you, and I've voted to reopen.Italicize
I
8

According to https://www.timeanddate.com/time/change/usa/new-york?year=1883 (which was the first hit in a Google search for "1883 time adjustment"):

Nov 18, 1883 - Time Zone Change (LMT → EST)

When local standard time was about to reach Sunday, November 18, 1883, 12:03:58 pm clocks were turned backward 0:03:58 hours to Sunday, November 18, 1883, 12:00:00 noon local standard time instead.

3:58 matches the "almost four minutes" that you're seeing. I haven't tested this, but I bet that if you iterate through months and days in addition to years, it occurs at that date.

See Also

Italicize answered 13/5, 2019 at 23:34 Comment(1)
Actually, I think the bug is in SimpleDateFormat. The code above that formats two dates produces the same string from SimpleDateFormat and DateTimeFormatter for values greater than or equal to -2717650800000L. For values less than that, it appears SimpleDateFormat does not account for the time adjustment in 1883. But it's quite possible I'm missing something.Dunigan

© 2022 - 2024 — McMap. All rights reserved.