msgpack / msgpack

MessagePack is an extremely efficient object serialization library. It's like JSON, but very fast and small.

Home Page:http://msgpack.org/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Cannot get a matching maximum value for Timestamp 32 and Timestamp 64

mlsomers opened this issue · comments

According to the documentation:

- Timestamp 32 format can represent a timestamp in [1970-01-01 00:00:00 UTC, 2106-02-07 06:28:16 UTC) range. Nanoseconds part is 0.
- Timestamp 64 format can represent a timestamp in [1970-01-01 00:00:00.000000000 UTC, 2514-05-30 01:53:04.000000000 UTC) range.

I have tried to confirm this (while writing a unit test), but my values were off.
For Timestamp 32 my implementation loses exactly one second.
For Timestamp 64 my implementation adds 2 hours, 46 minutes and 38,99999 seconds.

Now I am wondering:

  • Is my implementation buggy?
  • Is the documentation wrong?
  • Is there a bug in the .Net Framework's DateTime type?
  • Are there differences fundamental Gregorian calendar definitions (for example handling of leap-seconds)?

Here's my code (and the results):

DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
      
DateTime MaxFExt4 = epoch.AddSeconds(uint.MaxValue);
Console.WriteLine(string.Concat("Timestamp 32 : ", epoch.ToString("yyyy-MM-dd HH:mm:ss.fffffff"), " - ", MaxFExt4.ToString("yyyy-MM-dd HH:mm:ss.fffffff")));
      
long maxSecs = ((long)uint.MaxValue << 2) | 3; // 17179869183
long maxNanoSec = 999999999; // not uint.MaxValue
TimeSpan maxNanoSecSpan = new TimeSpan(maxNanoSec * 100); // 1 tick = 100 nanosec.
DateTime MaxFExt8 = epoch.AddSeconds(maxSecs).Add(maxNanoSecSpan); 
Console.WriteLine(string.Concat("Timestamp 64 : ", epoch.ToString("yyyy-MM-dd HH:mm:ss.fffffff"), " - ", MaxFExt8.ToString("yyyy-MM-dd HH:mm:ss.fffffff")));

// DateTime MaxExt8 = Zero.AddSeconds(long.MaxValue).Add(maxNanoSecSpan); // System.ArgumentOutOfRangeException
DateTime MinExt8 = DateTime.MinValue;
// DateTime MinExt8 = Zero.AddSeconds(long.MinValue).Add(-maxNanoSecSpan); // System.ArgumentOutOfRangeException
DateTime MaxExt8 = DateTime.MaxValue;
Console.WriteLine(string.Concat("Timestamp 96 : ", MinExt8.ToString("yyyy-MM-dd HH:mm:ss.fffffff"), " - ", MaxExt8.ToString("yyyy-MM-dd HH:mm:ss.fffffff")));

// output:
// Timestamp 32 : 1970-01-01 00:00:00.0000000 - 2106-02-07 06:28:15.0000000
// Timestamp 64 : 1970-01-01 00:00:00.0000000 - 2514-05-30 04:39:42.9999900
// Timestamp 96 : 0001-01-01 00:00:00.0000000 - 9999-12-31 23:59:59.9999999

Please ignore Timestamp 96 since it is far out of reach for the DateTime type in .Net. But someone else has already found an issue there.

Another annoying thing is that a .Net DateTime has two digit's more granularity (ticks = 100ths of a nanosecond) which need to be trimmed of in order to use the official spec.

Tested on a Mac. Got the same result as you for Timestamp32 but the same result as the spec for Timestamp64.

Tested on a Mac. Got the same result as you for Timestamp32 but the same result as the spec for Timestamp64.

@clwi That's interesting... are you using a .Net core framework with exactly the same code or a different platform?
My test was on the native .Net 4.0 framework on a Windows 10 PC (Intel little endian).

I used the MacOS builtin NSDate class. No .net.

The document's ranges are right-open ranges.

  • 32bit [1970-01-01 00:00:00 UTC, 2106-02-07 06:28:16 UTC)
  • 64bit [1970-01-01 00:00:00.000000000 UTC, 2514-05-30 01:53:04.000000000 UTC)

So, the maximum value of 32-bit timestamp should be 2106-02-07 06:28:15 UTC for 32-bit timestamp, and 2514-05-30 01:53:03.999999999 UTC for 64-bit timestamp.

My msgpack-ruby can show the expected result:

MessagePack.pack(Time.new(2106, 2, 7, 6, 28, 15, "utc")) #=> "\xD6\xFF\xFF\xFF\xFF\xFF"
MessagePack.pack(Time.at(Time.new(2514, 5, 30, 1, 53, 3, "utc").to_i, 999999999, :nsec).utc) #=> "\xD7\xFF\xEEk'\xFF\xFF\xFF\xFF\xFF"

Someone on StackOverflow helped me find the error.

TimeSpan maxNanoSecSpan = new TimeSpan(maxNanoSec * 100); // 1 tick = 100 nanosec.

should have devided instead of multiplied:

TimeSpan maxNanoSecSpan = new TimeSpan(maxNanoSec / 100); // 1 tick = 100 nanosec.

Now I get the correct answer:

Timestamp 64 : 1970-01-01 00:00:00.0000000 - 2514-05-30 01:53:03.9999999

By the way, my comment that .Net DateTime has two digit's more granularity is false, it is the other way around so no information will be lost when using MsgPack! 👍