/* Copyright 2010-present MongoDB Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Globalization;
using System.IO;
using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Bson.Serialization.Options;
namespace MongoDB.Bson.Serialization.Serializers
{
///
/// Represents a serializer for DateTimes.
///
public class DateTimeSerializer : StructSerializerBase, IRepresentationConfigurable
{
// private constants
private static class Flags
{
public const long DateTime = 1;
public const long Ticks = 2;
}
// private static fields
private static readonly DateTimeSerializer __dateOnlyInstance = new DateTimeSerializer(true);
private static readonly DateTimeSerializer __localInstance = new DateTimeSerializer(DateTimeKind.Local);
private static readonly DateTimeSerializer __utcInstance = new DateTimeSerializer(DateTimeKind.Utc);
// private fields
private readonly bool _dateOnly;
private readonly SerializerHelper _helper;
private readonly Int64Serializer _int64Serializer = new Int64Serializer();
private readonly DateTimeKind _kind;
private readonly BsonType _representation;
// constructors
///
/// Initializes a new instance of the class.
///
public DateTimeSerializer()
: this(DateTimeKind.Utc, BsonType.DateTime)
{
}
///
/// Initializes a new instance of the class.
///
/// if set to true [date only].
public DateTimeSerializer(bool dateOnly)
: this(dateOnly, BsonType.DateTime)
{
}
///
/// Initializes a new instance of the class.
///
/// if set to true [date only].
/// The representation.
public DateTimeSerializer(bool dateOnly, BsonType representation)
: this(dateOnly, DateTimeKind.Utc, representation)
{
}
///
/// Initializes a new instance of the class.
///
/// The representation.
public DateTimeSerializer(BsonType representation)
: this(DateTimeKind.Utc, representation)
{
}
///
/// Initializes a new instance of the class.
///
/// The kind.
public DateTimeSerializer(DateTimeKind kind)
: this(kind, BsonType.DateTime)
{
}
///
/// Initializes a new instance of the class.
///
/// The kind.
/// The representation.
public DateTimeSerializer(DateTimeKind kind, BsonType representation)
: this(false, kind, representation)
{
}
private DateTimeSerializer(bool dateOnly, DateTimeKind kind, BsonType representation)
{
switch (representation)
{
case BsonType.DateTime:
case BsonType.Document:
case BsonType.Int64:
case BsonType.String:
break;
default:
var message = string.Format("{0} is not a valid representation for a DateTimeSerializer.", representation);
throw new ArgumentException(message);
}
_dateOnly = dateOnly;
_kind = kind;
_representation = representation;
_helper = new SerializerHelper
(
new SerializerHelper.Member("DateTime", Flags.DateTime),
new SerializerHelper.Member("Ticks", Flags.Ticks)
);
}
// public static properties
///
/// Gets an instance of DateTimeSerializer with DateOnly=true.
///
public static DateTimeSerializer DateOnlyInstance
{
get { return __dateOnlyInstance; }
}
///
/// Gets an instance of DateTimeSerializer with Kind=Local.
///
public static DateTimeSerializer LocalInstance
{
get { return __localInstance; }
}
///
/// Gets an instance of DateTimeSerializer with Kind=Utc.
///
public static DateTimeSerializer UtcInstance
{
get { return __utcInstance; }
}
// public properties
///
/// Gets whether this DateTime consists of a Date only.
///
public bool DateOnly
{
get { return _dateOnly; }
}
///
/// Gets the DateTimeKind (Local, Unspecified or Utc).
///
public DateTimeKind Kind
{
get { return _kind; }
}
///
/// Gets the external representation.
///
///
/// The representation.
///
public BsonType Representation
{
get { return _representation; }
}
// public methods
///
/// Deserializes a value.
///
/// The deserialization context.
/// The deserialization args.
/// A deserialized value.
public override DateTime Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var bsonReader = context.Reader;
DateTime value;
var bsonType = bsonReader.GetCurrentBsonType();
switch (bsonType)
{
case BsonType.DateTime:
// use an intermediate BsonDateTime so MinValue and MaxValue are handled correctly
value = new BsonDateTime(bsonReader.ReadDateTime()).ToUniversalTime();
break;
case BsonType.Document:
value = default(DateTime);
_helper.DeserializeMembers(context, (elementName, flag) =>
{
switch (flag)
{
case Flags.DateTime: bsonReader.SkipValue(); break; // ignore value (use Ticks instead)
case Flags.Ticks: value = new DateTime(_int64Serializer.Deserialize(context), DateTimeKind.Utc); break;
}
});
break;
case BsonType.Int64:
value = DateTime.SpecifyKind(new DateTime(bsonReader.ReadInt64()), DateTimeKind.Utc);
break;
case BsonType.String:
if (_dateOnly)
{
value = DateTime.SpecifyKind(DateTime.ParseExact(bsonReader.ReadString(), "yyyy-MM-dd", null), DateTimeKind.Utc);
}
else
{
value = JsonConvert.ToDateTime(bsonReader.ReadString());
}
break;
default:
throw CreateCannotDeserializeFromBsonTypeException(bsonType);
}
if (_dateOnly)
{
if (value.TimeOfDay != TimeSpan.Zero)
{
throw new FormatException("TimeOfDay component for DateOnly DateTime value is not zero.");
}
value = DateTime.SpecifyKind(value, _kind); // not ToLocalTime or ToUniversalTime!
}
else
{
switch (_kind)
{
case DateTimeKind.Local:
case DateTimeKind.Unspecified:
value = DateTime.SpecifyKind(BsonUtils.ToLocalTime(value), _kind);
break;
case DateTimeKind.Utc:
value = BsonUtils.ToUniversalTime(value);
break;
}
}
return value;
}
///
/// Serializes a value.
///
/// The serialization context.
/// The serialization args.
/// The object.
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, DateTime value)
{
var bsonWriter = context.Writer;
DateTime utcDateTime;
if (_dateOnly)
{
if (value.TimeOfDay != TimeSpan.Zero)
{
throw new BsonSerializationException("TimeOfDay component is not zero.");
}
utcDateTime = DateTime.SpecifyKind(value, DateTimeKind.Utc); // not ToLocalTime
}
else
{
utcDateTime = BsonUtils.ToUniversalTime(value);
}
var millisecondsSinceEpoch = BsonUtils.ToMillisecondsSinceEpoch(utcDateTime);
switch (_representation)
{
case BsonType.DateTime:
bsonWriter.WriteDateTime(millisecondsSinceEpoch);
break;
case BsonType.Document:
bsonWriter.WriteStartDocument();
bsonWriter.WriteDateTime("DateTime", millisecondsSinceEpoch);
bsonWriter.WriteInt64("Ticks", utcDateTime.Ticks);
bsonWriter.WriteEndDocument();
break;
case BsonType.Int64:
bsonWriter.WriteInt64(utcDateTime.Ticks);
break;
case BsonType.String:
if (_dateOnly)
{
bsonWriter.WriteString(value.ToString("yyyy-MM-dd"));
}
else
{
if (value == DateTime.MinValue || value == DateTime.MaxValue)
{
// serialize MinValue and MaxValue as Unspecified so we do NOT get the time zone offset
value = DateTime.SpecifyKind(value, DateTimeKind.Unspecified);
}
else if (value.Kind == DateTimeKind.Unspecified)
{
// serialize Unspecified as Local se we get the time zone offset
value = DateTime.SpecifyKind(value, DateTimeKind.Local);
}
bsonWriter.WriteString(JsonConvert.ToString(value));
}
break;
default:
var message = string.Format("'{0}' is not a valid DateTime representation.", _representation);
throw new BsonSerializationException(message);
}
}
///
/// Returns a serializer that has been reconfigured with the specified dateOnly value.
///
/// if set to true the values will be required to be Date's only (zero time component).
///
/// The reconfigured serializer.
///
public DateTimeSerializer WithDateOnly(bool dateOnly)
{
if (dateOnly == _dateOnly)
{
return this;
}
else
{
return new DateTimeSerializer(dateOnly, _representation);
}
}
///
/// Returns a serializer that has been reconfigured with the specified dateOnly value and representation.
///
/// if set to true the values will be required to be Date's only (zero time component).
/// The representation.
///
/// The reconfigured serializer.
///
public DateTimeSerializer WithDateOnly(bool dateOnly, BsonType representation)
{
if (dateOnly == _dateOnly && representation == _representation)
{
return this;
}
else
{
return new DateTimeSerializer(dateOnly, representation);
}
}
///
/// Returns a serializer that has been reconfigured with the specified DateTimeKind value.
///
/// The DateTimeKind.
///
/// The reconfigured serializer.
///
public DateTimeSerializer WithKind(DateTimeKind kind)
{
if (kind == _kind && _dateOnly == false)
{
return this;
}
else
{
return new DateTimeSerializer(kind, _representation);
}
}
///
/// Returns a serializer that has been reconfigured with the specified DateTimeKind value and representation.
///
/// The DateTimeKind.
/// The representation.
///
/// The reconfigured serializer.
///
public DateTimeSerializer WithKind(DateTimeKind kind, BsonType representation)
{
if (kind == _kind && representation == _representation && _dateOnly == false)
{
return this;
}
else
{
return new DateTimeSerializer(kind, representation);
}
}
///
/// Returns a serializer that has been reconfigured with the specified representation.
///
/// The representation.
/// The reconfigured serializer.
public DateTimeSerializer WithRepresentation(BsonType representation)
{
if (representation == _representation)
{
return this;
}
else
{
if (_dateOnly)
{
return new DateTimeSerializer(_dateOnly, representation);
}
else
{
return new DateTimeSerializer(_kind, representation);
}
}
}
// explicit interface implementations
IBsonSerializer IRepresentationConfigurable.WithRepresentation(BsonType representation)
{
return WithRepresentation(representation);
}
}
}