Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Discrepancy when converting ancient dates between java.util.Date and java.time.Instant

Tags:

java

date

java-8

I have legacy code that uses java.util.Date creating an ancient date (30 Nov 0002). I'm trying to update what code I can, but that's necessitating converting between Date and LocalDate, etc. I can't completely get rid of the use of Date or the ancient date choice.

I'm finding what appears to be an error when converting back and forth between Date and Instant with this ancient date, and was hoping someone could explain what is going on.

Here's a sample:

    Date date = new Date();
    Instant instant = date.toInstant();
    System.out.println("Current:");
    System.out.println("Date: "+date);
    System.out.println("Instant: "+instant);
    System.out.println("Date epoch:    "+date.getTime());
    System.out.println("Instant epoch: "+instant.getEpochSecond()*1000);

    System.out.println("\nAncient from Date:");
    Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("MST"));
    cal.set(2, Calendar.NOVEMBER, 30, 0, 0, 0);
    date = cal.getTime();
    instant = date.toInstant();
    System.out.println("Date: "+date);
    System.out.println("Instant: "+instant);
    System.out.println("Date epoch:    "+date.getTime());
    System.out.println("Instant epoch: "+instant.getEpochSecond()*1000);

    System.out.println("\nAncient from Instant:");
    instant = Instant.parse("0002-11-30T00:00:00Z");
    date = Date.from(instant);
    System.out.println("Date: "+date);
    System.out.println("Instant: "+instant);
    System.out.println("Date epoch:    "+date.getTime());
    System.out.println("Instant epoch: "+instant.getEpochSecond()*1000);

Which prints the following:

Current:
Date: Tue Sep 18 12:34:27 MST 2018
Instant: 2018-09-18T19:34:27.177Z
Date epoch:    1537299267177
Instant epoch: 1537299267000

Ancient from Date:
Date: Thu Nov 30 00:00:00 MST 2
Instant: 0002-11-28T07:00:00.247Z
Date epoch:    -62075437199753
Instant epoch: -62075437200000

Ancient from Instant:
Date: Fri Dec 01 17:00:00 MST 2
Instant: 0002-11-30T00:00:00Z
Date epoch:    -62075289600000
Instant epoch: -62075289600000

So if I create an Instant at 30 Nov 2, then convert to a Date, the Date is 1 Dec 2. If I start with a Date at 30 Nov 2, the Instant is 28 Nov 2. I'm aware that neither Date nor Instant store timezone information, but why are the epochs so different based on whether I started with a Date vs. an Instant? Is there anyway I can work around that? I need to be able to start with either a Date or an Instant, and end up with the same epoch value. It would also be nice to know why the default toString() shows such different dates given the same epoch.

like image 561
Jeremy Avatar asked Jan 02 '23 20:01

Jeremy


1 Answers

The discrepancy resides in how the implementations of Date and Instant interact with each other in relation to their implementations, with Date using Gregorian/Julian calendars and Instant using ISO standard for Date, which follow a modified Gregorian calendar prior to the Julian calendar switchover.

The GregorianCalendar implementation has a special note:

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.

Well, yes, technically speaking. But for this issue, we don't quite encounter this.

cal.set(1582, Calendar.OCTOBER, 4, 0, 0, 0);

This yields a date, as expected, of October 4, 1582.

cal.set(1582, Calendar.OCTOBER, 5, 0, 0, 0);

This yields a date, of October 15, 1582.

WHAT MAGIC IS THIS, BATMAN?

Well, this isn't a coding error, it's actually an implementation of GregorianCalendar.

However, this year saw the beginning of the Gregorian Calendar switch, when the Papal bull known as Inter gravissimas introduced the Gregorian calendar, adopted by Spain, Portugal, the Polish–Lithuanian Commonwealth and most of present-day Italy from the start. In these countries, the year continued as normal until Thursday, October 4. However, the next day became Friday, October 15 (like a common year starting on Friday),

From Wikipedia on 1582

When we examine October 4, 1582, the following happens:

Date: 1582-Oct-04 00:00:00

Instant: 1582-10-14T00:00:00Z

There is a 10-day gap here, and the reason the instant exists on a "technically non-existent date" is accounted for by the definition of ISO instant dates.

The standard states that every date must be consecutive, so usage of the Julian calendar would be contrary to the standard (because at the switchover date, the dates would not be consecutive).

SO, whereas October 14, 1582 never existed in reality, it exists in ISO time by definition, but occurs on the real world's October 4, 1582 according to Julian Calendar.

Due to what I assume are additional leap year drifts from the first paragraph, where Julian centuries 1500, 1400, 1300, 1100, 1000, 900, 700, 600, 500, 300, 200, 100 have extra leap days not accounted for in the Gregorian calendar, we slowly shift back from a +10 to a -1 offset. This can be verified by adjusting the year in +100 increments.

If you are displaying historical event dates, you will be better off using a Date or JulianCalendar DateFormatter to display the correct correct historical date, as it actually occurred in history. Printing out the ISO time for historical periods may appear nonsensical or inaccurate, but storing the time in this format is still valid.

like image 159
Compass Avatar answered Feb 26 '23 23:02

Compass