Skip to content

Commit 83e34aa

Browse files
author
rstam
committed
Implemented CSHARP-377. Added new constructor that lets you specify the timestamp as a DateTime. Added new overloads of GenerateNewId that let you provide the timestamp (either as an int or a DateTime) to support generating backdated ObjectIds.
1 parent 6339e9c commit 83e34aa

File tree

4 files changed

+169
-6
lines changed

4 files changed

+169
-6
lines changed

Bson/ObjectModel/BsonObjectId.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,19 @@ public BsonObjectId(byte[] value)
5656
_value = new ObjectId(value);
5757
}
5858

59+
/// <summary>
60+
/// Initializes a new instance of the BsonObjectId class.
61+
/// </summary>
62+
/// <param name="timestamp">The timestamp (expressed as a DateTime).</param>
63+
/// <param name="machine">The machine hash.</param>
64+
/// <param name="pid">The PID.</param>
65+
/// <param name="increment">The increment.</param>
66+
public BsonObjectId(DateTime timestamp, int machine, short pid, int increment)
67+
: base(BsonType.ObjectId)
68+
{
69+
_value = new ObjectId(timestamp, machine, pid, increment);
70+
}
71+
5972
/// <summary>
6073
/// Initializes a new instance of the BsonObjectId class.
6174
/// </summary>
@@ -263,6 +276,26 @@ public static BsonObjectId GenerateNewId()
263276
return new BsonObjectId(ObjectId.GenerateNewId());
264277
}
265278

279+
/// <summary>
280+
/// Generates a new BsonObjectId with a unique value (with the timestamp component based on a given DateTime).
281+
/// </summary>
282+
/// <param name="timestamp">The timestamp component (expressed as a DateTime).</param>
283+
/// <returns>A BsonObjectId.</returns>
284+
public static BsonObjectId GenerateNewId(DateTime timestamp)
285+
{
286+
return new BsonObjectId(ObjectId.GenerateNewId(timestamp));
287+
}
288+
289+
/// <summary>
290+
/// Generates a new BsonObjectId with a unique value (with the given timestamp).
291+
/// </summary>
292+
/// <param name="timestamp">The timestamp component.</param>
293+
/// <returns>A BsonObjectId.</returns>
294+
public static BsonObjectId GenerateNewId(int timestamp)
295+
{
296+
return new BsonObjectId(ObjectId.GenerateNewId(timestamp));
297+
}
298+
266299
/// <summary>
267300
/// Parses a string and creates a new BsonObjectId.
268301
/// </summary>

Bson/ObjectModel/ObjectId.cs

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,18 @@ public ObjectId(byte[] bytes)
6262
Unpack(bytes, out _timestamp, out _machine, out _pid, out _increment);
6363
}
6464

65+
/// <summary>
66+
/// Initializes a new instance of the ObjectId class.
67+
/// </summary>
68+
/// <param name="timestamp">The timestamp (expressed as a DateTime).</param>
69+
/// <param name="machine">The machine hash.</param>
70+
/// <param name="pid">The PID.</param>
71+
/// <param name="increment">The increment.</param>
72+
public ObjectId(DateTime timestamp, int machine, short pid, int increment)
73+
: this(GetTimestampFromDateTime(timestamp), machine, pid, increment)
74+
{
75+
}
76+
6577
/// <summary>
6678
/// Initializes a new instance of the ObjectId class.
6779
/// </summary>
@@ -207,10 +219,29 @@ public DateTime CreationTime
207219
/// <summary>
208220
/// Generates a new ObjectId with a unique value.
209221
/// </summary>
210-
/// <returns>A ObjectId.</returns>
222+
/// <returns>An ObjectId.</returns>
211223
public static ObjectId GenerateNewId()
212224
{
213-
int timestamp = GetCurrentTimestamp();
225+
return GenerateNewId(GetTimestampFromDateTime(DateTime.UtcNow));
226+
}
227+
228+
/// <summary>
229+
/// Generates a new ObjectId with a unique value (with the timestamp component based on a given DateTime).
230+
/// </summary>
231+
/// <param name="timestamp">The timestamp component (expressed as a DateTime).</param>
232+
/// <returns>An ObjectId.</returns>
233+
public static ObjectId GenerateNewId(DateTime timestamp)
234+
{
235+
return GenerateNewId(GetTimestampFromDateTime(timestamp));
236+
}
237+
238+
/// <summary>
239+
/// Generates a new ObjectId with a unique value (with the given timestamp).
240+
/// </summary>
241+
/// <param name="timestamp">The timestamp component.</param>
242+
/// <returns>An ObjectId.</returns>
243+
public static ObjectId GenerateNewId(int timestamp)
244+
{
214245
int increment = Interlocked.Increment(ref __staticIncrement) & 0x00ffffff; // only use low order 3 bytes
215246
return new ObjectId(timestamp, __staticMachine, __staticPid, increment);
216247
}
@@ -311,10 +342,9 @@ private static int GetMachineHash()
311342
return (hash[0] << 16) + (hash[1] << 8) + hash[2]; // use first 3 bytes of hash
312343
}
313344

314-
private static int GetCurrentTimestamp()
345+
private static int GetTimestampFromDateTime(DateTime timestamp)
315346
{
316-
DateTime now = DateTime.UtcNow;
317-
return (int)Math.Floor((now - BsonConstants.UnixEpoch).TotalSeconds);
347+
return (int)Math.Floor((BsonUtils.ToUniversalTime(timestamp) - BsonConstants.UnixEpoch).TotalSeconds);
318348
}
319349

320350
// public methods

BsonUnitTests/ObjectModel/BsonObjectIdTests.cs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,24 @@ public void TestIntIntShortIntConstructor()
6060
Assert.IsTrue(bytes.SequenceEqual(objectId.ToByteArray()));
6161
}
6262

63+
[Test]
64+
public void TestDateTimeConstructor()
65+
{
66+
byte[] bytes = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
67+
var timestamp = BsonConstants.UnixEpoch.AddSeconds(0x01020304);
68+
var objectId = new BsonObjectId(timestamp, 0x050607, 0x0809, 0x0a0b0c);
69+
Assert.AreEqual(0x01020304, objectId.Timestamp);
70+
Assert.AreEqual(0x050607, objectId.Machine);
71+
Assert.AreEqual(0x0809, objectId.Pid);
72+
Assert.AreEqual(0x0a0b0c, objectId.Increment);
73+
Assert.AreEqual(0x050607, objectId.Machine);
74+
Assert.AreEqual(0x0809, objectId.Pid);
75+
Assert.AreEqual(0x0a0b0c, objectId.Increment);
76+
Assert.AreEqual(BsonConstants.UnixEpoch.AddSeconds(0x01020304), objectId.CreationTime);
77+
Assert.AreEqual("0102030405060708090a0b0c", objectId.ToString());
78+
Assert.IsTrue(bytes.SequenceEqual(objectId.ToByteArray()));
79+
}
80+
6381
[Test]
6482
public void TestStringConstructor()
6583
{
@@ -77,6 +95,38 @@ public void TestStringConstructor()
7795
Assert.IsTrue(bytes.SequenceEqual(objectId.ToByteArray()));
7896
}
7997

98+
[Test]
99+
public void TestGenerateNewId()
100+
{
101+
// compare against two timestamps in case seconds since epoch changes in middle of test
102+
var timestamp1 = (int)Math.Floor((DateTime.UtcNow - BsonConstants.UnixEpoch).TotalSeconds);
103+
var objectId = BsonObjectId.GenerateNewId();
104+
var timestamp2 = (int)Math.Floor((DateTime.UtcNow - BsonConstants.UnixEpoch).TotalSeconds);
105+
Assert.IsTrue(objectId.Timestamp == timestamp1 || objectId.Timestamp == timestamp2);
106+
Assert.IsTrue(objectId.Machine != 0);
107+
Assert.IsTrue(objectId.Pid != 0);
108+
}
109+
110+
[Test]
111+
public void TestGenerateNewIdWithDateTime()
112+
{
113+
var timestamp = new DateTime(2011, 1, 2, 3, 4, 5, DateTimeKind.Utc);
114+
var objectId = BsonObjectId.GenerateNewId(timestamp);
115+
Assert.IsTrue(objectId.CreationTime == timestamp);
116+
Assert.IsTrue(objectId.Machine != 0);
117+
Assert.IsTrue(objectId.Pid != 0);
118+
}
119+
120+
[Test]
121+
public void TestGenerateNewIdWithTimestamp()
122+
{
123+
var timestamp = 0x01020304;
124+
var objectId = BsonObjectId.GenerateNewId(timestamp);
125+
Assert.IsTrue(objectId.Timestamp == timestamp);
126+
Assert.IsTrue(objectId.Machine != 0);
127+
Assert.IsTrue(objectId.Pid != 0);
128+
}
129+
80130
[Test]
81131
public void TestIComparable()
82132
{

BsonUnitTests/ObjectModel/ObjectIdTests.cs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public void TestByteArrayConstructor()
4444
}
4545

