[CSHARP-2997] DateTimeSerializer with different Tick count when deserializing Created: 06/Mar/20  Updated: 27/Oct/23  Resolved: 09/Mar/20

Status: Closed
Project: C# Driver
Component/s: Serialization
Affects Version/s: 2.10.2
Fix Version/s: None

Type: Bug Priority: Major - P3
Reporter: Samir Kharchi Assignee: Robert Stam
Resolution: Works as Designed Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified
Environment:

Win10



 Description   

Using the DateTimeSerializer the tick count differs when deserializing a DateTime object:

DateTimeSerializer.LocalInstance.Serialize(context, args, aDateTime);
var decodedDateTime = DateTimeSerializer.LocalInstance.Deserialize(context, args);

The deserialized DateTime object is equal to the original except for the Tick count which differs by some nanoseconds (this is reproducable and it's always the last 4 digits of the tick count that differ). Here is one Xunit exception message:

Xunit.Sdk.EqualException: "Assert.Equal() Failure
Expected: 2020-03-06T09:54:09.6356673+01:00
Actual:     2020-03-06T09:54:09.6350000+01:00

 



 Comments   
Comment by Robert Stam [ 10/Mar/20 ]

Also, you can change the default representation for a .NET DateTime in your application by executing the following lines at startup:

var int64DateTimeSerializer = new DateTimeSerializer(BsonType.Int64);
BsonSerializer.RegisterSerializer(int64DateTimeSerializer);

But keep in mind that the serializer registry is only used when serializing POCOs (classes), so registering that serializer will have no effect on any code that use the BsonDocument object model, such as:

var dt = DateTime.Parse("2020-03-11T02:21:01.1234567Z");
var document = new BsonDocument("dt", dt); // dt is converted to a BsonDateTime and therefore truncated to millisecond precision

 

Comment by Robert Stam [ 10/Mar/20 ]

You make a good point that it is unfortunate that the default representation for a .NET DateTime in a binary BSON document results in truncation to millisecond precision.

In hindsight, since there is no exact match between a .NET DateTime and a BSON binary type, it might have been better to require applications to explicitly specify how they want a .NET DateTime to be represented in BSON.

The default we chose is probably the one most applications would make (unless they need more than millisecond precision of course). For most applications the default is convenient.

Sorry this caused you an issue.

Comment by Samir Kharchi [ 10/Mar/20 ]

First of all thanks Robert for investigating and explaining. Also thanks for the alternative representations in order to workaround this, I'll definetly take that route (though I have already switched to storing the tick count itself and parse the datetime back from that).

However, what you are saying is that the default serialization (due to the according bson representation) is not symmetric and that this is accepted by design? This doesn't seem right imo from an API perspective. How is it a non-working process is the accepted default behavior for such a core data type in a language specific driver? It might be expected and If it was an alternative representation, ok, but as a default? It renders any default serialization bogus without the user knowing (there is no such info in the driver documentation) it's hard to embrace this as not being an issue.

But that's just my 2 cents..

Cheers in any case

Comment by Robert Stam [ 09/Mar/20 ]

While it might be a bit surprising, this is the expected behavior.

The reason is that by default a .NET DateTime value is serialized as a BSON datetime (converted to UTC), and in BSON the resolution of a datetime is in milliseconds. See:

http://bsonspec.org/spec.html

So when a .NET DateTime is converted to a BSON datetime and back again the result is truncated to millisecond precision.

If you need to store a DateTime value with the same precision as the native .NET precision you can specify an alternative representation.

The following code shows some examples using the 4 possible representations:

 

public static void Main(string[] args)
{
    var value = DateTime.Parse("2020-03-06T09:54:09.6356673+01:00");
 
    var serializer1 = new DateTimeSerializer(representation: BsonType.DateTime); // BsonType.DateTime is the default and has millisecond precision
    var representation1 = ToJson(value, serializer1);
    // representation1 == "{ \"x\" : ISODate(\"2020-03-06T08:54:09.635Z\") }"
 
    var serializer2 = new DateTimeSerializer(representation: BsonType.Int64);
    var representation2 = ToJson(value, serializer2);
    // representation2 == "{ \"x\" : NumberLong(\"637190816496356673\") }"
 
    var serializer3 = new DateTimeSerializer(representation: BsonType.Document);
    var representation3 = ToJson(value, serializer3);
    // representation3 == "{ \"x\" : { \"DateTime\" : ISODate(\"2020-03-06T08:54:09.635Z\"), \"Ticks\" : NumberLong(\"637190816496356673\") } }"
 
    var serializer4 = new DateTimeSerializer(representation: BsonType.String);
    var representation4 = ToJson(value, serializer4);
    // representation4 == "{ \"x\" : \"2020-03-06T03:54:09.6356673-05:00\" }"
}

Where the ToJson helper function is:

private static string ToJson(DateTime value, IBsonSerializer<DateTime> serializer)
{
    var stringWriter = new StringWriter();
    var writer = new JsonWriter(stringWriter);
    var context = BsonSerializationContext.CreateRoot(writer);
 
    writer.WriteStartDocument();
    writer.WriteName("x");
    serializer.Serialize(context, value);
    writer.WriteEndDocument();
 
    return stringWriter.ToString();
}

Note that the Int64 and String representations, while providing round trip capability with no loss of precision, cannot be queried with datetime semantics because they are not represented as BSON datetimes. To the server these representations are just a 64-bit integer or a string, and it has no way of knowing that these actually represent datetime values.

 

 

 

 

 

Comment by Samir Kharchi [ 06/Mar/20 ]

Btw. I am using and testing with a local MongoDB Server v4.2

Generated at Wed Feb 07 21:44:04 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.