diff --git a/Bson/Bson.csproj b/Bson/Bson.csproj index 66c628d0bd7..24ceb35cbf5 100644 --- a/Bson/Bson.csproj +++ b/Bson/Bson.csproj @@ -73,7 +73,9 @@ + + diff --git a/Bson/DefaultSerializer/Attributes/BsonDateTimeOptionsAttribute.cs b/Bson/DefaultSerializer/Attributes/BsonDateTimeOptionsAttribute.cs new file mode 100644 index 00000000000..a0b8bc6f962 --- /dev/null +++ b/Bson/DefaultSerializer/Attributes/BsonDateTimeOptionsAttribute.cs @@ -0,0 +1,58 @@ +/* Copyright 2010 10gen 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.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MongoDB.Bson.DefaultSerializer { + // [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] + public class BsonDateTimeOptionsAttribute : BsonSerializationOptionsAttribute { + #region private fields + private bool dateOnly = false; + private DateTimeKind kind = DateTimeKind.Utc; + private BsonType representation = BsonType.DateTime; + #endregion + + #region constructors + public BsonDateTimeOptionsAttribute() { + } + #endregion + + #region public properties + public bool DateOnly { + get { return dateOnly; } + set { dateOnly = value; } + } + + public DateTimeKind Kind { + get { return kind; } + set { kind = value; } + } + + public BsonType Representation { + get { return representation; } + set { representation = value; } + } + #endregion + + #region public methods + public override object GetOptions() { + return new DateTimeSerializationOptions { DateOnly = dateOnly, Kind = kind, Representation = representation }; + } + #endregion + } +} diff --git a/Bson/DefaultSerializer/Attributes/BsonIdAttribute.cs b/Bson/DefaultSerializer/Attributes/BsonIdAttribute.cs index fae5a15dede..2857dedfb82 100644 --- a/Bson/DefaultSerializer/Attributes/BsonIdAttribute.cs +++ b/Bson/DefaultSerializer/Attributes/BsonIdAttribute.cs @@ -23,6 +23,7 @@ namespace MongoDB.Bson.DefaultSerializer { public class BsonIdAttribute : Attribute { #region private fields private Type idGenerator; + private int order = int.MaxValue; #endregion #region constructors @@ -35,6 +36,11 @@ public class BsonIdAttribute : Attribute { get { return idGenerator; } set { idGenerator = value; } } + + public int Order { + get { return order; } + set { order = value; } + } #endregion } } diff --git a/Bson/DefaultSerializer/Attributes/BsonRepresentationAttribute.cs b/Bson/DefaultSerializer/Attributes/BsonRepresentationAttribute.cs index ba996063baf..d053024d018 100644 --- a/Bson/DefaultSerializer/Attributes/BsonRepresentationAttribute.cs +++ b/Bson/DefaultSerializer/Attributes/BsonRepresentationAttribute.cs @@ -19,8 +19,8 @@ using System.Text; namespace MongoDB.Bson.DefaultSerializer { - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] - public class BsonRepresentationAttribute : Attribute { + // [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] + public class BsonRepresentationAttribute : BsonSerializationOptionsAttribute { #region private fields private BsonType representation; #endregion @@ -38,5 +38,11 @@ BsonType representation get { return representation; } } #endregion + + #region public methods + public override object GetOptions() { + return representation; + } + #endregion } } diff --git a/Bson/DefaultSerializer/Attributes/BsonSerializationOptionsAttribute.cs b/Bson/DefaultSerializer/Attributes/BsonSerializationOptionsAttribute.cs new file mode 100644 index 00000000000..7f036457c50 --- /dev/null +++ b/Bson/DefaultSerializer/Attributes/BsonSerializationOptionsAttribute.cs @@ -0,0 +1,33 @@ +/* Copyright 2010 10gen 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.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MongoDB.Bson.DefaultSerializer { + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] + public abstract class BsonSerializationOptionsAttribute : Attribute { + #region constructors + protected BsonSerializationOptionsAttribute() { + } + #endregion + + #region public methods + public abstract object GetOptions(); + #endregion + } +} diff --git a/Bson/DefaultSerializer/BsonClassMap.cs b/Bson/DefaultSerializer/BsonClassMap.cs index 8b5ddba3e7a..faa574b3a89 100644 --- a/Bson/DefaultSerializer/BsonClassMap.cs +++ b/Bson/DefaultSerializer/BsonClassMap.cs @@ -462,62 +462,59 @@ BsonMemberMap memberMap private BsonMemberMap AutoMapMember( MemberInfo memberInfo ) { - var elementName = conventions.ElementNameConvention.GetElementName(memberInfo); - var order = int.MaxValue; - IBsonIdGenerator idGenerator = null; - - var idAttribute = (BsonIdAttribute) memberInfo.GetCustomAttributes(typeof(BsonIdAttribute), false).FirstOrDefault(); - if (idAttribute != null) { - elementName = "_id"; // if BsonIdAttribute is present ignore BsonElementAttribute - var idGeneratorType = idAttribute.IdGenerator; - if (idGeneratorType != null) { - idGenerator = (IBsonIdGenerator) Activator.CreateInstance(idGeneratorType); + var memberMap = MapMember(memberInfo); + + memberMap.SetElementName(conventions.ElementNameConvention.GetElementName(memberInfo)); + memberMap.SetIgnoreIfNull(conventions.IgnoreIfNullConvention.IgnoreIfNull(memberInfo)); + memberMap.SetSerializeDefaultValue(conventions.SerializeDefaultValueConvention.SerializeDefaultValue(memberInfo)); + + var defaultValue = conventions.DefaultValueConvention.GetDefaultValue(memberInfo); + if (defaultValue != null) { + memberMap.SetDefaultValue(defaultValue); + } + + foreach (var attribute in memberInfo.GetCustomAttributes(false)) { + var defaultValueAttribute = attribute as BsonDefaultValueAttribute; + if (defaultValueAttribute != null) { + memberMap.SetDefaultValue(defaultValueAttribute.DefaultValue); + memberMap.SetSerializeDefaultValue(defaultValueAttribute.SerializeDefaultValue); } - } else { - var elementAttribute = (BsonElementAttribute) memberInfo.GetCustomAttributes(typeof(BsonElementAttribute), false).FirstOrDefault(); + + var elementAttribute = attribute as BsonElementAttribute; if (elementAttribute != null) { - elementName = elementAttribute.ElementName; - order = elementAttribute.Order; + memberMap.SetElementName(elementAttribute.ElementName); + memberMap.SetOrder(elementAttribute.Order); + continue; } - } - var memberMap = MapMember(memberInfo); - memberMap.SetElementName(elementName); - if (order != int.MaxValue) { - memberMap.SetOrder(order); - } - if (idAttribute != null) { - memberMap.SetIdGenerator(idGenerator); - SetIdMember(memberMap); - } - - var defaultValueAttribute = (BsonDefaultValueAttribute) memberInfo.GetCustomAttributes(typeof(BsonDefaultValueAttribute), false).FirstOrDefault(); - if (defaultValueAttribute != null) { - memberMap.SetDefaultValue(defaultValueAttribute.DefaultValue); - memberMap.SetSerializeDefaultValue(defaultValueAttribute.SerializeDefaultValue); - } else { - var defaultValue = conventions.DefaultValueConvention.GetDefaultValue(memberMap.MemberInfo); - if (defaultValue != null) { - memberMap.SetDefaultValue(defaultValue); + var idAttribute = attribute as BsonIdAttribute; + if (idAttribute != null) { + memberMap.SetElementName("_id"); + memberMap.SetOrder(idAttribute.Order); + var idGeneratorType = idAttribute.IdGenerator; + if (idGeneratorType != null) { + var idGenerator = (IBsonIdGenerator) Activator.CreateInstance(idGeneratorType); + memberMap.SetIdGenerator(idGenerator); + } + SetIdMember(memberMap); + continue; } - memberMap.SetSerializeDefaultValue(conventions.SerializeDefaultValueConvention.SerializeDefaultValue(memberMap.MemberInfo)); - } - var ignoreIfNullAttribute = (BsonIgnoreIfNullAttribute) memberInfo.GetCustomAttributes(typeof(BsonIgnoreIfNullAttribute), false).FirstOrDefault(); - if (ignoreIfNullAttribute != null) { - memberMap.SetIgnoreIfNull(true); - } else { - memberMap.SetIgnoreIfNull(conventions.IgnoreIfNullConvention.IgnoreIfNull(memberMap.MemberInfo)); - } + var ignoreIfNullAttribute = attribute as BsonIgnoreIfNullAttribute; + if (ignoreIfNullAttribute != null) { + memberMap.SetIgnoreIfNull(true); + } - var requiredAttribute = (BsonRequiredAttribute) memberInfo.GetCustomAttributes(typeof(BsonRequiredAttribute), false).FirstOrDefault(); - if (requiredAttribute != null) { - memberMap.SetIsRequired(true); - } + var requiredAttribute = attribute as BsonRequiredAttribute; + if (requiredAttribute != null) { + memberMap.SetIsRequired(true); + } - var representationAttribute = (BsonRepresentationAttribute) memberInfo.GetCustomAttributes(typeof(BsonRepresentationAttribute), false).FirstOrDefault(); - if (representationAttribute != null) { - memberMap.SetSerializationOptions(representationAttribute.Representation); + // note: this handles subclasses of BsonSerializationOptionsAttribute also + var serializationOptionsAttribute = attribute as BsonSerializationOptionsAttribute; + if (serializationOptionsAttribute != null) { + memberMap.SetSerializationOptions(serializationOptionsAttribute.GetOptions()); + } } return memberMap; diff --git a/Bson/DefaultSerializer/Serializers/BsonPrimitiveSerializers.cs b/Bson/DefaultSerializer/Serializers/BsonPrimitiveSerializers.cs index 19a62044ab9..20af17190bd 100644 --- a/Bson/DefaultSerializer/Serializers/BsonPrimitiveSerializers.cs +++ b/Bson/DefaultSerializer/Serializers/BsonPrimitiveSerializers.cs @@ -67,25 +67,128 @@ object value #endregion } + public class DateTimeSerializationOptions { + #region private fields + private bool dateOnly = false; + private DateTimeKind kind = DateTimeKind.Utc; + private BsonType representation = BsonType.DateTime; + #endregion + + #region public properties + public bool DateOnly { + get { return dateOnly; } + set { dateOnly = value; } + } + + public DateTimeKind Kind { + get { return kind; } + set { kind = value; } + } + + public BsonType Representation { + get { return representation; } + set { representation = value; } + } + #endregion + + #region public methods + public override bool Equals( + object obj + ) { + if (obj == null || obj.GetType() != typeof(DateTimeSerializationOptions)) { + return false; + } + var other = (DateTimeSerializationOptions) obj; + return + this.dateOnly == other.dateOnly && + this.kind == other.kind && + this.representation == other.representation; + } + + public override int GetHashCode() { + // see Effective Java by Joshua Bloch + int hash = 17; + hash = 37 * hash + dateOnly.GetHashCode(); + hash = 37 * hash + kind.GetHashCode(); + hash = 37 * hash + representation.GetHashCode(); + return hash; + } + + public override string ToString() { + return string.Format("DateOnly={0}, Kind={1}, Representation={2}", dateOnly, kind, representation); + } + #endregion + } + public class DateTimeSerializer : BsonBaseSerializer { #region private static fields - private static DateTimeSerializer singleton = new DateTimeSerializer(); + private static DateTimeSerializer local = new DateTimeSerializer(new DateTimeSerializationOptions { Representation = BsonType.DateTime, Kind = DateTimeKind.Local }); + private static DateTimeSerializer localDateOnly = new DateTimeSerializer(new DateTimeSerializationOptions { Representation = BsonType.DateTime, Kind = DateTimeKind.Local, DateOnly = true }); + private static DateTimeSerializer stringDateOnly = new DateTimeSerializer(new DateTimeSerializationOptions { Representation = BsonType.String, DateOnly = true }); + private static DateTimeSerializer stringRepresentation = new DateTimeSerializer(new DateTimeSerializationOptions { Representation = BsonType.String }); + private static DateTimeSerializer unspecified = new DateTimeSerializer(new DateTimeSerializationOptions { Representation = BsonType.DateTime, Kind = DateTimeKind.Unspecified }); + private static DateTimeSerializer unspecifiedDateOnly = new DateTimeSerializer(new DateTimeSerializationOptions { Representation = BsonType.DateTime, Kind = DateTimeKind.Unspecified, DateOnly = true }); + private static DateTimeSerializer utc = new DateTimeSerializer(new DateTimeSerializationOptions { Representation = BsonType.DateTime, Kind = DateTimeKind.Utc }); + private static DateTimeSerializer utcDateOnly = new DateTimeSerializer(new DateTimeSerializationOptions { Representation = BsonType.DateTime, Kind = DateTimeKind.Utc, DateOnly = true }); + #endregion + + #region private fields + private DateTimeSerializationOptions options; #endregion #region constructors - private DateTimeSerializer() { + private DateTimeSerializer( + DateTimeSerializationOptions options + ) { + this.options = options; } #endregion #region public static properties - public static DateTimeSerializer Singleton { - get { return singleton; } + public static DateTimeSerializer Local { + get { return local; } + } + + public static DateTimeSerializer LocalDateOnly { + get { return localDateOnly; } + } + + public static DateTimeSerializer StringDateOnly { + get { return stringDateOnly; } + } + + public static DateTimeSerializer StringRepresentation { + get { return stringRepresentation; } + } + + public static DateTimeSerializer Unspecified { + get { return unspecified; } + } + + public static DateTimeSerializer UnspecifiedDateOnly { + get { return unspecifiedDateOnly; } + } + + public static DateTimeSerializer Utc { + get { return utc; } + } + + public static DateTimeSerializer UtcDateOnly { + get { return utcDateOnly; } } #endregion #region public static methods public static void RegisterSerializers() { - BsonSerializer.RegisterSerializer(typeof(DateTime), singleton); + BsonSerializer.RegisterSerializer(typeof(DateTime), utc); // default options + BsonSerializer.RegisterSerializer(typeof(DateTime), local.options, local); + BsonSerializer.RegisterSerializer(typeof(DateTime), localDateOnly.options, localDateOnly); + BsonSerializer.RegisterSerializer(typeof(DateTime), stringDateOnly.options, stringDateOnly); + BsonSerializer.RegisterSerializer(typeof(DateTime), stringRepresentation.options, stringRepresentation); + BsonSerializer.RegisterSerializer(typeof(DateTime), unspecified.options, unspecified); + BsonSerializer.RegisterSerializer(typeof(DateTime), unspecifiedDateOnly.options, unspecifiedDateOnly); + BsonSerializer.RegisterSerializer(typeof(DateTime), utc.options, utc); + BsonSerializer.RegisterSerializer(typeof(DateTime), utcDateOnly.options, utcDateOnly); } #endregion @@ -95,7 +198,41 @@ public class DateTimeSerializer : BsonBaseSerializer { Type nominalType, out string name ) { - return bsonReader.ReadDateTime(out name); + var bsonType = bsonReader.PeekBsonType(); + DateTime value; + switch (bsonType) { + case BsonType.DateTime: + value = bsonReader.ReadDateTime(out name); + break; + case BsonType.String: + if (options.DateOnly) { + value = DateTime.SpecifyKind(DateTime.Parse(bsonReader.ReadString(out name)), DateTimeKind.Utc); + } else { + value = XmlConvert.ToDateTime(bsonReader.ReadString(out name), XmlDateTimeSerializationMode.RoundtripKind); + } + break; + default: + var message = string.Format("Can't deserialize DateTime from BsonType: {0}", bsonType); + throw new FileFormatException(message); + } + + if (options.DateOnly) { + if (value.TimeOfDay != TimeSpan.Zero) { + throw new FileFormatException("TimeOfDay component for DateOnly DateTime value is not zero"); + } + value = DateTime.SpecifyKind(value, options.Kind); // not ToLocalTime or ToUniversalTime! + } else { + switch (options.Kind) { + case DateTimeKind.Local: + case DateTimeKind.Unspecified: + value = ToLocalTimeHelper(value, options.Kind); + break; + case DateTimeKind.Utc: + value = ToUniversalTimeHelper(value); + break; + } + } + return value; } public override void SerializeElement( @@ -104,7 +241,59 @@ public class DateTimeSerializer : BsonBaseSerializer { string name, object value ) { - bsonWriter.WriteDateTime(name, (DateTime) value); + var dateTimeValue = (DateTime) value; + if (options.DateOnly) { + if (dateTimeValue.TimeOfDay != TimeSpan.Zero) { + throw new BsonSerializationException("TimeOfDay component for DateOnly DateTime value is not zero"); + } + } + + switch (options.Representation) { + case BsonType.DateTime: + if (dateTimeValue.Kind != DateTimeKind.Utc) { + if (options.DateOnly) { + dateTimeValue = DateTime.SpecifyKind(dateTimeValue, DateTimeKind.Utc); // not ToUniversalTime! + } else { + dateTimeValue = ToUniversalTimeHelper(dateTimeValue); + } + } + bsonWriter.WriteDateTime(name, dateTimeValue); + break; + case BsonType.String: + if (options.DateOnly) { + bsonWriter.WriteString(name, dateTimeValue.ToString("yyyy-MM-dd")); + } else { + if (dateTimeValue == DateTime.MinValue || dateTimeValue == DateTime.MaxValue) { + dateTimeValue = DateTime.SpecifyKind(dateTimeValue, DateTimeKind.Utc); + } + bsonWriter.WriteString(name, XmlConvert.ToString(dateTimeValue, XmlDateTimeSerializationMode.RoundtripKind)); + } + break; + default: + var message = string.Format("Invalid representation for DateTime: {0}", options.Representation); + throw new BsonSerializationException(message); + } + } + #endregion + + #region private methods + private DateTime ToLocalTimeHelper( + DateTime value, + DateTimeKind kind + ) { + if (value != DateTime.MinValue && value != DateTime.MaxValue) { + value = value.ToLocalTime(); + } + return value = DateTime.SpecifyKind(value, kind); + } + + private DateTime ToUniversalTimeHelper( + DateTime value + ) { + if (value != DateTime.MinValue && value != DateTime.MaxValue) { + value = value.ToUniversalTime(); + } + return value = DateTime.SpecifyKind(value, DateTimeKind.Utc); } #endregion } diff --git a/Bson/IO/BsonBinaryWriter.cs b/Bson/IO/BsonBinaryWriter.cs index 103ee7e58fe..679e376a600 100644 --- a/Bson/IO/BsonBinaryWriter.cs +++ b/Bson/IO/BsonBinaryWriter.cs @@ -158,6 +158,9 @@ DateTime value if ((context.WriteState & BsonWriteState.Document) == 0) { throw new InvalidOperationException("WriteDateTime can only be called when WriteState is one of the document states"); } + if (value.Kind != DateTimeKind.Utc) { + throw new ArgumentException("DateTime value must be in UTC"); + } buffer.WriteByte((byte) BsonType.DateTime); buffer.WriteCString(name); long milliseconds = (long) Math.Floor((value.ToUniversalTime() - BsonConstants.UnixEpoch).TotalMilliseconds); diff --git a/Bson/IO/BsonJsonWriter.cs b/Bson/IO/BsonJsonWriter.cs index 19868be15d1..91f52c387a2 100644 --- a/Bson/IO/BsonJsonWriter.cs +++ b/Bson/IO/BsonJsonWriter.cs @@ -117,6 +117,9 @@ DateTime value if ((context.WriteState & BsonWriteState.Document) == 0) { throw new InvalidOperationException("WriteDateTime can only be called when WriteState is one of the document states"); } + if (value.Kind != DateTimeKind.Utc) { + throw new ArgumentException("DateTime value must be in UTC"); + } long milliseconds = (long) Math.Floor((value.ToUniversalTime() - BsonConstants.UnixEpoch).TotalMilliseconds); switch (settings.OutputMode) { case BsonJsonOutputMode.Strict: diff --git a/Bson/Serialization/BsonSerializer.cs b/Bson/Serialization/BsonSerializer.cs index 07ee304209c..9f0d4e845c4 100644 --- a/Bson/Serialization/BsonSerializer.cs +++ b/Bson/Serialization/BsonSerializer.cs @@ -161,7 +161,12 @@ object serializationOptions } if (serializer == null) { - var message = string.Format("No serializer found for type: {0}", type.FullName); + string message; + if (serializationOptions == null) { + message = string.Format("No serializer found for type: {0}", type.FullName); + } else { + message = string.Format("No serializer found for type: {0} (options: {1})", type.FullName, serializationOptions); + } throw new BsonSerializationException(message); } diff --git a/BsonUnitTests/BsonUnitTests.csproj b/BsonUnitTests/BsonUnitTests.csproj index 7f6a4c3723a..c20ad65c160 100644 --- a/BsonUnitTests/BsonUnitTests.csproj +++ b/BsonUnitTests/BsonUnitTests.csproj @@ -1,4 +1,4 @@ - + Debug @@ -80,6 +80,7 @@ + diff --git a/BsonUnitTests/DefaultSerializer/Serializers/BsonPrimitiveSerializerTests.cs b/BsonUnitTests/DefaultSerializer/Serializers/BsonPrimitiveSerializerTests.cs index d00f6c26870..f56973ef63b 100644 --- a/BsonUnitTests/DefaultSerializer/Serializers/BsonPrimitiveSerializerTests.cs +++ b/BsonUnitTests/DefaultSerializer/Serializers/BsonPrimitiveSerializerTests.cs @@ -21,6 +21,7 @@ using NUnit.Framework; using MongoDB.Bson; +using MongoDB.Bson.DefaultSerializer; using MongoDB.Bson.IO; using MongoDB.Bson.Serialization; @@ -63,6 +64,7 @@ public class TestClass { [TestFixture] public class DateTimeSerializerTests { public class TestClass { + [BsonDateTimeOptions(Kind = DateTimeKind.Local)] public DateTime DateTime { get; set; } } @@ -71,7 +73,7 @@ public class TestClass { var obj = new TestClass { DateTime = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Local) }; - long milliseconds = (long) (obj.DateTime.ToUniversalTime() - BsonConstants.UnixEpoch).TotalMilliseconds; + long milliseconds = (long) (DateTime.MinValue - BsonConstants.UnixEpoch).TotalMilliseconds; var json = obj.ToJson(); var expected = ("{ 'DateTime' : { '$date' : " + milliseconds.ToString() + " } }").Replace("'", "\""); Assert.AreEqual(expected, json); @@ -86,7 +88,7 @@ public class TestClass { var obj = new TestClass { DateTime = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Unspecified) }; - long milliseconds = (long) (obj.DateTime.ToUniversalTime() - BsonConstants.UnixEpoch).TotalMilliseconds; + long milliseconds = (long) (DateTime.MinValue - BsonConstants.UnixEpoch).TotalMilliseconds; var json = obj.ToJson(); var expected = ("{ 'DateTime' : { '$date' : " + milliseconds.ToString() + " } }").Replace("'", "\""); Assert.AreEqual(expected, json); @@ -101,7 +103,7 @@ public class TestClass { var obj = new TestClass { DateTime = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc) }; - long milliseconds = (long) (obj.DateTime - BsonConstants.UnixEpoch).TotalMilliseconds; + long milliseconds = (long) (DateTime.MinValue - BsonConstants.UnixEpoch).TotalMilliseconds; var json = obj.ToJson(); var expected = ("{ 'DateTime' : { '$date' : " + milliseconds.ToString() + " } }").Replace("'", "\""); Assert.AreEqual(expected, json); @@ -116,7 +118,7 @@ public class TestClass { var obj = new TestClass { DateTime = DateTime.SpecifyKind(DateTime.MaxValue, DateTimeKind.Local) }; - long milliseconds = (long) (obj.DateTime.ToUniversalTime() - BsonConstants.UnixEpoch).TotalMilliseconds; + long milliseconds = (long) (DateTime.MaxValue - BsonConstants.UnixEpoch).TotalMilliseconds; var json = obj.ToJson(); var expected = ("{ 'DateTime' : { '$date' : " + milliseconds.ToString() + " } }").Replace("'", "\""); Assert.AreEqual(expected, json); @@ -131,7 +133,7 @@ public class TestClass { var obj = new TestClass { DateTime = DateTime.SpecifyKind(DateTime.MaxValue, DateTimeKind.Unspecified) }; - long milliseconds = (long) (obj.DateTime.ToUniversalTime() - BsonConstants.UnixEpoch).TotalMilliseconds; + long milliseconds = (long) (DateTime.MaxValue - BsonConstants.UnixEpoch).TotalMilliseconds; var json = obj.ToJson(); var expected = ("{ 'DateTime' : { '$date' : " + milliseconds.ToString() + " } }").Replace("'", "\""); Assert.AreEqual(expected, json); @@ -146,7 +148,7 @@ public class TestClass { var obj = new TestClass { DateTime = DateTime.SpecifyKind(DateTime.MaxValue, DateTimeKind.Utc) }; - long milliseconds = (long) (obj.DateTime - BsonConstants.UnixEpoch).TotalMilliseconds; + long milliseconds = (long) (DateTime.MaxValue - BsonConstants.UnixEpoch).TotalMilliseconds; var json = obj.ToJson(); var expected = ("{ 'DateTime' : { '$date' : " + milliseconds.ToString() + " } }").Replace("'", "\""); Assert.AreEqual(expected, json); diff --git a/BsonUnitTests/DefaultSerializer/Serializers/DateTimeSerializerTests.cs b/BsonUnitTests/DefaultSerializer/Serializers/DateTimeSerializerTests.cs new file mode 100644 index 00000000000..5f877c695c7 --- /dev/null +++ b/BsonUnitTests/DefaultSerializer/Serializers/DateTimeSerializerTests.cs @@ -0,0 +1,626 @@ +/* Copyright 2010 10gen 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.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml; +using NUnit.Framework; + +using MongoDB.Bson; +using MongoDB.Bson.DefaultSerializer; +using MongoDB.Bson.Serialization; + +namespace MongoDB.BsonUnitTests.DefaultSerializer.Serializers.DateTimeSerializationOptions { + [TestFixture] + public class LocalTests { + public class C { + [BsonDateTimeOptions(Kind = DateTimeKind.Local)] + public DateTime DT { get; set; } + [BsonDateTimeOptions(Kind = DateTimeKind.Local, DateOnly = true)] + public DateTime D { get; set; } + } + + [Test] + public void TestMaxLocal() { + var c = new C { DT = DateTime.SpecifyKind(DateTime.MaxValue, DateTimeKind.Local) }; + c.D = c.DT.Date; + + var json = c.ToJson(); + var rep1 = ((long) (DateTime.MaxValue - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var rep2 = ((long) (DateTime.MaxValue.Date - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var expected = "{ 'DT' : { '$date' : #1 }, 'D' : { '$date' : #2 } }".Replace("#1", rep1).Replace("#2", rep2).Replace("'", "\""); + Assert.AreEqual(expected, json); + + var bson = c.ToBson(); + var rehydrated = BsonSerializer.DeserializeDocument(bson); + Assert.IsTrue(bson.SequenceEqual(rehydrated.ToBson())); + } + + [Test] + public void TestMaxUnspecified() { + var c = new C { DT = DateTime.SpecifyKind(DateTime.MaxValue, DateTimeKind.Unspecified) }; + c.D = c.DT.Date; + + var json = c.ToJson(); + var rep1 = ((long) (DateTime.MaxValue - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var rep2 = ((long) (DateTime.MaxValue.Date - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var expected = "{ 'DT' : { '$date' : #1 }, 'D' : { '$date' : #2 } }".Replace("#1", rep1).Replace("#2", rep2).Replace("'", "\""); + Assert.AreEqual(expected, json); + + var bson = c.ToBson(); + var rehydrated = BsonSerializer.DeserializeDocument(bson); + Assert.IsTrue(bson.SequenceEqual(rehydrated.ToBson())); + } + + [Test] + public void TestMaxUtc() { + var c = new C { DT = DateTime.SpecifyKind(DateTime.MaxValue, DateTimeKind.Utc) }; + c.D = c.DT.Date; + + var json = c.ToJson(); + var rep1 = ((long) (DateTime.MaxValue - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var rep2 = ((long) (DateTime.MaxValue.Date - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var expected = "{ 'DT' : { '$date' : #1 }, 'D' : { '$date' : #2 } }".Replace("#1", rep1).Replace("#2", rep2).Replace("'", "\""); + Assert.AreEqual(expected, json); + + var bson = c.ToBson(); + var rehydrated = BsonSerializer.DeserializeDocument(bson); + Assert.IsTrue(bson.SequenceEqual(rehydrated.ToBson())); + } + + [Test] + public void TestMinLocal() { + var c = new C { DT = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Local) }; + c.D = c.DT.Date; + + var json = c.ToJson(); + var rep1 = ((long) (DateTime.MinValue - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var rep2 = ((long) (DateTime.MinValue.Date - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var expected = "{ 'DT' : { '$date' : #1 }, 'D' : { '$date' : #2 } }".Replace("#1", rep1).Replace("#2", rep2).Replace("'", "\""); + Assert.AreEqual(expected, json); + + var bson = c.ToBson(); + var rehydrated = BsonSerializer.DeserializeDocument(bson); + Assert.IsTrue(bson.SequenceEqual(rehydrated.ToBson())); + } + + [Test] + public void TestMinUnspecified() { + var c = new C { DT = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Unspecified) }; + c.D = c.DT.Date; + + var json = c.ToJson(); + var rep1 = ((long) (DateTime.MinValue - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var rep2 = ((long) (DateTime.MinValue.Date - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var expected = "{ 'DT' : { '$date' : #1 }, 'D' : { '$date' : #2 } }".Replace("#1", rep1).Replace("#2", rep2).Replace("'", "\""); + Assert.AreEqual(expected, json); + + var bson = c.ToBson(); + var rehydrated = BsonSerializer.DeserializeDocument(bson); + Assert.IsTrue(bson.SequenceEqual(rehydrated.ToBson())); + } + + [Test] + public void TestMinUtc() { + var c = new C { DT = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc) }; + c.D = c.DT.Date; + + var json = c.ToJson(); + var rep1 = ((long) (DateTime.MinValue - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var rep2 = ((long) (DateTime.MinValue.Date - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var expected = "{ 'DT' : { '$date' : #1 }, 'D' : { '$date' : #2 } }".Replace("#1", rep1).Replace("#2", rep2).Replace("'", "\""); + Assert.AreEqual(expected, json); + + var bson = c.ToBson(); + var rehydrated = BsonSerializer.DeserializeDocument(bson); + Assert.IsTrue(bson.SequenceEqual(rehydrated.ToBson())); + } + + [Test] + public void TestUnixEpoch() { + var c = new C { DT = BsonConstants.UnixEpoch }; + c.D = c.DT.Date; + + var json = c.ToJson(); + var expected = "{ 'DT' : { '$date' : 0 }, 'D' : { '$date' : 0 } }".Replace("'", "\""); + Assert.AreEqual(expected, json); + + var bson = c.ToBson(); + var rehydrated = BsonSerializer.DeserializeDocument(bson); + Assert.IsTrue(bson.SequenceEqual(rehydrated.ToBson())); + } + + [Test] + public void TestNow() { + var c = new C { DT = DateTime.Now }; + c.D = c.DT.Date; + + var json = c.ToJson(); + var rep1 = ((long) (c.DT.ToUniversalTime() - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var rep2 = ((long) (c.D - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var expected = "{ 'DT' : { '$date' : #1 }, 'D' : { '$date' : #2 } }".Replace("#1", rep1).Replace("#2", rep2).Replace("'", "\""); + Assert.AreEqual(expected, json); + + var bson = c.ToBson(); + var rehydrated = BsonSerializer.DeserializeDocument(bson); + rehydrated.DT = rehydrated.DT.ToLocalTime(); + Assert.IsTrue(bson.SequenceEqual(rehydrated.ToBson())); + } + + [Test] + public void TestUtcNow() { + var c = new C { DT = DateTime.UtcNow }; + c.D = c.DT.Date; + + var json = c.ToJson(); + var rep1 = ((long) (c.DT - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var rep2 = ((long) (c.D - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var expected = "{ 'DT' : { '$date' : #1 }, 'D' : { '$date' : #2 } }".Replace("#1", rep1).Replace("#2", rep2).Replace("'", "\""); + Assert.AreEqual(expected, json); + + var bson = c.ToBson(); + var rehydrated = BsonSerializer.DeserializeDocument(bson); + Assert.IsTrue(bson.SequenceEqual(rehydrated.ToBson())); + } + } + + [TestFixture] + public class StringRepresentationTests { + public class C { + [BsonDateTimeOptions(Representation = BsonType.String)] + public DateTime DT { get; set; } + [BsonDateTimeOptions(Representation = BsonType.String, DateOnly = true)] + public DateTime D { get; set; } + } + + [Test] + public void TestMaxLocal() { + var c = new C { DT = DateTime.SpecifyKind(DateTime.MaxValue, DateTimeKind.Local) }; + c.D = c.DT.Date; + + var json = c.ToJson(); + var expected = "{ 'DT' : '9999-12-31T23:59:59.9999999Z', 'D' : '9999-12-31' }".Replace("'", "\""); + Assert.AreEqual(expected, json); + + var bson = c.ToBson(); + var rehydrated = BsonSerializer.DeserializeDocument(bson); + Assert.IsTrue(bson.SequenceEqual(rehydrated.ToBson())); + } + + [Test] + public void TestMaxUnspecified() { + var c = new C { DT = DateTime.SpecifyKind(DateTime.MaxValue, DateTimeKind.Unspecified) }; + c.D = c.DT.Date; + + var json = c.ToJson(); + var expected = "{ 'DT' : '9999-12-31T23:59:59.9999999Z', 'D' : '9999-12-31' }".Replace("'", "\""); + Assert.AreEqual(expected, json); + + var bson = c.ToBson(); + var rehydrated = BsonSerializer.DeserializeDocument(bson); + Assert.IsTrue(bson.SequenceEqual(rehydrated.ToBson())); + } + + [Test] + public void TestMaxUtc() { + var c = new C { DT = DateTime.SpecifyKind(DateTime.MaxValue, DateTimeKind.Utc) }; + c.D = c.DT.Date; + + var json = c.ToJson(); + var expected = "{ 'DT' : '9999-12-31T23:59:59.9999999Z', 'D' : '9999-12-31' }".Replace("'", "\""); + Assert.AreEqual(expected, json); + + var bson = c.ToBson(); + var rehydrated = BsonSerializer.DeserializeDocument(bson); + Assert.IsTrue(bson.SequenceEqual(rehydrated.ToBson())); + } + + [Test] + public void TestMinLocal() { + var c = new C { DT = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Local) }; + c.D = c.DT.Date; + + var json = c.ToJson(); + var expected = "{ 'DT' : '0001-01-01T00:00:00Z', 'D' : '0001-01-01' }".Replace("'", "\""); + Assert.AreEqual(expected, json); + + var bson = c.ToBson(); + var rehydrated = BsonSerializer.DeserializeDocument(bson); + Assert.IsTrue(bson.SequenceEqual(rehydrated.ToBson())); + } + + [Test] + public void TestMinUnspecified() { + var c = new C { DT = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Unspecified) }; + c.D = c.DT.Date; + + var json = c.ToJson(); + var expected = "{ 'DT' : '0001-01-01T00:00:00Z', 'D' : '0001-01-01' }".Replace("'", "\""); + Assert.AreEqual(expected, json); + + var bson = c.ToBson(); + var rehydrated = BsonSerializer.DeserializeDocument(bson); + Assert.IsTrue(bson.SequenceEqual(rehydrated.ToBson())); + } + + [Test] + public void TestMinUtc() { + var c = new C { DT = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc) }; + c.D = c.DT.Date; + + var json = c.ToJson(); + var expected = "{ 'DT' : '0001-01-01T00:00:00Z', 'D' : '0001-01-01' }".Replace("'", "\""); + Assert.AreEqual(expected, json); + + var bson = c.ToBson(); + var rehydrated = BsonSerializer.DeserializeDocument(bson); + Assert.IsTrue(bson.SequenceEqual(rehydrated.ToBson())); + } + + [Test] + public void TestUnixEpoch() { + var c = new C { DT = BsonConstants.UnixEpoch }; + c.D = c.DT.Date; + + var json = c.ToJson(); + var expected = "{ 'DT' : '1970-01-01T00:00:00Z', 'D' : '1970-01-01' }".Replace("'", "\""); + Assert.AreEqual(expected, json); + + var bson = c.ToBson(); + var rehydrated = BsonSerializer.DeserializeDocument(bson); + Assert.IsTrue(bson.SequenceEqual(rehydrated.ToBson())); + } + + [Test] + public void TestNow() { + var c = new C { DT = DateTime.Now }; + c.D = c.DT.Date; + + var json = c.ToJson(); + var rep1 = XmlConvert.ToString(c.DT, XmlDateTimeSerializationMode.RoundtripKind); + var rep2 = c.D.ToString("yyyy-MM-dd"); + var expected = "{ 'DT' : '#1', 'D' : '#2' }".Replace("#1", rep1).Replace("#2", rep2).Replace("'", "\""); + Assert.AreEqual(expected, json); + + var bson = c.ToBson(); + var rehydrated = BsonSerializer.DeserializeDocument(bson); + rehydrated.DT = rehydrated.DT.ToLocalTime(); + Assert.IsTrue(bson.SequenceEqual(rehydrated.ToBson())); + } + + [Test] + public void TestUtcNow() { + var c = new C { DT = DateTime.UtcNow }; + c.D = c.DT.Date; + + var json = c.ToJson(); + var rep1 = XmlConvert.ToString(c.DT, XmlDateTimeSerializationMode.RoundtripKind); + var rep2 = c.D.ToString("yyyy-MM-dd"); + var expected = "{ 'DT' : '#1', 'D' : '#2' }".Replace("#1", rep1).Replace("#2", rep2).Replace("'", "\""); + Assert.AreEqual(expected, json); + + var bson = c.ToBson(); + var rehydrated = BsonSerializer.DeserializeDocument(bson); + Assert.IsTrue(bson.SequenceEqual(rehydrated.ToBson())); + } + } + + [TestFixture] + public class UnspecifiedTests { + public class C { + [BsonDateTimeOptions(Kind = DateTimeKind.Unspecified)] + public DateTime DT { get; set; } + [BsonDateTimeOptions(Kind = DateTimeKind.Unspecified, DateOnly = true)] + public DateTime D { get; set; } + } + + [Test] + public void TestMaxLocal() { + var c = new C { DT = DateTime.SpecifyKind(DateTime.MaxValue, DateTimeKind.Local) }; + c.D = c.DT.Date; + + var json = c.ToJson(); + var rep1 = ((long) (DateTime.MaxValue - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var rep2 = ((long) (DateTime.MaxValue.Date - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var expected = "{ 'DT' : { '$date' : #1 }, 'D' : { '$date' : #2 } }".Replace("#1", rep1).Replace("#2", rep2).Replace("'", "\""); + Assert.AreEqual(expected, json); + + var bson = c.ToBson(); + var rehydrated = BsonSerializer.DeserializeDocument(bson); + Assert.IsTrue(bson.SequenceEqual(rehydrated.ToBson())); + } + + [Test] + public void TestMaxUnspecified() { + var c = new C { DT = DateTime.SpecifyKind(DateTime.MaxValue, DateTimeKind.Unspecified) }; + c.D = c.DT.Date; + + var json = c.ToJson(); + var rep1 = ((long) (DateTime.MaxValue - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var rep2 = ((long) (DateTime.MaxValue.Date - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var expected = "{ 'DT' : { '$date' : #1 }, 'D' : { '$date' : #2 } }".Replace("#1", rep1).Replace("#2", rep2).Replace("'", "\""); + Assert.AreEqual(expected, json); + + var bson = c.ToBson(); + var rehydrated = BsonSerializer.DeserializeDocument(bson); + Assert.IsTrue(bson.SequenceEqual(rehydrated.ToBson())); + } + + [Test] + public void TestMaxUtc() { + var c = new C { DT = DateTime.SpecifyKind(DateTime.MaxValue, DateTimeKind.Utc) }; + c.D = c.DT.Date; + + var json = c.ToJson(); + var rep1 = ((long) (DateTime.MaxValue - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var rep2 = ((long) (DateTime.MaxValue.Date - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var expected = "{ 'DT' : { '$date' : #1 }, 'D' : { '$date' : #2 } }".Replace("#1", rep1).Replace("#2", rep2).Replace("'", "\""); + Assert.AreEqual(expected, json); + + var bson = c.ToBson(); + var rehydrated = BsonSerializer.DeserializeDocument(bson); + Assert.IsTrue(bson.SequenceEqual(rehydrated.ToBson())); + } + + [Test] + public void TestMinLocal() { + var c = new C { DT = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Local) }; + c.D = c.DT.Date; + + var json = c.ToJson(); + var rep1 = ((long) (DateTime.MinValue - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var rep2 = ((long) (DateTime.MinValue.Date - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var expected = "{ 'DT' : { '$date' : #1 }, 'D' : { '$date' : #2 } }".Replace("#1", rep1).Replace("#2", rep2).Replace("'", "\""); + Assert.AreEqual(expected, json); + + var bson = c.ToBson(); + var rehydrated = BsonSerializer.DeserializeDocument(bson); + Assert.IsTrue(bson.SequenceEqual(rehydrated.ToBson())); + } + + [Test] + public void TestMinUnspecified() { + var c = new C { DT = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Unspecified) }; + c.D = c.DT.Date; + + var json = c.ToJson(); + var rep1 = ((long) (DateTime.MinValue - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var rep2 = ((long) (DateTime.MinValue.Date - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var expected = "{ 'DT' : { '$date' : #1 }, 'D' : { '$date' : #2 } }".Replace("#1", rep1).Replace("#2", rep2).Replace("'", "\""); + Assert.AreEqual(expected, json); + + var bson = c.ToBson(); + var rehydrated = BsonSerializer.DeserializeDocument(bson); + Assert.IsTrue(bson.SequenceEqual(rehydrated.ToBson())); + } + + [Test] + public void TestMinUtc() { + var c = new C { DT = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc) }; + c.D = c.DT.Date; + + var json = c.ToJson(); + var rep1 = ((long) (DateTime.MinValue - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var rep2 = ((long) (DateTime.MinValue.Date - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var expected = "{ 'DT' : { '$date' : #1 }, 'D' : { '$date' : #2 } }".Replace("#1", rep1).Replace("#2", rep2).Replace("'", "\""); + Assert.AreEqual(expected, json); + + var bson = c.ToBson(); + var rehydrated = BsonSerializer.DeserializeDocument(bson); + Assert.IsTrue(bson.SequenceEqual(rehydrated.ToBson())); + } + + [Test] + public void TestUnixEpoch() { + var c = new C { DT = BsonConstants.UnixEpoch }; + c.D = c.DT.Date; + + var json = c.ToJson(); + var expected = "{ 'DT' : { '$date' : 0 }, 'D' : { '$date' : 0 } }".Replace("'", "\""); + Assert.AreEqual(expected, json); + + var bson = c.ToBson(); + var rehydrated = BsonSerializer.DeserializeDocument(bson); + Assert.IsTrue(bson.SequenceEqual(rehydrated.ToBson())); + } + + [Test] + public void TestNow() { + var c = new C { DT = DateTime.Now }; + c.D = c.DT.Date; + + var json = c.ToJson(); + var rep1 = ((long) (c.DT.ToUniversalTime() - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var rep2 = ((long) (c.D - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var expected = "{ 'DT' : { '$date' : #1 }, 'D' : { '$date' : #2 } }".Replace("#1", rep1).Replace("#2", rep2).Replace("'", "\""); + Assert.AreEqual(expected, json); + + var bson = c.ToBson(); + var rehydrated = BsonSerializer.DeserializeDocument(bson); + Assert.IsTrue(bson.SequenceEqual(rehydrated.ToBson())); + } + + [Test] + public void TestUtcNow() { + var c = new C { DT = DateTime.UtcNow }; + c.D = c.DT.Date; + + var json = c.ToJson(); + var rep1 = ((long) (c.DT - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var rep2 = ((long) (c.D - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var expected = "{ 'DT' : { '$date' : #1 }, 'D' : { '$date' : #2 } }".Replace("#1", rep1).Replace("#2", rep2).Replace("'", "\""); + Assert.AreEqual(expected, json); + + var bson = c.ToBson(); + var rehydrated = BsonSerializer.DeserializeDocument(bson); + Assert.IsTrue(bson.SequenceEqual(rehydrated.ToBson())); + } + } + + [TestFixture] + public class UtcTests { + public class C { + [BsonDateTimeOptions(Kind = DateTimeKind.Utc)] + public DateTime DT { get; set; } + [BsonDateTimeOptions(Kind = DateTimeKind.Utc, DateOnly = true)] + public DateTime D { get; set; } + } + + [Test] + public void TestMaxLocal() { + var c = new C { DT = DateTime.SpecifyKind(DateTime.MaxValue, DateTimeKind.Local) }; + c.D = c.DT.Date; + + var json = c.ToJson(); + var rep1 = ((long) (DateTime.MaxValue - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var rep2 = ((long) (DateTime.MaxValue.Date - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var expected = "{ 'DT' : { '$date' : #1 }, 'D' : { '$date' : #2 } }".Replace("#1", rep1).Replace("#2", rep2).Replace("'", "\""); + Assert.AreEqual(expected, json); + + var bson = c.ToBson(); + var rehydrated = BsonSerializer.DeserializeDocument(bson); + Assert.IsTrue(bson.SequenceEqual(rehydrated.ToBson())); + } + + [Test] + public void TestMaxUnspecified() { + var c = new C { DT = DateTime.SpecifyKind(DateTime.MaxValue, DateTimeKind.Unspecified) }; + c.D = c.DT.Date; + + var json = c.ToJson(); + var rep1 = ((long) (DateTime.MaxValue - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var rep2 = ((long) (DateTime.MaxValue.Date - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var expected = "{ 'DT' : { '$date' : #1 }, 'D' : { '$date' : #2 } }".Replace("#1", rep1).Replace("#2", rep2).Replace("'", "\""); + Assert.AreEqual(expected, json); + + var bson = c.ToBson(); + var rehydrated = BsonSerializer.DeserializeDocument(bson); + Assert.IsTrue(bson.SequenceEqual(rehydrated.ToBson())); + } + + [Test] + public void TestMaxUtc() { + var c = new C { DT = DateTime.SpecifyKind(DateTime.MaxValue, DateTimeKind.Utc) }; + c.D = c.DT.Date; + + var json = c.ToJson(); + var rep1 = ((long) (DateTime.MaxValue - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var rep2 = ((long) (DateTime.MaxValue.Date - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var expected = "{ 'DT' : { '$date' : #1 }, 'D' : { '$date' : #2 } }".Replace("#1", rep1).Replace("#2", rep2).Replace("'", "\""); + Assert.AreEqual(expected, json); + + var bson = c.ToBson(); + var rehydrated = BsonSerializer.DeserializeDocument(bson); + Assert.IsTrue(bson.SequenceEqual(rehydrated.ToBson())); + } + + [Test] + public void TestMinLocal() { + var c = new C { DT = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Local) }; + c.D = c.DT.Date; + + var json = c.ToJson(); + var rep1 = ((long) (DateTime.MinValue - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var rep2 = ((long) (DateTime.MinValue.Date - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var expected = "{ 'DT' : { '$date' : #1 }, 'D' : { '$date' : #2 } }".Replace("#1", rep1).Replace("#2", rep2).Replace("'", "\""); + Assert.AreEqual(expected, json); + + var bson = c.ToBson(); + var rehydrated = BsonSerializer.DeserializeDocument(bson); + Assert.IsTrue(bson.SequenceEqual(rehydrated.ToBson())); + } + + [Test] + public void TestMinUnspecified() { + var c = new C { DT = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Unspecified) }; + c.D = c.DT.Date; + + var json = c.ToJson(); + var rep1 = ((long) (DateTime.MinValue - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var rep2 = ((long) (DateTime.MinValue.Date - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var expected = "{ 'DT' : { '$date' : #1 }, 'D' : { '$date' : #2 } }".Replace("#1", rep1).Replace("#2", rep2).Replace("'", "\""); + Assert.AreEqual(expected, json); + + var bson = c.ToBson(); + var rehydrated = BsonSerializer.DeserializeDocument(bson); + Assert.IsTrue(bson.SequenceEqual(rehydrated.ToBson())); + } + + [Test] + public void TestMinUtc() { + var c = new C { DT = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc) }; + c.D = c.DT.Date; + + var json = c.ToJson(); + var rep1 = ((long) (DateTime.MinValue - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var rep2 = ((long) (DateTime.MinValue.Date - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var expected = "{ 'DT' : { '$date' : #1 }, 'D' : { '$date' : #2 } }".Replace("#1", rep1).Replace("#2", rep2).Replace("'", "\""); + Assert.AreEqual(expected, json); + + var bson = c.ToBson(); + var rehydrated = BsonSerializer.DeserializeDocument(bson); + Assert.IsTrue(bson.SequenceEqual(rehydrated.ToBson())); + } + + [Test] + public void TestUnixEpoch() { + var c = new C { DT = BsonConstants.UnixEpoch }; + c.D = c.DT.Date; + + var json = c.ToJson(); + var expected = "{ 'DT' : { '$date' : 0 }, 'D' : { '$date' : 0 } }".Replace("'", "\""); + Assert.AreEqual(expected, json); + + var bson = c.ToBson(); + var rehydrated = BsonSerializer.DeserializeDocument(bson); + Assert.IsTrue(bson.SequenceEqual(rehydrated.ToBson())); + } + + [Test] + public void TestNow() { + var c = new C { DT = DateTime.Now }; + c.D = c.DT.Date; + + var json = c.ToJson(); + var rep1 = ((long) (c.DT.ToUniversalTime() - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var rep2 = ((long) (c.D - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var expected = "{ 'DT' : { '$date' : #1 }, 'D' : { '$date' : #2 } }".Replace("#1", rep1).Replace("#2", rep2).Replace("'", "\""); + Assert.AreEqual(expected, json); + + var bson = c.ToBson(); + var rehydrated = BsonSerializer.DeserializeDocument(bson); + rehydrated.DT = rehydrated.DT.ToLocalTime(); + Assert.IsTrue(bson.SequenceEqual(rehydrated.ToBson())); + } + + [Test] + public void TestUtcNow() { + var c = new C { DT = DateTime.UtcNow }; + c.D = c.DT.Date; + + var json = c.ToJson(); + var rep1 = ((long) (c.DT - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var rep2 = ((long) (c.D - BsonConstants.UnixEpoch).TotalMilliseconds).ToString(); + var expected = "{ 'DT' : { '$date' : #1 }, 'D' : { '$date' : #2 } }".Replace("#1", rep1).Replace("#2", rep2).Replace("'", "\""); + Assert.AreEqual(expected, json); + + var bson = c.ToBson(); + var rehydrated = BsonSerializer.DeserializeDocument(bson); + Assert.IsTrue(bson.SequenceEqual(rehydrated.ToBson())); + } + } +}