Skip to content

Commit d4ffe76

Browse files
authored
CSHARP-5262: Add TimeOnly support (#1467)
1 parent dacd83c commit d4ffe76

File tree

5 files changed

+856
-0
lines changed

5 files changed

+856
-0
lines changed
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
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.Serialization.Options;
18+
using MongoDB.Bson.Serialization.Serializers;
19+
20+
namespace MongoDB.Bson.Serialization.Attributes
21+
{
22+
#if NET6_0_OR_GREATER
23+
/// <summary>
24+
/// Specifies the external representation and related options for this field or property.
25+
/// </summary>
26+
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
27+
public class BsonTimeOnlyOptionsAttribute : BsonSerializationOptionsAttribute
28+
{
29+
// private fields
30+
private BsonType _representation;
31+
private TimeOnlyUnits _units;
32+
33+
// constructors
34+
/// <summary>
35+
/// Initializes a new instance of the BsonTimeOnlyOptionsAttribute class.
36+
/// </summary>
37+
/// <param name="representation">The external representation.</param>
38+
public BsonTimeOnlyOptionsAttribute(BsonType representation)
39+
{
40+
_representation = representation;
41+
}
42+
43+
/// <summary>
44+
/// Initializes a new instance of the BsonTimeOnlyOptionsAttribute class.
45+
/// </summary>
46+
/// <param name="representation">The external representation.</param>
47+
/// <param name="units">The TimeOnlyUnits.</param>
48+
public BsonTimeOnlyOptionsAttribute(BsonType representation, TimeOnlyUnits units)
49+
{
50+
_representation = representation;
51+
_units = units;
52+
}
53+
54+
// public properties
55+
/// <summary>
56+
/// Gets the external representation.
57+
/// </summary>
58+
public BsonType Representation => _representation;
59+
60+
/// <summary>
61+
/// Gets or sets the TimeOnlyUnits.
62+
/// </summary>
63+
public TimeOnlyUnits Units => _units;
64+
65+
/// <summary>
66+
/// Reconfigures the specified serializer by applying this attribute to it.
67+
/// </summary>
68+
/// <param name="serializer">The serializer.</param>
69+
/// <returns>A reconfigured serializer.</returns>
70+
protected override IBsonSerializer Apply(IBsonSerializer serializer)
71+
{
72+
if (serializer is TimeOnlySerializer timeOnlySerializer)
73+
{
74+
return timeOnlySerializer.WithRepresentation(_representation, _units);
75+
}
76+
77+
return base.Apply(serializer);
78+
}
79+
}
80+
#endif
81+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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+
namespace MongoDB.Bson.Serialization.Options
17+
{
18+
/// <summary>
19+
/// Represents the units a TimeOnly is serialized in.
20+
/// </summary>
21+
public enum TimeOnlyUnits
22+
{
23+
/// <summary>
24+
/// Use ticks as the units.
25+
/// </summary>
26+
Ticks = 0,
27+
/// <summary>
28+
/// Use hours as the units.
29+
/// </summary>
30+
Hours,
31+
/// <summary>
32+
/// Use minutes as the units.
33+
/// </summary>
34+
Minutes,
35+
/// <summary>
36+
/// Use seconds as the units.
37+
/// </summary>
38+
Seconds,
39+
/// <summary>
40+
/// Use milliseconds as the units.
41+
/// </summary>
42+
Milliseconds,
43+
/// <summary>
44+
/// Use microseconds as the units.
45+
/// </summary>
46+
Microseconds,
47+
/// <summary>
48+
/// Use nanoseconds as the units.
49+
/// </summary>
50+
Nanoseconds
51+
}
52+
}

src/MongoDB.Bson/Serialization/PrimitiveSerializationProvider.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ static PrimitiveSerializationProvider()
6161
{ typeof(SByte), typeof(SByteSerializer) },
6262
{ typeof(Single), typeof(SingleSerializer) },
6363
{ typeof(String), typeof(StringSerializer) },
64+
#if NET6_0_OR_GREATER
65+
{ typeof(TimeOnly), typeof(TimeOnlySerializer) },
66+
#endif
6467
{ typeof(TimeSpan), typeof(TimeSpanSerializer) },
6568
{ typeof(Tuple<>), typeof(TupleSerializer<>) },
6669
{ typeof(Tuple<,>), typeof(TupleSerializer<,>) },
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
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.Serialization.Options;
18+
19+
namespace MongoDB.Bson.Serialization.Serializers
20+
{
21+
#if NET6_0_OR_GREATER
22+
/// <summary>
23+
/// Represents a serializer for TimeOnlys
24+
/// </summary>
25+
public sealed class TimeOnlySerializer: StructSerializerBase<TimeOnly>, IRepresentationConfigurable<TimeOnlySerializer>
26+
{
27+
// static
28+
private static readonly TimeOnlySerializer __instance = new ();
29+
30+
/// <summary>
31+
/// Gets the default TimeOnlySerializer
32+
/// </summary>
33+
public static TimeOnlySerializer Instance => __instance;
34+
35+
// private fields
36+
private readonly BsonType _representation;
37+
private readonly TimeOnlyUnits _units;
38+
39+
// constructors
40+
/// <summary>
41+
/// Initializes a new instance of the <see cref="TimeOnlySerializer"/> class.
42+
/// </summary>
43+
public TimeOnlySerializer()
44+
: this(BsonType.Int64, TimeOnlyUnits.Ticks)
45+
{
46+
}
47+
48+
/// <summary>
49+
/// Initializes a new instance of the <see cref="TimeOnlySerializer"/> class.
50+
/// </summary>
51+
/// <param name="representation">The representation.</param>
52+
public TimeOnlySerializer(BsonType representation)
53+
: this(representation, TimeOnlyUnits.Ticks)
54+
{
55+
}
56+
57+
/// <summary>
58+
/// Initializes a new instance of the <see cref="TimeOnlySerializer"/> class.
59+
/// </summary>
60+
/// <param name="representation">The representation.</param>
61+
/// <param name="units">The units.</param>
62+
public TimeOnlySerializer(BsonType representation, TimeOnlyUnits units)
63+
{
64+
switch (representation)
65+
{
66+
case BsonType.Double:
67+
case BsonType.Int32:
68+
case BsonType.Int64:
69+
case BsonType.String:
70+
break;
71+
72+
default:
73+
throw new ArgumentException($"{representation} is not a valid representation for a TimeOnlySerializer.");
74+
}
75+
76+
_representation = representation;
77+
_units = units;
78+
}
79+
80+
// public properties
81+
/// <inheritdoc />
82+
public BsonType Representation => _representation;
83+
84+
/// <summary>
85+
/// Gets the units.
86+
/// </summary>
87+
/// <value>
88+
/// The units.
89+
/// </value>
90+
public TimeOnlyUnits Units => _units;
91+
92+
// public methods
93+
/// <inheritdoc/>
94+
public override TimeOnly Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
95+
{
96+
var bsonReader = context.Reader;
97+
var bsonType = bsonReader.GetCurrentBsonType();
98+
99+
return bsonType switch
100+
{
101+
BsonType.String => TimeOnly.ParseExact(bsonReader.ReadString(), "o"),
102+
BsonType.Int64 => FromInt64(bsonReader.ReadInt64(), _units),
103+
BsonType.Int32 => FromInt32(bsonReader.ReadInt32(), _units),
104+
BsonType.Double => FromDouble(bsonReader.ReadDouble(), _units),
105+
_ => throw CreateCannotDeserializeFromBsonTypeException(bsonType)
106+
};
107+
}
108+
109+
/// <inheritdoc/>
110+
public override bool Equals(object obj)
111+
{
112+
if (object.ReferenceEquals(obj, null)) { return false; }
113+
if (object.ReferenceEquals(this, obj)) { return true; }
114+
115+
return
116+
base.Equals(obj) &&
117+
obj is TimeOnlySerializer other &&
118+
_representation.Equals(other._representation) &&
119+
_units.Equals(other._units);
120+
}
121+
122+
/// <inheritdoc/>
123+
public override int GetHashCode() => 0;
124+
125+
/// <inheritdoc />
126+
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TimeOnly value)
127+
{
128+
var bsonWriter = context.Writer;
129+
130+
switch (_representation)
131+
{
132+
case BsonType.Double:
133+
bsonWriter.WriteDouble(ToDouble(value, _units));
134+
break;
135+
136+
case BsonType.Int32:
137+
bsonWriter.WriteInt32(ToInt32(value, _units));
138+
break;
139+
140+
case BsonType.Int64:
141+
bsonWriter.WriteInt64(ToInt64(value, _units));
142+
break;
143+
144+
case BsonType.String:
145+
bsonWriter.WriteString(value.ToString("o"));
146+
break;
147+
148+
default:
149+
throw new BsonSerializationException($"'{_representation}' is not a valid TimeOnly representation.");
150+
}
151+
}
152+
153+
/// <inheritdoc />
154+
public TimeOnlySerializer WithRepresentation(BsonType representation)
155+
{
156+
return representation == _representation ? this : new TimeOnlySerializer(representation);
157+
}
158+
159+
/// <summary>
160+
/// Returns a serializer that has been reconfigured with the specified representation and units.
161+
/// </summary>
162+
/// <param name="representation">The representation.</param>
163+
/// <param name="units">The units.</param>
164+
/// <returns>
165+
/// The reconfigured serializer.
166+
/// </returns>
167+
public TimeOnlySerializer WithRepresentation(BsonType representation, TimeOnlyUnits units)
168+
{
169+
if (representation == _representation && units == _units)
170+
{
171+
return this;
172+
}
173+
174+
return new TimeOnlySerializer(representation, units);
175+
}
176+
177+
// private methods
178+
private TimeOnly FromDouble(double value, TimeOnlyUnits units)
179+
{
180+
return units is TimeOnlyUnits.Nanoseconds
181+
? new TimeOnly((long)(value / 100.0))
182+
: new TimeOnly((long)(value * TicksPerUnit(units)));
183+
}
184+
185+
private TimeOnly FromInt32(int value, TimeOnlyUnits units)
186+
{
187+
return units is TimeOnlyUnits.Nanoseconds
188+
? new TimeOnly(value / 100)
189+
: new TimeOnly(value * TicksPerUnit(units));
190+
}
191+
192+
private TimeOnly FromInt64(long value, TimeOnlyUnits units)
193+
{
194+
return units is TimeOnlyUnits.Nanoseconds
195+
? new TimeOnly(value / 100)
196+
: new TimeOnly(value * TicksPerUnit(units));
197+
}
198+
199+
private long TicksPerUnit(TimeOnlyUnits units)
200+
{
201+
return units switch
202+
{
203+
TimeOnlyUnits.Hours => TimeSpan.TicksPerHour,
204+
TimeOnlyUnits.Minutes => TimeSpan.TicksPerMinute,
205+
TimeOnlyUnits.Seconds => TimeSpan.TicksPerSecond,
206+
TimeOnlyUnits.Milliseconds => TimeSpan.TicksPerMillisecond,
207+
TimeOnlyUnits.Microseconds => TimeSpan.TicksPerMillisecond / 1000,
208+
TimeOnlyUnits.Ticks => 1,
209+
_ => throw new ArgumentException($"Invalid TimeOnlyUnits value: {units}.")
210+
};
211+
}
212+
213+
private double ToDouble(TimeOnly timeOnly, TimeOnlyUnits units)
214+
{
215+
return units is TimeOnlyUnits.Nanoseconds
216+
? timeOnly.Ticks * 100
217+
: timeOnly.Ticks / (double)TicksPerUnit(units);
218+
}
219+
220+
private int ToInt32(TimeOnly timeOnly, TimeOnlyUnits units)
221+
{
222+
return units is TimeOnlyUnits.Nanoseconds
223+
? (int)(timeOnly.Ticks * 100)
224+
: (int)(timeOnly.Ticks / TicksPerUnit(units));
225+
}
226+
227+
private long ToInt64(TimeOnly timeOnly, TimeOnlyUnits units)
228+
{
229+
return units is TimeOnlyUnits.Nanoseconds
230+
? timeOnly.Ticks * 100
231+
: timeOnly.Ticks / TicksPerUnit(units);
232+
}
233+
234+
// explicit interface implementations
235+
IBsonSerializer IRepresentationConfigurable.WithRepresentation(BsonType representation)
236+
{
237+
return WithRepresentation(representation);
238+
}
239+
}
240+
#endif
241+
}

0 commit comments

Comments
 (0)