Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inconsistency in handling date and time in Java

I have noticed strange behavior of date and time in java. I have the following code:

public class TestDateTime {
    public static void main(String[] args) {
        TimeZone.setDefault(TimeZone.getTimeZone("Europe/Helsinki"));

        Calendar calendar = GregorianCalendar.getInstance();

        assert(calendar.getTimeZone().equals(TimeZone.getDefault()));
        //Set 1899-12-30T23:00:00
        calendar.set(1899,11,30,23,0,0);
        calendar.set(Calendar.MILLISECOND,0);

        long timeInMillis = calendar.getTimeInMillis();
        java.util.Date calendarDateTime = new java.util.Date(timeInMillis);

        LocalDateTime localDateTime = LocalDateTime.ofInstant(ofEpochMilli(timeInMillis), ZoneId.systemDefault());
        System.out.println("Time in millis: " + timeInMillis);
        System.out.println("Date: " + calendarDateTime.toString());
        System.out.println("Local DateTime: " + localDateTime.toString());
    }
}

The output is:

Time in millis: -2209086000000
Date: Sat Dec 30 23:00:00 EET 1899
Local DateTime: 1899-12-30T22:39:49

timeInMillis must contain the number of milliseconds passed from 1970-01-01T00:00:00Z. The instance of Date class stores number of milliseconds passed from 1970-01-01T00:00:00Z. Date.toString() method returns local date and time for the default timezone.

So the Date.toString() and LocalDateTime.toString() must return the same date and time, but we see the difference (more than 20 minutes).

Is this a bug of java, or I use date and time incorrectly in Java?

like image 757
Art Spasky Avatar asked Mar 17 '17 19:03

Art Spasky


2 Answers

This is a weirdness caused by Finland time change, see Clock Changes in Helsinki, Finland (Helsingfors) in 1921:

May 1, 1921 - Time Zone Change (HMT → EET)

When local standard time was about to reach Sunday, May 1, 1921, 12:00:00 midnight clocks were turned forward 0:20:11 hours to Sunday, May 1, 1921, 12:20:11 am local standard time instead

Those 20 minutes 11 seconds seem to be what you're observing.

As Jim Garrison said in his answer, LocalDateTime is correctly handling that, while Calendar is not.

In reality, it seems that the old TimeZone is getting the offset wrong, while the new ZoneId is getting it right, as can be seen in the following test code:

public static void main(String[] args) {
    compare(1800, 1, 1,  0, 0, 0);
    compare(1899,12,31, 23,59,59);
    compare(1900, 1, 1,  0, 0, 0);
    compare(1900,12,30, 23, 0, 0);
    compare(1921, 4,30,  0, 0, 0);
    compare(1921, 5, 1,  0, 0, 0);
    compare(1921, 5, 2,  0, 0, 0);
}
private static void compare(int year, int month, int day, int hour, int minute, int second) {
    Calendar calendar = new GregorianCalendar();
    calendar.clear();
    calendar.setTimeZone(TimeZone.getTimeZone("Europe/Helsinki"));
    calendar.set(year, month-1, day, hour, minute, second);
    Date date = calendar.getTime();
    
    ZonedDateTime zdt = ZonedDateTime.of(year, month, day, hour, minute, second, 0, ZoneId.of("Europe/Helsinki"));
    
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z XXX");
    sdf.setTimeZone(TimeZone.getTimeZone("Europe/Helsinki"));
    DateTimeFormatter dtf = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss z XXX");
    
    System.out.printf("%04d-%02d-%02d %02d:%02d:%02d   %s = %d   %s = %d   %d%n",
                      year, month, day, hour, minute, second,
                      sdf.format(date), date.getTime(),
                      dtf.format(zdt), zdt.toInstant().toEpochMilli(),
                      date.getTime() - zdt.toInstant().toEpochMilli());
}

Output

1800-01-01 00:00:00   1800-01-01 00:00:00 EET +02:00 = -5364669600000   1800-01-01 00:00:00 EET +01:39 = -5364668389000   -1211000
1899-12-31 23:59:59   1899-12-31 23:59:59 EET +02:00 = -2208996001000   1899-12-31 23:59:59 EET +01:39 = -2208994790000   -1211000
1900-01-01 00:00:00   1900-01-01 00:00:00 EET +02:00 = -2208996000000   1900-01-01 00:00:00 EET +01:39 = -2208994789000   -1211000
1900-12-30 23:00:00   1900-12-30 23:00:00 EET +01:39 = -2177548789000   1900-12-30 23:00:00 EET +01:39 = -2177548789000   0
1921-04-30 00:00:00   1921-04-30 00:00:00 EET +01:39 = -1536025189000   1921-04-30 00:00:00 EET +01:39 = -1536025189000   0
1921-05-01 00:00:00   1921-05-01 00:20:11 EET +02:00 = -1535938789000   1921-05-01 00:20:11 EET +02:00 = -1535938789000   0
1921-05-02 00:00:00   1921-05-02 00:00:00 EET +02:00 = -1535853600000   1921-05-02 00:00:00 EET +02:00 = -1535853600000   0
like image 96
Andreas Avatar answered Oct 27 '22 11:10

Andreas


LocalDateTime is CORRECT. According to the TZ database, the GMT offset at that date was 1:39:49:

# Zone  NAME        GMTOFF  RULES   FORMAT  [UNTIL]
Zone    Europe/Helsinki 1:39:49 -   LMT 1878 May 31
            1:39:49 -   HMT 1921 May    # Helsinki Mean Time
            2:00    Finland EE%sT   1983
            2:00    EU  EE%sT

Historical timezones are incredibly complex, and prior to standardization offsets were inherited from settings based on things like mean solar noon. When going back that far just about any offset is possible, and the IANA TZ database is the master reference for historical data.

From what I can see in the database, the weird offset did not get standardized to 2:00:00 until 1921 when HMT was replaced with EE(S)T.

like image 25
Jim Garrison Avatar answered Oct 27 '22 12:10

Jim Garrison