Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / database / MongoDB

MongoDB C#: Serializer for DateTimeOffset to BsonType DateTime

5.00/5 (2 votes)
24 Nov 2018CPOL1 min read 17.5K  
Serializing DateTimeOffset to DateTime in MongoDB is currently not supported. This solution will show you how it can work.

Introduction

In case you have the requirement to use DateTimeOffsets in your models, and you want it to be stored as BsonType DateTime, you will get errors like "'DateTime' is not a valid DateTimeOffset representation." or "Cannot deserialize a 'DateTimeOffset' from BsonType 'DateTime'."

Background

We are using in some models DateTimeOffsets, because we have an XML based import running, that has values that represent DateTimeOffsets (XXXXXX +01:00) or similar. We want to have those values being stored in MongoDB as DateTime though. We do not care much about the offset information, UTC being stored is just fine for us.

You can easily create your own DateTimeOffset serializer in MongoDB and register it to be the serializer for DateTimeOffset. I will show you how.

Using the Code

You can look at the source code of MongoDB C# client and see how a DateTimeSerializer or DateTimeOffset serializer is built and just adapt what is there. The final serializer can look like the following:

C#
public class DateTimeOffsetSupportingBsonDateTimeSerializer : StructSerializerBase<DateTimeOffset>, 
                 IRepresentationConfigurable<DateTimeOffsetSupportingBsonDateTimeSerializer>
{
    private BsonType _representation;
    private string StringSerializationFormat = "YYYY-MM-ddTHH:mm:ss.FFFFFFK";

    public DateTimeOffsetSupportingBsonDateTimeSerializer() : this(BsonType.DateTime)
    {
    }

    public DateTimeOffsetSupportingBsonDateTimeSerializer(BsonType representation)
    {
        switch (representation)
        {
            case BsonType.String:
            case BsonType.DateTime:
                break;
            default:
                throw new ArgumentException(string.Format("{0} is 
                not a valid representation for {1}", representation, this.GetType().Name));
        }

        _representation = representation;
    }

    public BsonType Representation => _representation;

    public override DateTimeOffset Deserialize(BsonDeserializationContext context, 
                                               BsonDeserializationArgs args)
    {
        var bsonReader = context.Reader;
        long ticks;
        TimeSpan offset;

        BsonType bsonType = bsonReader.GetCurrentBsonType();
        switch (bsonType)
        {
            case BsonType.String:
                var stringValue = bsonReader.ReadString();
                return DateTimeOffset.ParseExact
                    (stringValue, StringSerializationFormat, DateTimeFormatInfo.InvariantInfo);

            case BsonType.DateTime:
                var dateTimeValue = bsonReader.ReadDateTime();
                return DateTimeOffset.FromUnixTimeMilliseconds(dateTimeValue);

            default:
                throw CreateCannotDeserializeFromBsonTypeException(bsonType);
        }
    }

    public override void Serialize
       (BsonSerializationContext context, BsonSerializationArgs args, DateTimeOffset value)
    {
        var bsonWriter = context.Writer;

        switch (_representation)
        {
            case BsonType.String:
                bsonWriter.WriteString(value.ToString
                      (StringSerializationFormat, DateTimeFormatInfo.InvariantInfo));
                break;

            case BsonType.DateTime:
                bsonWriter.WriteDateTime(value.ToUnixTimeMilliseconds());
                break;

            default:
                var message = string.Format("'{0}' is not a valid 
                              DateTimeOffset representation.", _representation);
                throw new BsonSerializationException(message);
        }
    }

    public DateTimeOffsetSupportingBsonDateTimeSerializer WithRepresentation(BsonType representation)
    {
        if(representation == _representation)
        {
            return this;
        }
        return new DateTimeOffsetSupportingBsonDateTimeSerializer(representation);
    }

    IBsonSerializer IRepresentationConfigurable.WithRepresentation(BsonType representation)
    {
        return WithRepresentation(representation);
    }

    protected Exception CreateCannotDeserializeFromBsonTypeException(BsonType bsonType)
    {
        var message = string.Format("Cannot deserialize a '{0}' from BsonType '{1}'.",
            BsonUtils.GetFriendlyTypeName(ValueType),
            bsonType);
        return new FormatException(message);
    }
}

The resulting serializer can handle serialization of DateTimeOffsets now from/to strings and DateTimes (you can also leave out the string representation part). You can add any logic that you are interested in, on top. To register the new serializer, you should just call the following before you start using the MongoCollection.

BsonSerializer.RegisterSerializer<DateTimeOffset>(new DateTimeOffsetSupportingBsonDateTimeSerializer());

And you are good to go.

Points of Interest

DateTimes are represented in MongoDb as milliseconds since Unix epoch. The .NET Framework has now built-in support to convert from and to DateTimeOffset using those values.

So no matter whether your DateTimeOffset is of value "2018-11-24T14:40:23+05:00" or "2018-11-24T09:40:23+00:00" it yields to the same DateTime in MongoDB.

History

  • 2018-11-24: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)