Search code examples
javadatejava-time

Converting java.util.Date (01.01.0001) to java.time.LocalDate returns 29.12.0000


I want to parse java.util.Date to java.time.LocalDate I found this code, when I searched for the problem:

Date date = new SimpleDateFormat("dd.MM.yyyy").parse("01.01.0001");
date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();

if I have the 01.01.0001 as start date, I got problems:

date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();

==> (java.time.LocalDate) 0000-12-29

29.12.0000 and 01.01.0001 are not the same.

  1. Why is the actual result different from the expected?
  2. What can I do if I also want to receive the LocalDate with the value 01.01.0001?

Informations to the time-zone:

ZoneId.systemDefault()

==> (java.time.ZoneRegion) Europe/Berlin

  1. I think, the problem is this step: date.toInstant()
  2. I do not think, that it is (only) a time-zone problem. If I test it with an different year. For example 1901, than it end at 31.12.1900.
        Date date0001 = new SimpleDateFormat("dd.MM.yyyy").parse("01.01.0001");
        System.out.println(date0001.toInstant());

==> 0000-12-29T23:00:00Z

        Date date1901 = new SimpleDateFormat("dd.MM.yyyy").parse("01.01.1901");
        System.out.println(date1901.toInstant());

==> 1900-12-31T23:00:00Z


Solution

  • Multiple problems

    • 1 day difference: the time zone as already explained in other answers, that is, for example, midnight at the time zone Europe/Berlin corresponds to 23:00 the day before at UTC (no daylight saving time)
    • 2 day difference: the old classes (Date, SimpleDateFormat, Calendar) are using the Julian Calendar up to October 1582, while the new classes in java.time are using the Proleptic Gregorian Calendar

    From Wikipedia we can see the difference between both, and following code confirms that:

    TimeZone.setDefault(TimeZone.getTimeZone("UTC")); // one error less
    for (var year : List.of(1, 101, 201, 1401, 1501, 1601)) {
        var date = new Date(year-1900, 0, 10);
        var instant = date.toInstant();
        System.out.println(date.getTime() + " = " + date);
        System.out.println(instant.toEpochMilli() + " = " + instant);
        System.out.println();
    }
    

    Output:

       -62134992000000 = Mon Jan 10 00:00:00 UTC 1
       -62134992000000 = 0001-01-08T00:00:00Z
       
       -58979232000000 = Sun Jan 10 00:00:00 UTC 101
       -58979232000000 = 0101-01-09T00:00:00Z
       
       -55823472000000 = Sat Jan 10 00:00:00 UTC 201
       -55823472000000 = 0201-01-10T00:00:00Z
       
       -17954352000000 = Mon Jan 10 00:00:00 UTC 1401
       -17954352000000 = 1401-01-19T00:00:00Z
       
       -14798592000000 = Sun Jan 10 00:00:00 UTC 1501
       -14798592000000 = 1501-01-20T00:00:00Z
       
       -11643696000000 = Wed Jan 10 00:00:00 UTC 1601
       -11643696000000 = 1601-01-10T00:00:00Z
    

    Javadoc

    We can find the explanation for difference in the documentation (emphasis added):

    • GregorianCalendar

      Historically, in those countries which adopted the Gregorian calendar first, October 4, 1582 (Julian) was thus followed by October 15, 1582 (Gregorian). This calendar models this correctly. Before the Gregorian cutover, GregorianCalendar implements the Julian calendar. The only difference between the Gregorian and the Julian calendar is the leap year rule. The Julian calendar specifies leap years every four years, whereas the Gregorian calendar omits century years which are not divisible by 400.

    • java.time

      The classes defined here represent the principle date-time concepts, including instants, durations, dates, times, time-zones and periods. They are based on the ISO calendar system, which is the de facto world calendar following the proleptic Gregorian rules.


    Conclusion

    If dealing with dates after 1582, Date#toInstant, Instant#atOffset (Instant#atZone) and OffsetDateTime#toLocalDate (ZonedDateTime#toLocalDate) can be used, taking care to use the correct time zone (offset.) Anyway, as already warned, using these classes is strongly discouraged!
    Otherwise, for dates before/at 1582, you will need to first check which calendar was (should be) used to represent these dates.

    For a Julian Calendar (before 1583), as I already commented, following or similar code can be used:

    var calendar = Calendar.getInstance(); 
    calendar.setTime(date); 
    var localDate = LocalDate.of(
        calendar.get(Calendar.YEAR), 
        1 + calendar.get(Calendar.MONTH),   // months are 0-based
        calendar.get(Calendar.DAY_OF_MONTH));