Search code examples
java.netdatetimecalendargregorian-calendar

Convert date between java and .net - 2 days off


I need to transform a .NET DateTime to an equivalent Java Calendar representation.

The .NET DateTime uses Ticks since Jan 1st 0001 (the .NET epoch) as the underlying representation.

The Java GregorianCalendar uses milliseconds since Jan 1st 1970 (the Java (or Unix) epoch). The value is negative for dates before the Java epoch, as expected.

Here I'm transforming the DateTime representation in millis since the Java epoch:

var dt = new DateTime(1,2,3);  //way, way back.
var javaEpoch = new DateTime(1970, 1, 1);

var javaMillis = (dt - javaEpoch).Ticks / TimeSpan.TicksPerMillisecond;

dt.ToString("MM/dd/yyyy").Dump();          // .Dump() is provided by LinqPad. 
javaMillis.Dump();                         // Use Console.WriteLine(...)
                                           // for a regular console app.

This outputs:

02/03/0001
-62132745600000

Now copy-paste the milliseconds value in this Java snippet:

java.util.Calendar cal = new java.util.GregorianCalendar();
cal.setTimeInMillis(-62132745600000L);
java.text.SimpleDateFormat df = new java.text.SimpleDateFormat();
df.applyPattern("MM/dd/yyyy");
System.out.println(df.format(cal.getTime()));

This outputs:

02/05/0001

I guess my question is: How am I supposed to get a valid milliseconds value from a DateTime, from which I can correctly construct a Java Calendar?

...with the implied sub-question "what is really going on in here?"

EDIT: I played with DateTimeValues around the missing date range from Julian to Gregorian calendar (Oct 4 1582 is "followed" by Oct 15 1582).

For dates more recent than Oct 15 1582, the conversion seems to work fine.

...But around the missing range, DateTime starts (or rather, doesn't start) to act funny:

var timespan = new DateTime(1582, 10, 15) - new DateTime(1582, 10, 4);

returns a TimeSpan of 11 days, so the hole is not taken into consideration by the DateTime operators. What gives? I thought the underlying implementation is based on System.Globalization.GregorianCalendar.


Solution

  • Answering the 'why':

    From the (decompiled - thanks dotPeek!) .NET 4 source code (comments are mine):

    public static DateTime operator -(DateTime d, TimeSpan t)
    {
      //range checks
      long internalTicks = d.InternalTicks;
      long num = t._ticks;
      if (internalTicks < num || internalTicks - 3155378975999999999L > num)
        throw new ArgumentOutOfRangeException("t", 
                Environment.GetResourceString("ArgumentOutOfRange_DateArithmetic"));
    
        else
    
        //plain arithmetic using the Ticks property of the two dates.
        return new DateTime((ulong) (internalTicks - num) | d.InternalKind);
    }
    

    So yeah, absolutely no special 'gregorian' treatment for DateTime operators.

    About the 'how to fix':

    I ended up using something along these lines: (pseudo-Java)

    Calendar cal = new GregorianCalendar();
    cal.set(dt.Year, dt.Month - 1 /*it's 0-based*/, dt.Day, dt.Hour, dt.Minute, dt.Second);
    cal.set(Calendar.MILLISECOND, dt.Millisecond);