Different result in parsing datetime via calendsr or simpleDateFormatter

I now working with java 1.6 and encounter strange behaviour, may be bug, here is code:

import org.junit.Test;

import javax.xml.bind.DatatypeConverter;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class TestDate {
@Test
public void testConvert() throws Exception {
    Calendar parsedCalendar = DatatypeConverter.parseDateTime("0001-01-01T00:00:00");
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
    Date sdfDate = simpleDateFormat.parse("0001-01-01T00:00:00");

    Calendar parsedCalendar2 = DatatypeConverter.parseDateTime("1980-03-01T00:00:00");
    SimpleDateFormat simpleDateFormat2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
    Date sdfDate2 = simpleDateFormat2.parse("1980-03-01T00:00:00");


    System.out.println("parsedCalendar: " + parsedCalendar.getTimeInMillis());
    System.out.println("parsedCalendar TZ: " + parsedCalendar.getTimeZone());
    System.out.println("parsedCalendar Date: " + parsedCalendar.getTime());
    System.out.println("sdfDate: " + sdfDate);
    System.out.println("sdfDate millis: " + sdfDate.getTime());

    System.out.println("parsedCalendar2: " + parsedCalendar2.getTimeInMillis());
    System.out.println("parsedCalendar2 TZ: " + parsedCalendar2.getTimeZone());
    System.out.println("parsedCalendar2 Date: " + parsedCalendar2.getTime());
    System.out.println("sdfDate2: " + sdfDate2);
    System.out.println("sdfDate2 millis: " + sdfDate2.getTime());

}
}

And here is problem: debug

OUTPUT:

parsedCalendar: -62135622000000
parsedCalendar TZ: sun.util.calendar.ZoneInfo[id="Asia/Novosibirsk",offset=25200000,dstSavings=0,useDaylight=false,transitions=67,lastRule=null]
parsedCalendar Date: Mon Jan 03 00:00:00 NOVT 1
sdfDate: Sat Jan 01 00:00:00 NOVT 1
sdfDate millis: -62135794800000

parsedCalendar2: 320691600000
parsedCalendar2 TZ: sun.util.calendar.ZoneInfo[id="Asia/Novosibirsk",offset=25200000,dstSavings=0,useDaylight=false,transitions=67,lastRule=null]
parsedCalendar2 Date: Sat Mar 01 00:00:00 NOVT 1980
sdfDate2: Sat Mar 01 00:00:00 NOVT 1980
sdfDate2 millis: 320691600000

DEBUG:

 parsedCalendar.getTimeInMillis() = -62135622000000
    sdfDate.getTime() = -62135794800000
    parsedCalendar.getTime() = {Date@790} "Mon Jan 03 00:00:00 NOVT 1"
    sdfDate = {Date@759} "Sat Jan 01 00:00:00 NOVT 1"
    parsedCalendar2.getTimeInMillis() = 320691600000
    sdfDate2.getTime() = 320691600000
    parsedCalendar2.getTimeZone() = {ZoneInfo@755} "sun.util.calendar.ZoneInfo[id="Asia/Novosibirsk",offset=25200000,dstSavings=0,useDaylight=false,transitions=67,lastRule=null]"
    parsedCalendar.getTimeZone() = {ZoneInfo@756} "sun.util.calendar.ZoneInfo[id="Asia/Novosibirsk",offset=25200000,dstSavings=0,useDaylight=false,transitions=67,lastRule=null]"
    simpleDateFormat.getTimeZone() = {ZoneInfo@757} "sun.util.calendar.ZoneInfo[id="Asia/Novosibirsk",offset=25200000,dstSavings=0,useDaylight=false,transitions=67,lastRule=null]"
    simpleDateFormat2.getTimeZone() = {ZoneInfo@758} "sun.util.calendar.ZoneInfo[id="Asia/Novosibirsk",offset=25200000,dstSavings=0,useDaylight=false,transitions=67,lastRule=null]"

As you can see in parsing 0001 dateTime there is difference in ms! And in parsing 1980 it is not. Who can explain why?

Jon Skeet
people
quotationmark

This is due to the difference between the Julian and Gregorian calendar systems.

SimpleDateFormat uses the default calendar system, which I believe to be GregorianCalendar on both your system and mine. GregorianCalendar (despite its name) switches between the Gregorian calendar system and the Julian calendar system, based on the gregorianChange property. It assumes that any date provided after that cut-over is Gregorian, and any before it is Julian. The default cut-over is in 1582.

DatatypeConverter uses a pure proleptic Gregorian calendar instead, as that's what the W3C XML Schema docs require.

That means if you parse a value just before the calendar switch, you'll see a large difference - and that difference will get smaller as you go further back in time, with a difference of 3 days every 400 years. (The three century-years which aren't divisible by 400, and so are leap years in the Julian calendar but not in the Gregorian calendar.)

If you set the calendar in the SimpleDateFormat to a GregorianCalendar which you've called setGregorianChange(Long.MIN_VALUE) on first, the two will agree.

Here's code to make it easier to explore the difference:

import javax.xml.bind.DatatypeConverter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.TimeUnit;

public class Test {    
    public static void main(String[] args) throws ParseException {
        convert("0001-01-01T00:00:00");
        convert("1000-01-01T00:00:00");
        convert("1580-01-01T00:00:00");
        convert("1590-01-01T00:00:00");
        convert("1980-03-01T00:00:00");
    }

    private static void convert(String input) throws ParseException {
        Calendar datatypeConverterResult = DatatypeConverter.parseDateTime(input);
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
        Date sdfResult = simpleDateFormat.parse(input);

        System.out.println("Input: " + input);
        long datatypeConverterMillis = datatypeConverterResult.getTimeInMillis();
        long sdfResultMillis = sdfResult.getTime();
        long days = TimeUnit.MILLISECONDS.toDays(datatypeConverterMillis - sdfResultMillis);
        System.out.println("DatatypeConverter epoch millis: " + datatypeConverterMillis);
        System.out.println("SimpleDateTime epoch millis: " + sdfResultMillis);
        System.out.println("Difference in days: " + days);
        System.out.println("Parsed calendar time zone: " + datatypeConverterResult.getTimeZone().getID());
        System.out.println();
    }
}

Note that on Java 9, you need to specify the module explicitly. This is simplest done with java.se.ee:

$ javac Test.java --add-modules java.se.ee
$ java --add-modules java.se.ee Test

Output on my box:

Input: 0001-01-01T00:00:00
DatatypeConverter epoch millis: -62135596800000
SimpleDateTime epoch millis: -62135769600000
Difference in days: 2
Parsed calendar time zone: Europe/London

Input: 1000-01-01T00:00:00
DatatypeConverter epoch millis: -30610224000000
SimpleDateTime epoch millis: -30609792000000
Difference in days: -5
Parsed calendar time zone: Europe/London

Input: 1580-01-01T00:00:00
DatatypeConverter epoch millis: -12307248000000
SimpleDateTime epoch millis: -12306384000000
Difference in days: -10
Parsed calendar time zone: Europe/London

Input: 1590-01-01T00:00:00
DatatypeConverter epoch millis: -11991628800000
SimpleDateTime epoch millis: -11991628800000
Difference in days: 0
Parsed calendar time zone: Europe/London

Input: 1980-03-01T00:00:00
DatatypeConverter epoch millis: 320716800000
SimpleDateTime epoch millis: 320716800000
Difference in days: 0
Parsed calendar time zone: Europe/London

people

See more on this question at Stackoverflow