Skip to content

Commit 1c1e46c

Browse files
authored
CSHARP-5261: Add DateOnly support (#1440)
* CSHARP-5261: Add DateOnly support * Added naming to class sections * Small correction * Added basic test plus small fix * Various fixes for dateOnly * Removed unused * small correction * Improved testing * Corrected testing * Small corrections * Removed unnecessary test * Fixes according to review * Fixed formatting * Improved performance * Corrections according to PR * Corrected date format and null operator * Added test to verify that serializer is correctly registered
1 parent 40e27f9 commit 1c1e46c

File tree

6 files changed

+492
-30
lines changed

6 files changed

+492
-30
lines changed

src/MongoDB.Bson/Serialization/PrimitiveSerializationProvider.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ static PrimitiveSerializationProvider()
4040
{ typeof(Char), typeof(CharSerializer) },
4141
{ typeof(CultureInfo), typeof(CultureInfoSerializer) },
4242
{ typeof(DateTime), typeof(DateTimeSerializer) },
43+
#if NET6_0_OR_GREATER
44+
{ typeof(DateOnly), typeof(DateOnlySerializer) },
45+
#endif
4346
{ typeof(DateTimeOffset), typeof(DateTimeOffsetSerializer) },
4447
{ typeof(Decimal), typeof(DecimalSerializer) },
4548
{ typeof(Decimal128), typeof(Decimal128Serializer) },
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System;
17+
using MongoDB.Bson.IO;
18+
using MongoDB.Bson.Serialization.Options;
19+
20+
namespace MongoDB.Bson.Serialization.Serializers
21+
{
22+
#if NET6_0_OR_GREATER
23+
/// <summary>
24+
/// Represents a serializer for DateOnlys.
25+
/// </summary>
26+
public sealed class DateOnlySerializer : StructSerializerBase<DateOnly>, IRepresentationConfigurable<DateOnlySerializer>
27+
{
28+
// static
29+
private static readonly DateOnlySerializer __instance = new DateOnlySerializer();
30+
31+
/// <summary>
32+
/// Gets the default DateOnlySerializer.
33+
/// </summary>
34+
public static DateOnlySerializer Instance => __instance;
35+
36+
// private constants
37+
private static class Flags
38+
{
39+
public const long DateTime = 1;
40+
public const long Ticks = 2;
41+
}
42+
43+
// private fields
44+
private readonly RepresentationConverter _converter;
45+
private readonly SerializerHelper _helper;
46+
private readonly BsonType _representation;
47+
48+
// constructors
49+
/// <summary>
50+
/// Initializes a new instance of the <see cref="DateOnlySerializer"/> class.
51+
/// </summary>
52+
public DateOnlySerializer()
53+
: this(BsonType.DateTime)
54+
{
55+
}
56+
57+
/// <summary>
58+
/// Initializes a new instance of the <see cref="DateOnlySerializer"/> class.
59+
/// </summary>
60+
/// <param name="representation">The representation.</param>
61+
public DateOnlySerializer(BsonType representation)
62+
{
63+
switch (representation)
64+
{
65+
case BsonType.DateTime:
66+
case BsonType.Document:
67+
case BsonType.Int64:
68+
case BsonType.String:
69+
break;
70+
71+
default:
72+
throw new ArgumentException($"{representation} is not a valid representation for a DateOnlySerializer.");
73+
}
74+
75+
_representation = representation;
76+
_converter = new RepresentationConverter(false, false);
77+
78+
_helper = new SerializerHelper
79+
(
80+
new SerializerHelper.Member("DateTime", Flags.DateTime),
81+
new SerializerHelper.Member("Ticks", Flags.Ticks)
82+
);
83+
}
84+
85+
// public properties
86+
/// <inheritdoc />
87+
public BsonType Representation => _representation;
88+
89+
//public methods
90+
/// <inheritdoc />
91+
public override DateOnly Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
92+
{
93+
var bsonReader = context.Reader;
94+
DateOnly value;
95+
96+
var bsonType = bsonReader.GetCurrentBsonType();
97+
98+
switch (bsonType)
99+
{
100+
case BsonType.DateTime:
101+
value = VerifyAndMakeDateOnly(BsonUtils.ToDateTimeFromMillisecondsSinceEpoch(bsonReader.ReadDateTime()));
102+
break;
103+
104+
case BsonType.Document:
105+
value = default;
106+
_helper.DeserializeMembers(context, (_, flag) =>
107+
{
108+
switch (flag)
109+
{
110+
case Flags.DateTime: bsonReader.SkipValue(); break; // ignore value (use Ticks instead)
111+
case Flags.Ticks:
112+
value = VerifyAndMakeDateOnly(new DateTime(Int64Serializer.Instance.Deserialize(context), DateTimeKind.Utc));
113+
break;
114+
}
115+
});
116+
break;
117+
118+
case BsonType.Decimal128:
119+
value = VerifyAndMakeDateOnly(new DateTime(_converter.ToInt64(bsonReader.ReadDecimal128()), DateTimeKind.Utc));
120+
break;
121+
122+
case BsonType.Double:
123+
value = VerifyAndMakeDateOnly(new DateTime(_converter.ToInt64(bsonReader.ReadDouble()), DateTimeKind.Utc));
124+
break;
125+
126+
case BsonType.Int32:
127+
value = VerifyAndMakeDateOnly(new DateTime(bsonReader.ReadInt32(), DateTimeKind.Utc));
128+
break;
129+
130+
case BsonType.Int64:
131+
value = VerifyAndMakeDateOnly(new DateTime(bsonReader.ReadInt64(), DateTimeKind.Utc));
132+
break;
133+
134+
case BsonType.String:
135+
value = DateOnly.ParseExact(bsonReader.ReadString(), "yyyy-MM-dd");
136+
break;
137+
138+
default:
139+
throw CreateCannotDeserializeFromBsonTypeException(bsonType);
140+
}
141+
142+
return value;
143+
144+
DateOnly VerifyAndMakeDateOnly(DateTime dt)
145+
{
146+
if (dt.TimeOfDay != TimeSpan.Zero)
147+
{
148+
throw new FormatException("Deserialized value has a non-zero time component.");
149+
}
150+
151+
return DateOnly.FromDateTime(dt);
152+
}
153+
}
154+
155+
/// <inheritdoc/>
156+
public override bool Equals(object obj)
157+
{
158+
if (object.ReferenceEquals(obj, null)) { return false; }
159+
if (object.ReferenceEquals(this, obj)) { return true; }
160+
return
161+
base.Equals(obj) &&
162+
obj is DateOnlySerializer other &&
163+
_representation.Equals(other._representation);
164+
}
165+
166+
/// <inheritdoc/>
167+
public override int GetHashCode() => 0;
168+
169+
/// <inheritdoc />
170+
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, DateOnly value)
171+
{
172+
var bsonWriter = context.Writer;
173+
174+
var utcDateTime = value.ToDateTime(new TimeOnly(0), DateTimeKind.Utc);
175+
var millisecondsSinceEpoch = BsonUtils.ToMillisecondsSinceEpoch(utcDateTime);
176+
177+
switch (_representation)
178+
{
179+
case BsonType.DateTime:
180+
bsonWriter.WriteDateTime(millisecondsSinceEpoch);
181+
break;
182+
183+
case BsonType.Document:
184+
bsonWriter.WriteStartDocument();
185+
bsonWriter.WriteDateTime("DateTime", millisecondsSinceEpoch);
186+
bsonWriter.WriteInt64("Ticks", utcDateTime.Ticks);
187+
bsonWriter.WriteEndDocument();
188+
break;
189+
190+
case BsonType.Int64:
191+
bsonWriter.WriteInt64(utcDateTime.Ticks);
192+
break;
193+
194+
case BsonType.String:
195+
bsonWriter.WriteString(value.ToString("yyyy-MM-dd"));
196+
break;
197+
198+
default:
199+
throw new BsonSerializationException($"'{_representation}' is not a valid DateOnly representation.");
200+
}
201+
}
202+
203+
/// <inheritdoc />
204+
public DateOnlySerializer WithRepresentation(BsonType representation)
205+
{
206+
return representation == _representation ? this : new DateOnlySerializer(representation);
207+
}
208+
209+
// explicit interface implementations
210+
IBsonSerializer IRepresentationConfigurable.WithRepresentation(BsonType representation)
211+
{
212+
return WithRepresentation(representation);
213+
}
214+
}
215+
#endif
216+
}
217+

src/MongoDB.Bson/Serialization/Serializers/DateTimeSerializer.cs

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
using System;
1717
using MongoDB.Bson.IO;
18+
using MongoDB.Bson.Serialization.Options;
1819

1920
namespace MongoDB.Bson.Serialization.Serializers
2021
{
@@ -51,6 +52,7 @@ private static class Flags
5152
private readonly SerializerHelper _helper;
5253
private readonly DateTimeKind _kind;
5354
private readonly BsonType _representation;
55+
private readonly RepresentationConverter _converter;
5456

5557
// constructors
5658
/// <summary>
@@ -126,6 +128,7 @@ private DateTimeSerializer(bool dateOnly, DateTimeKind kind, BsonType representa
126128
_dateOnly = dateOnly;
127129
_kind = kind;
128130
_representation = representation;
131+
_converter = new RepresentationConverter(false, false);
129132

130133
_helper = new SerializerHelper
131134
(
@@ -138,54 +141,36 @@ private DateTimeSerializer(bool dateOnly, DateTimeKind kind, BsonType representa
138141
/// <summary>
139142
/// Gets an instance of DateTimeSerializer with DateOnly=true.
140143
/// </summary>
141-
public static DateTimeSerializer DateOnlyInstance
142-
{
143-
get { return __dateOnlyInstance; }
144-
}
144+
public static DateTimeSerializer DateOnlyInstance => __dateOnlyInstance;
145145

146146
/// <summary>
147147
/// Gets an instance of DateTimeSerializer with Kind=Local.
148148
/// </summary>
149-
public static DateTimeSerializer LocalInstance
150-
{
151-
get { return __localInstance; }
152-
}
149+
public static DateTimeSerializer LocalInstance => __localInstance;
153150

154151
/// <summary>
155152
/// Gets an instance of DateTimeSerializer with Kind=Utc.
156153
/// </summary>
157-
public static DateTimeSerializer UtcInstance
158-
{
159-
get { return __utcInstance; }
160-
}
154+
public static DateTimeSerializer UtcInstance => __utcInstance;
161155

162156
// public properties
163157
/// <summary>
164158
/// Gets whether this DateTime consists of a Date only.
165159
/// </summary>
166-
public bool DateOnly
167-
{
168-
get { return _dateOnly; }
169-
}
160+
public bool DateOnly => _dateOnly;
170161

171162
/// <summary>
172163
/// Gets the DateTimeKind (Local, Unspecified or Utc).
173164
/// </summary>
174-
public DateTimeKind Kind
175-
{
176-
get { return _kind; }
177-
}
165+
public DateTimeKind Kind => _kind;
178166

179167
/// <summary>
180168
/// Gets the external representation.
181169
/// </summary>
182170
/// <value>
183171
/// The representation.
184172
/// </value>
185-
public BsonType Representation
186-
{
187-
get { return _representation; }
188-
}
173+
public BsonType Representation => _representation;
189174

190175
// public methods
191176
/// <summary>
@@ -208,7 +193,7 @@ public override DateTime Deserialize(BsonDeserializationContext context, BsonDes
208193
break;
209194

210195
case BsonType.Document:
211-
value = default(DateTime);
196+
value = default;
212197
_helper.DeserializeMembers(context, (elementName, flag) =>
213198
{
214199
switch (flag)
@@ -219,6 +204,18 @@ public override DateTime Deserialize(BsonDeserializationContext context, BsonDes
219204
});
220205
break;
221206

207+
case BsonType.Decimal128:
208+
value = DateTime.SpecifyKind(new DateTime(_converter.ToInt64(bsonReader.ReadDecimal128())), DateTimeKind.Utc);
209+
break;
210+
211+
case BsonType.Double:
212+
value = DateTime.SpecifyKind(new DateTime(_converter.ToInt64(bsonReader.ReadDouble())), DateTimeKind.Utc);
213+
break;
214+
215+
case BsonType.Int32:
216+
value = DateTime.SpecifyKind(new DateTime(bsonReader.ReadInt32()), DateTimeKind.Utc);
217+
break;
218+
222219
case BsonType.Int64:
223220
value = DateTime.SpecifyKind(new DateTime(bsonReader.ReadInt64()), DateTimeKind.Utc);
224221
break;

0 commit comments

Comments
 (0)