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.