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

    From Wikipedia we can see the difference between both, and the 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 the difference in the documentation (emphasis added):


    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, the 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));