Uploaded image for project: 'C# Driver'
  1. C# Driver
  2. CSHARP-3181

Default DateTimeOffset serialization makes it impossible to search across timezones

    • Type: Icon: Bug Bug
    • Resolution: Fixed
    • Priority: Icon: Critical - P2 Critical - P2
    • 3.0.0
    • Affects Version/s: None
    • Component/s: Serialization
    • Needed
    • Hide

      The new default serialization of DatTimeOffset is BsonType.Document (before it was BsonType.Array)

      Show
      The new default serialization of DatTimeOffset is BsonType.Document (before it was BsonType.Array)

      The default serialization for DateTimeOffset persists two values: a local date time (in ticks) + the original timezone, preventing searching across timezones.  Consider the example:

      Document A has the timestamp '2020-01-10 10:00:00 +00:00' and document B has the timestamp '2020-01-10 11:00:00 +01:00'. In .NET, comparing both timestamps returns true, because even though their timezones are different, both are referring to the same point in time.

       

      Now, if I persist both documents to MongoDB and later I search for these documents using the timestamp '2020-01-10 10:00:00 +00:00', only the first document will be returned. If I search for '2020-01-10 11:00:00 +01:00' then only the second document will be returned. This is not the expected behaviour, essentially the search only works on local times and it even disregards the timezone.

       

      Someone else raised this issue before on CSHARP-2603, but it was dismissed as support and no follow up is recorded. I solved this problem in my codebase by rolling my own serializer, which serialized the DateTime part of the type as UtcTicks, essentially solving the problem.

       

      public class UtcDateTimeOffsetSerializer : StructSerializerBase<DateTimeOffset>
      {
          // public methods
          /// <summary>
          /// Deserializes a value.
          /// </summary>
          /// <param name="context">The deserialization context.</param>
          /// <param name="args">The deserialization args.</param>
          /// <returns>A deserialized value.</returns>
          public override DateTimeOffset Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
          {
              var bsonReader = context.Reader;
              var bsonType = bsonReader.GetCurrentBsonType();
              
              if (bsonType != BsonType.Array)
                  throw CreateCannotDeserializeFromBsonTypeException(bsonType);
      
              bsonReader.ReadStartArray();
              var ticks = bsonReader.ReadInt64();
              var offset = TimeSpan.FromMinutes(bsonReader.ReadInt32());
              bsonReader.ReadEndArray();
              return new DateTimeOffset(ticks + offset.Ticks, offset);
          }
      
          /// <summary>
          /// Serializes a value.
          /// </summary>
          /// <param name="context">The serialization context.</param>
          /// <param name="args">The serialization args.</param>
          /// <param name="value">The object.</param>
          public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, DateTimeOffset value)
          {
              var bsonWriter = context.Writer;
              bsonWriter.WriteStartArray();
              bsonWriter.WriteInt64(value.UtcTicks);
              bsonWriter.WriteInt32((int) value.Offset.TotalMinutes);
              bsonWriter.WriteEndArray();
          }
      }
      
      
      

       

      Given that this format is essentially incompatible with existing data, I would suggest that the default representation is changed from Array to Document so the DateTime portion is recorded in UTC epoch milliseconds, which is at least searchable.

            Assignee:
            ferdinando.papale@mongodb.com Ferdinando Papale
            Reporter:
            d@frata.io Diego Frata
            Votes:
            0 Vote for this issue
            Watchers:
            4 Start watching this issue

              Created:
              Updated:
              Resolved: