Skip to content

Commit b7f6663

Browse files
committed
CSHARP-5737: Add legacy representation for TimeOnly
1 parent dd3f679 commit b7f6663

File tree

3 files changed

+129
-12
lines changed

3 files changed

+129
-12
lines changed

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515

1616
using System;
1717
using MongoDB.Bson.IO;
18-
using MongoDB.Bson.Serialization.Attributes;
1918
using MongoDB.Bson.Serialization.Options;
2019

2120
namespace MongoDB.Bson.Serialization.Serializers

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

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
*/
1515

1616
using System;
17+
using MongoDB.Bson.IO;
1718
using MongoDB.Bson.Serialization.Options;
1819

1920
namespace MongoDB.Bson.Serialization.Serializers
@@ -32,7 +33,20 @@ public sealed class TimeOnlySerializer: StructSerializerBase<TimeOnly>, IReprese
3233
/// </summary>
3334
public static TimeOnlySerializer Instance => __instance;
3435

36+
// private constants
37+
private static class Flags
38+
{
39+
public const long Hour = 1;
40+
public const long Minute = 2;
41+
public const long Second = 4;
42+
public const long Millisecond = 8;
43+
public const long Microsecond = 16;
44+
public const long Nanosecond = 32;
45+
public const long Ticks = 64;
46+
}
47+
3548
// private fields
49+
private readonly SerializerHelper _helper;
3650
private readonly BsonType _representation;
3751
private readonly TimeOnlyUnits _units;
3852

@@ -58,7 +72,7 @@ public TimeOnlySerializer(BsonType representation)
5872
/// Initializes a new instance of the <see cref="TimeOnlySerializer"/> class.
5973
/// </summary>
6074
/// <param name="representation">The representation.</param>
61-
/// <param name="units">The units.</param>
75+
/// <param name="units">The units. Ignored if representation is BsonType.Document.</param>
6276
public TimeOnlySerializer(BsonType representation, TimeOnlyUnits units)
6377
{
6478
switch (representation)
@@ -67,6 +81,7 @@ public TimeOnlySerializer(BsonType representation, TimeOnlyUnits units)
6781
case BsonType.Int32:
6882
case BsonType.Int64:
6983
case BsonType.String:
84+
case BsonType.Document:
7085
break;
7186

7287
default:
@@ -75,6 +90,17 @@ public TimeOnlySerializer(BsonType representation, TimeOnlyUnits units)
7590

7691
_representation = representation;
7792
_units = units;
93+
94+
_helper = new SerializerHelper
95+
(
96+
new SerializerHelper.Member("Hour", Flags.Hour, isOptional: true),
97+
new SerializerHelper.Member("Minute", Flags.Minute, isOptional: true),
98+
new SerializerHelper.Member("Second", Flags.Second, isOptional: true),
99+
new SerializerHelper.Member("Millisecond", Flags.Millisecond, isOptional: true),
100+
new SerializerHelper.Member("Microsecond", Flags.Microsecond, isOptional: true),
101+
new SerializerHelper.Member("Nanosecond", Flags.Nanosecond, isOptional: true),
102+
new SerializerHelper.Member("Ticks", Flags.Ticks, isOptional: false)
103+
);
78104
}
79105

80106
// public properties
@@ -102,6 +128,7 @@ public override TimeOnly Deserialize(BsonDeserializationContext context, BsonDes
102128
BsonType.Int64 => FromInt64(bsonReader.ReadInt64(), _units),
103129
BsonType.Int32 => FromInt32(bsonReader.ReadInt32(), _units),
104130
BsonType.Double => FromDouble(bsonReader.ReadDouble(), _units),
131+
BsonType.Document => FromDocument(context),
105132
_ => throw CreateCannotDeserializeFromBsonTypeException(bsonType)
106133
};
107134
}
@@ -145,6 +172,20 @@ public override void Serialize(BsonSerializationContext context, BsonSerializati
145172
bsonWriter.WriteString(value.ToString("o"));
146173
break;
147174

175+
case BsonType.Document:
176+
bsonWriter.WriteStartDocument();
177+
bsonWriter.WriteInt32("Hour", value.Hour);
178+
bsonWriter.WriteInt32("Minute", value.Minute);
179+
bsonWriter.WriteInt32("Second", value.Second);
180+
bsonWriter.WriteInt32("Millisecond", value.Millisecond);
181+
#if NET7_0_OR_GREATER
182+
bsonWriter.WriteInt32("Microsecond", value.Microsecond);
183+
bsonWriter.WriteInt32("Nanosecond", value.Nanosecond);
184+
#endif
185+
bsonWriter.WriteInt64("Ticks", value.Ticks);
186+
bsonWriter.WriteEndDocument();
187+
break;
188+
148189
default:
149190
throw new BsonSerializationException($"'{_representation}' is not a valid TimeOnly representation.");
150191
}
@@ -196,6 +237,29 @@ private TimeOnly FromInt64(long value, TimeOnlyUnits units)
196237
: new TimeOnly(value * TicksPerUnit(units));
197238
}
198239

240+
private TimeOnly FromDocument(BsonDeserializationContext context)
241+
{
242+
var bsonReader = context.Reader;
243+
var ticks = 0L;
244+
245+
_helper.DeserializeMembers(context, (_, flag) =>
246+
{
247+
switch (flag)
248+
{
249+
case Flags.Hour:
250+
case Flags.Minute:
251+
case Flags.Second:
252+
case Flags.Millisecond:
253+
case Flags.Microsecond:
254+
case Flags.Nanosecond:
255+
bsonReader.SkipValue(); break; // ignore value (use Ticks instead)
256+
case Flags.Ticks: ticks = Int64Serializer.Instance.Deserialize(context); break;
257+
}
258+
});
259+
260+
return FromInt64(ticks, TimeOnlyUnits.Ticks);
261+
}
262+
199263
private long TicksPerUnit(TimeOnlyUnits units)
200264
{
201265
return units switch

tests/MongoDB.Bson.Tests/Serialization/Serializers/TimeOnlySerializerTests.cs

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,21 @@ public void Attribute_should_set_correct_units()
4343
Microseconds = timeOnly,
4444
Ticks = timeOnly,
4545
Nanoseconds = timeOnly,
46+
Document = timeOnly
4647
};
4748

4849
var json = testObj.ToJson();
4950

50-
var expected = "{ \"Hours\" : 13, "
51-
+ "\"Minutes\" : 804, "
52-
+ "\"Seconds\" : 48293, "
53-
+ "\"Milliseconds\" : 48293000, "
54-
+ "\"Microseconds\" : 48293000000, "
55-
+ "\"Ticks\" : 482930000000, "
56-
+ "\"Nanoseconds\" : 48293000000000 }";
51+
var baseString = """
52+
{ "Hours" : 13, "Minutes" : 804, "Seconds" : 48293, "Milliseconds" : 48293000, "Microseconds" : 48293000000, "Ticks" : 482930000000, "Nanoseconds" : 48293000000000
53+
""";
54+
55+
var documentString = """
56+
{ "Hour" : 13, "Minute" : 24, "Second" : 53, "Millisecond" : 0, "Ticks" : 482930000000 }
57+
""";
58+
59+
60+
var expected = baseString + """, "Document" : """ + documentString + " }";
5761
Assert.Equal(expected, json);
5862
}
5963

@@ -69,7 +73,7 @@ public void Constructor_with_no_arguments_should_return_expected_result()
6973
[Theory]
7074
[ParameterAttributeData]
7175
public void Constructor_with_representation_should_return_expected_result(
72-
[Values(BsonType.String, BsonType.Int64, BsonType.Int32, BsonType.Double)]
76+
[Values(BsonType.String, BsonType.Int64, BsonType.Int32, BsonType.Double, BsonType.Document)]
7377
BsonType representation,
7478
[Values(TimeOnlyUnits.Ticks, TimeOnlyUnits.Hours, TimeOnlyUnits.Minutes, TimeOnlyUnits.Seconds,
7579
TimeOnlyUnits.Milliseconds, TimeOnlyUnits.Microseconds, TimeOnlyUnits.Nanoseconds)]
@@ -81,6 +85,53 @@ public void Constructor_with_representation_should_return_expected_result(
8185
subject.Units.Should().Be(units);
8286
}
8387

88+
[Theory]
89+
[InlineData("""{ "x" : { Ticks: { "$numberLong" : "307255946583" } } }""","08:32:05.5946583" )]
90+
[InlineData("""{ "x" : { Ticks: { "$numberLong" : "0" } } }""","00:00:00.0000000" )]
91+
[InlineData("""{ "x" : { Ticks: { "$numberLong" : "863999999999" } } }""","23:59:59.9999999" )]
92+
public void Deserialize_with_document_should_have_expected_result(string json, string expectedResult)
93+
{
94+
var subject = new TimeOnlySerializer();
95+
TestDeserialize(subject, json, expectedResult);
96+
}
97+
98+
[Theory]
99+
[InlineData("""{ "x" : { Ticks: { "$numberDouble" : "307255946583" } } }""","08:32:05.5946583" )]
100+
[InlineData("""{ "x" : { Ticks: { "$numberDecimal" : "307255946583" } } }""","08:32:05.5946583" )]
101+
public void Deserialize_with_document_should_be_forgiving_of_actual_numeric_type(string json, string expectedResult)
102+
{
103+
var subject = new TimeOnlySerializer();
104+
TestDeserialize(subject, json, expectedResult);
105+
}
106+
107+
[Theory]
108+
[InlineData("""
109+
{ "x" : { Hour: { "$numberInt": 0 }, Minute: { "$numberInt": 0 }, Second: { "$numberInt": 0 },
110+
Millisecond: { "$numberInt": 0 }, Microsecond: { "$numberInt": 0 }, Nanosecond: { "$numberInt": 0 },
111+
Ticks: { "$numberDouble" : "307255946583" } } }
112+
""","08:32:05.5946583" )]
113+
public void Deserialize_with_document_should_ignore_other_time_components(string json, string expectedResult)
114+
{
115+
var subject = new TimeOnlySerializer();
116+
TestDeserialize(subject, json, expectedResult);
117+
}
118+
119+
[Theory]
120+
[InlineData("""{ "x" : { "Unknown": "test", Ticks: { "$numberDouble" : "307255946583" } } }""" )]
121+
public void Deserialize_with_document_should_throw_when_field_is_unknown(string json)
122+
{
123+
var subject = new TimeOnlySerializer();
124+
125+
using var reader = new JsonReader(json);
126+
reader.ReadStartDocument();
127+
reader.ReadName("x");
128+
var context = BsonDeserializationContext.CreateRoot(reader);
129+
130+
var exception = Record.Exception(() => subject.Deserialize(context));
131+
exception.Should().BeOfType<BsonSerializationException>();
132+
exception.Message.Should().Be("Invalid element: 'Unknown'.");
133+
}
134+
84135
[Theory]
85136
[InlineData("""{ "x" : "08:32:05.5946583" }""","08:32:05.5946583" )]
86137
[InlineData("""{ "x" : "00:00:00.0000000" }""","00:00:00.0000000")]
@@ -407,8 +458,8 @@ public void Serializer_should_be_registered()
407458
[Theory]
408459
[ParameterAttributeData]
409460
public void WithRepresentation_should_return_expected_result(
410-
[Values(BsonType.String, BsonType.Int64, BsonType.Int32, BsonType.Double)] BsonType oldRepresentation,
411-
[Values(BsonType.String, BsonType.Int64, BsonType.Int32, BsonType.Double)] BsonType newRepresentation)
461+
[Values(BsonType.String, BsonType.Int64, BsonType.Int32, BsonType.Double, BsonType.Document)] BsonType oldRepresentation,
462+
[Values(BsonType.String, BsonType.Int64, BsonType.Int32, BsonType.Double, BsonType.Document)] BsonType newRepresentation)
412463
{
413464
var subject = new TimeOnlySerializer(oldRepresentation);
414465

@@ -473,6 +524,9 @@ private class TestClass
473524

474525
[BsonTimeOnlyOptions(BsonType.Int64, TimeOnlyUnits.Nanoseconds )]
475526
public TimeOnly Nanoseconds { get; set; }
527+
528+
[BsonTimeOnlyOptions(BsonType.Document, TimeOnlyUnits.Nanoseconds )]
529+
public TimeOnly Document { get; set; }
476530
}
477531
}
478532
#endif

0 commit comments

Comments
 (0)