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:
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 string
s 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