4646
[Test]
47-
public void TestIntLongConstructor()
47+
public void TestIntIntShortIntConstructor()
4848
{
4949
byte[] bytes = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
5050
var objectId = new ObjectId(0x01020304, 0x050607, 0x0809, 0x0a0b0c);
@@ -60,6 +60,24 @@ public void TestIntLongConstructor()
6060
Assert.IsTrue(bytes.SequenceEqual(objectId.ToByteArray()));
6161
}
6262

63+
[Test]
64+
public void TestDateTimeConstructor()
65+
{
66+
byte[] bytes = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
67+
var timestamp = BsonConstants.UnixEpoch.AddSeconds(0x01020304);
68+
var objectId = new ObjectId(timestamp, 0x050607, 0x0809, 0x0a0b0c);
69+
Assert.AreEqual(0x01020304, objectId.Timestamp);
70+
Assert.AreEqual(0x050607, objectId.Machine);
71+
Assert.AreEqual(0x0809, objectId.Pid);
72+
Assert.AreEqual(0x0a0b0c, objectId.Increment);
73+
Assert.AreEqual(0x050607, objectId.Machine);
74+
Assert.AreEqual(0x0809, objectId.Pid);
75+
Assert.AreEqual(0x0a0b0c, objectId.Increment);
76+
Assert.AreEqual(BsonConstants.UnixEpoch.AddSeconds(0x01020304), objectId.CreationTime);
77+
Assert.AreEqual("0102030405060708090a0b0c", objectId.ToString());
78+
Assert.IsTrue(bytes.SequenceEqual(objectId.ToByteArray()));
79+
}
80+
6381
[Test]
6482
public void TestStringConstructor()
6583
{
@@ -77,6 +95,38 @@ public void TestStringConstructor()
7795
Assert.IsTrue(bytes.SequenceEqual(objectId.ToByteArray()));
7896
}
7997

98+
[Test]
99+
public void TestGenerateNewId()
100+
{
101+
// compare against two timestamps in case seconds since epoch changes in middle of test
102+
var timestamp1 = (int)Math.Floor((DateTime.UtcNow - BsonConstants.UnixEpoch).TotalSeconds);
103+
var objectId = ObjectId.GenerateNewId();
104+
var timestamp2 = (int)Math.Floor((DateTime.UtcNow - BsonConstants.UnixEpoch).TotalSeconds);
105+
Assert.IsTrue(objectId.Timestamp == timestamp1 || objectId.Timestamp == timestamp2);
106+
Assert.IsTrue(objectId.Machine != 0);
107+
Assert.IsTrue(objectId.Pid != 0);
108+
}
109+
110+
[Test]
111+
public void TestGenerateNewIdWithDateTime()
112+
{
113+
var timestamp = new DateTime(2011, 1, 2, 3, 4, 5, DateTimeKind.Utc);
114+
var objectId = ObjectId.GenerateNewId(timestamp);
115+
Assert.IsTrue(objectId.CreationTime == timestamp);
116+
Assert.IsTrue(objectId.Machine != 0);
117+
Assert.IsTrue(objectId.Pid != 0);
118+
}
119+
120+
[Test]
121+
public void TestGenerateNewIdWithTimestamp()
122+
{
123+
var timestamp = 0x01020304;
124+
var objectId = ObjectId.GenerateNewId(timestamp);
125+
Assert.IsTrue(objectId.Timestamp == timestamp);
126+
Assert.IsTrue(objectId.Machine != 0);
127+
Assert.IsTrue(objectId.Pid != 0);
128+
}
129+
80130
[Test]
81131
public void TestIComparable()
82132
{

0 commit comments

Comments
 (0)