Skip to content

Commit 68939e2

Browse files
author
rstam
committed
Implemented CSHARP-357. BsonDateTime is now a faithful representation of a BSON DateTime value. The DateTime field has been removed and only the millisecondsSinceEpoch field remains.
1 parent b8b9d21 commit 68939e2

File tree

9 files changed

+259
-59
lines changed

9 files changed

+259
-59
lines changed

Bson/BsonUtils.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,15 @@ public static byte[] ParseHexString(string s)
5353
/// <returns>A DateTime.</returns>
5454
public static DateTime ToDateTimeFromMillisecondsSinceEpoch(long millisecondsSinceEpoch)
5555
{
56+
if (millisecondsSinceEpoch < BsonConstants.DateTimeMinValueMillisecondsSinceEpoch ||
57+
millisecondsSinceEpoch > BsonConstants.DateTimeMaxValueMillisecondsSinceEpoch)
58+
{
59+
var message = string.Format(
60+
"The value {0} for the BsonDateTime MillisecondsSinceEpoch is outside the range that can be converted to a .NET DateTime.",
61+
millisecondsSinceEpoch);
62+
throw new ArgumentOutOfRangeException("millisecondsSinceEpoch", message);
63+
}
64+
5665
// MaxValue has to be handled specially to avoid rounding errors
5766
if (millisecondsSinceEpoch == BsonConstants.DateTimeMaxValueMillisecondsSinceEpoch)
5867
{
@@ -116,7 +125,14 @@ public static DateTime ToLocalTime(DateTime dateTime, DateTimeKind kind)
116125
public static long ToMillisecondsSinceEpoch(DateTime dateTime)
117126
{
118127
var utcDateTime = ToUniversalTime(dateTime);
119-
return (utcDateTime - BsonConstants.UnixEpoch).Ticks / 10000;
128+
if (utcDateTime == DateTime.MaxValue)
129+
{
130+
return BsonConstants.DateTimeMaxValueMillisecondsSinceEpoch;
131+
}
132+
else
133+
{
134+
return (utcDateTime - BsonConstants.UnixEpoch).Ticks / 10000;
135+
}
120136
}
121137

122138
/// <summary>

Bson/ObjectModel/BsonDateTime.cs

Lines changed: 24 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ public class BsonDateTime : BsonValue, IComparable<BsonDateTime>, IEquatable<Bso
2929
{
3030
// private fields
3131
private long _millisecondsSinceEpoch;
32-
private DateTime _value; // only valid if millisecondsSinceEpoch is between MinValue and MaxValue for DateTime
3332

3433
// constructors
3534
/// <summary>
@@ -40,7 +39,6 @@ public BsonDateTime(DateTime value)
4039
: base(BsonType.DateTime)
4140
{
4241
_millisecondsSinceEpoch = BsonUtils.ToMillisecondsSinceEpoch(value);
43-
_value = value;
4442
}
4543

4644
/// <summary>
@@ -51,11 +49,6 @@ public BsonDateTime(long millisecondsSinceEpoch)
5149
: base(BsonType.DateTime)
5250
{
5351
_millisecondsSinceEpoch = millisecondsSinceEpoch;
54-
if (millisecondsSinceEpoch >= BsonConstants.DateTimeMinValueMillisecondsSinceEpoch &&
55-
millisecondsSinceEpoch <= BsonConstants.DateTimeMaxValueMillisecondsSinceEpoch)
56-
{
57-
_value = BsonUtils.ToDateTimeFromMillisecondsSinceEpoch(millisecondsSinceEpoch);
58-
}
5952
}
6053

6154
// public properties
@@ -95,15 +88,7 @@ public DateTime Value
9588
{
9689
get
9790
{
98-
if (_millisecondsSinceEpoch < BsonConstants.DateTimeMinValueMillisecondsSinceEpoch)
99-
{
100-
throw new OverflowException("MillisecondsSinceEpoch value is before DateTime.MinValue.");
101-
}
102-
if (_millisecondsSinceEpoch > BsonConstants.DateTimeMaxValueMillisecondsSinceEpoch)
103-
{
104-
throw new OverflowException("MillisecondsSinceEpoch value is after DateTime.MaxValue.");
105-
}
106-
return _value;
91+
return BsonUtils.ToDateTimeFromMillisecondsSinceEpoch(_millisecondsSinceEpoch);
10792
}
10893
}
10994

@@ -188,14 +173,7 @@ public static BsonDateTime Create(long millisecondsSinceEpoch)
188173
public int CompareTo(BsonDateTime other)
189174
{
190175
if (other == null) { return 1; }
191-
if (IsValidDateTime && other.IsValidDateTime)
192-
{
193-
return _value.CompareTo(other._value);
194-
}
195-
else
196-
{
197-
return _millisecondsSinceEpoch.CompareTo(other._millisecondsSinceEpoch);
198-
}
176+
return _millisecondsSinceEpoch.CompareTo(other._millisecondsSinceEpoch);
199177
}
200178

201179
/// <summary>
@@ -209,14 +187,7 @@ public override int CompareTo(BsonValue other)
209187
var otherDateTime = other as BsonDateTime;
210188
if (otherDateTime != null)
211189
{
212-
if (IsValidDateTime && otherDateTime.IsValidDateTime)
213-
{
214-
return _value.CompareTo(otherDateTime._value);
215-
}
216-
else
217-
{
218-
return _millisecondsSinceEpoch.CompareTo(otherDateTime._millisecondsSinceEpoch);
219-
}
190+
return _millisecondsSinceEpoch.CompareTo(otherDateTime._millisecondsSinceEpoch);
220191
}
221192
var otherTimestamp = other as BsonTimestamp;
222193
if (otherTimestamp != null)
@@ -234,7 +205,7 @@ public override int CompareTo(BsonValue other)
234205
public bool Equals(BsonDateTime rhs)
235206
{
236207
if (object.ReferenceEquals(rhs, null) || GetType() != rhs.GetType()) { return false; }
237-
return _millisecondsSinceEpoch == rhs._millisecondsSinceEpoch && _value == rhs._value;
208+
return _millisecondsSinceEpoch == rhs._millisecondsSinceEpoch;
238209
}
239210

240211
/// <summary>
@@ -257,10 +228,28 @@ public override int GetHashCode()
257228
int hash = 17;
258229
hash = 37 * hash + _bsonType.GetHashCode();
259230
hash = 37 * hash + _millisecondsSinceEpoch.GetHashCode();
260-
hash = 37 * hash + _value.GetHashCode();
261231
return hash;
262232
}
263233

234+
/// <summary>
235+
/// Converts the BsonDateTime value to a .NET DateTime value in the local timezone.
236+
/// </summary>
237+
/// <returns>A DateTime in the local timezone.</returns>
238+
public DateTime ToLocalTime()
239+
{
240+
var utcDateTime = BsonUtils.ToDateTimeFromMillisecondsSinceEpoch(_millisecondsSinceEpoch);
241+
return BsonUtils.ToLocalTime(utcDateTime, DateTimeKind.Local);
242+
}
243+
244+
/// <summary>
245+
/// Converts the BsonDateTime value to a .NET DateTime value in UTC.
246+
/// </summary>
247+
/// <returns>A DateTime in UTC.</returns>
248+
public DateTime ToUniversalTime()
249+
{
250+
return BsonUtils.ToDateTimeFromMillisecondsSinceEpoch(_millisecondsSinceEpoch);
251+
}
252+
264253
/// <summary>
265254
/// Returns a string representation of the value.
266255
/// </summary>
@@ -269,7 +258,7 @@ public override string ToString()
269258
{
270259
if (IsValidDateTime)
271260
{
272-
return _value.ToString("yyyy-MM-ddTHH:mm:ss.FFFFFFFK");
261+
return BsonUtils.ToDateTimeFromMillisecondsSinceEpoch(_millisecondsSinceEpoch).ToString("yyyy-MM-ddTHH:mm:ss.FFFFFFFK");
273262
}
274263
else
275264
{

Bson/ObjectModel/BsonValue.cs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,11 +200,11 @@ public byte[] AsByteArray
200200
}
201201

202202
/// <summary>
203-
/// Casts the BsonValue to a DateTime (throws an InvalidCastException if the cast is not valid).
203+
/// Casts the BsonValue to a DateTime in UTC (throws an InvalidCastException if the cast is not valid).
204204
/// </summary>
205205
public DateTime AsDateTime
206206
{
207-
get { return ((BsonDateTime)this).Value; }
207+
get { return AsUniversalTime; }
208208
}
209209

210210
/// <summary>
@@ -231,6 +231,14 @@ public int AsInt32
231231
get { return ((BsonInt32)this).Value; }
232232
}
233233

234+
/// <summary>
235+
/// Casts the BsonValue to a DateTime in the local timezone (throws an InvalidCastException if the cast is not valid).
236+
/// </summary>
237+
public DateTime AsLocalTime
238+
{
239+
get { return ((BsonDateTime)this).ToLocalTime(); }
240+
}
241+
234242
/// <summary>
235243
/// Casts the BsonValue to a Int64 (throws an InvalidCastException if the cast is not valid).
236244
/// </summary>
@@ -319,6 +327,14 @@ public string AsString
319327
get { return ((BsonString)this).Value; }
320328
}
321329

330+
/// <summary>
331+
/// Casts the BsonValue to a DateTime in UTC (throws an InvalidCastException if the cast is not valid).
332+
/// </summary>
333+
public DateTime AsUniversalTime
334+
{
335+
get { return ((BsonDateTime)this).ToUniversalTime(); }
336+
}
337+
322338
/// <summary>
323339
/// Gets the BsonType of this BsonValue.
324340
/// </summary>

BsonUnitTests/BsonUnitTests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@
139139
<Compile Include="Jira\CSharp310Tests.cs" />
140140
<Compile Include="Jira\CSharp350Tests.cs" />
141141
<Compile Include="Jira\CSharp351Tests.cs" />
142+
<Compile Include="Jira\CSharp357Tests.cs" />
142143
<Compile Include="Jira\CSharp369Tests.cs" />
143144
<Compile Include="Jira\CSharp70Tests.cs" />
144145
<Compile Include="Jira\CSharp71Tests.cs" />

BsonUnitTests/BsonUtilsTests.cs

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,21 @@
1414
*/
1515

1616
using System;
17+
using NUnit.Framework;
1718

1819
using MongoDB.Bson;
1920

20-
using NUnit.Framework;
21-
2221
namespace MongoDB.BsonUnitTests
2322
{
2423
[TestFixture]
2524
public class BsonUtilsTests
2625
{
27-
2826
[Test]
2927
public void TestMaxToDateTimeConversion()
3028
{
3129
var actual = BsonUtils.ToDateTimeFromMillisecondsSinceEpoch(
3230
BsonConstants.DateTimeMaxValueMillisecondsSinceEpoch);
31+
Assert.AreEqual(DateTimeKind.Utc, actual.Kind);
3332
Assert.AreEqual(DateTime.MaxValue, actual);
3433
}
3534

@@ -38,13 +37,15 @@ public void TestMinToDateTimeConversion()
3837
{
3938
var actual = BsonUtils.ToDateTimeFromMillisecondsSinceEpoch(
4039
BsonConstants.DateTimeMinValueMillisecondsSinceEpoch);
40+
Assert.AreEqual(DateTimeKind.Utc, actual.Kind);
4141
Assert.AreEqual(DateTime.MinValue, actual);
4242
}
4343

4444
[Test]
4545
public void TestZeroToDateTimeConversion()
4646
{
4747
var actual = BsonUtils.ToDateTimeFromMillisecondsSinceEpoch(0);
48+
Assert.AreEqual(DateTimeKind.Utc, actual.Kind);
4849
Assert.AreEqual(BsonConstants.UnixEpoch, actual);
4950
}
5051

@@ -53,8 +54,7 @@ public void TestZeroToDateTimeConversion()
5354
public void TestGreaterThanMaxToDateTimeConversion()
5455
{
5556
var actual = BsonUtils.ToDateTimeFromMillisecondsSinceEpoch(
56-
BsonConstants.DateTimeMaxValueMillisecondsSinceEpoch+1);
57-
Assert.AreEqual(BsonConstants.UnixEpoch, actual);
57+
BsonConstants.DateTimeMaxValueMillisecondsSinceEpoch + 1);
5858
}
5959

6060
[Test]
@@ -63,24 +63,20 @@ public void TestLessThanMinToDateTimeConversion()
6363
{
6464
var actual = BsonUtils.ToDateTimeFromMillisecondsSinceEpoch(
6565
BsonConstants.DateTimeMinValueMillisecondsSinceEpoch - 1);
66-
Assert.AreEqual(BsonConstants.UnixEpoch, actual);
6766
}
6867

6968
[Test]
7069
public void TestMaxToMillisConversion()
7170
{
7271
var actual = BsonUtils.ToMillisecondsSinceEpoch(DateTime.MaxValue);
73-
Assert.AreEqual(BsonConstants.DateTimeMaxValueMillisecondsSinceEpoch,
74-
actual);
72+
Assert.AreEqual(BsonConstants.DateTimeMaxValueMillisecondsSinceEpoch, actual);
7573
}
7674

7775
[Test]
7876
public void TestMinToMillisConversion()
7977
{
8078
var actual = BsonUtils.ToMillisecondsSinceEpoch(DateTime.MinValue);
81-
Assert.AreEqual(BsonConstants.DateTimeMinValueMillisecondsSinceEpoch,
82-
actual);
79+
Assert.AreEqual(BsonConstants.DateTimeMinValueMillisecondsSinceEpoch, actual);
8380
}
84-
8581
}
8682
}

0 commit comments

Comments
 (0)