We recently tried to fix NSDate's conversion operators with DateTime
(3c65ab1756), but unfortunately a corner case
was missed.
The new approach in the above-mentioned commit would get the individual
date/time components for a given date and use the appropriate constructor for
the other type to re-construct the date/time in question.
However, one case was missed: when converting from NSDate to DateTime, we'd
get a fractional number of milliseconds. This fractional number could be
something like 999.99 milliseconds, and when converting that to the int the
DateTime constructor expected for the number of milliseconds, then DateTime
would throw an exception, because the number of milliseconds could only be
between 0 and 999.
I've solved this by not using floating-point math in the computations. We're
now getting the number of nanoseconds from the NSDate (which is a natural
number, and represents the total number of nanoseconds less than a whole
second), and then converting that to the number of milliseconds, microseconds
and ticks that can be used with DateTime using integral math. Unfortunately
DateTime doesn't have a constructor that takes the remaining number of ticks
after all the other fields have been provided, but that can be added
afterwards.
I've also made a few other improvements:
* Improve the validation for the NSDate -> DateTime conversion to detect BC
dates by using the NSDate's Era component (to throw because DateTime only
supports AC dates). Also don't allow a tick later than year 10.000 (DateTime
only supports up to a tick before year 10.000) - but explicitly support
exactly year 10.000, and convert it to DateTime.MaxValue (this is because
due to precision errors NSDate can't actually express 'a tick before year
10.000', it ends up being rounded up to year 10.000 exactly). This means
there are no more magical values in the range validation checks.
* Increase precision in the DateTime -> NSDate conversion by starting with the
sub-second amount of ticks from the DateTime instance (instead of the number
of milliseconds). This allows us to compute the nanoseconds NSDate expects
with much higher precision.
* More tests!
Fixes this test:
MonoTouchFixtures.Foundation.DateTest.DateTimeToNSDate : 2 ms
[FAIL] Precision32022 : System.ArgumentOutOfRangeException : Valid values are between 0 and 999, inclusive.
Parameter name: millisecond
at System.DateTime..ctor (System.Int32 year, System.Int32 month, System.Int32 day, System.Int32 hour, System.Int32 minute, System.Int32 second, System.Int32 millisecond, System.DateTimeKind kind) [0x0002d] in <4d40c65adfc14d7fb19bad9310f3eb2a>:0
at Foundation.NSDate.op_Explicit (Foundation.NSDate d) [0x000b8] in <9cb1e1018c034b75ba5f4ed7b83ba2f2>:0
at MonoTouchFixtures.Foundation.DateTest.Precision32022 () [0x0000c] in <c44b5df5f7b84b69b737e9fd61bddaed>:0
at (wrapper managed-to-native) System.Reflection.RuntimeMethodInfo.InternalInvoke(System.Reflection.RuntimeMethodInfo,object,object[],System.Exception&)
at System.Reflection.RuntimeMethodInfo.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x0006a] in <4d40c65adfc14d7fb19bad9310f3eb2a>:0
Fixes https://github.com/xamarin/maccore/issues/2632.
Date and time is difficult.
Ref: https://gist.github.com/timvisee/fcda9bbdff88d45cc9061606b4b923ca
Ref: the rest of internet...