Skip to content

Commit 01b74f2

Browse files
authored
Add DefaultIsMonotonic property to Ulid class (#4)
Introduce a new static property `DefaultIsMonotonic` to the `Ulid` class, allowing users to set the default behavior for generating ULIDs. Update ULID creation methods to accept a nullable `isMonotonic` parameter for enhanced flexibility. Revise test cases in `UlidComparableTests` and `UlidNewTests` to validate the new behavior. Update documentation in `README.md` and `PACKAGE.md` to reflect these changes, ensuring accurate usage information. Add comments to clarify the implications of the new property and method parameters for improved code readability.
1 parent f2fa071 commit 01b74f2

File tree

5 files changed

+142
-60
lines changed

5 files changed

+142
-60
lines changed

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,13 @@ The `Ulid` implementation provides the following properties and methods:
9797

9898
### Creation
9999

100-
- `Ulid.New(bool isMonotonic = true)`\
101-
Generates a new ULID. If `isMonotonic` is `true`, ensures monotonicity during timestamp collisions.
102-
- `Ulid.New(DateTimeOffset dateTimeOffset, bool isMonotonic = true)`\
100+
- `Ulid.DefaultIsMonotonic = true`\
101+
Sets the default behavior for generating ULIDs unless overridden during generation. If `true` (default), ensures monotonicity during timestamp collisions.
102+
- `Ulid.New(bool? isMonotonic = null)`\
103+
Generates a new ULID. If `isMonotonic` is `null` (default), uses `Ulid.DefaultIsMonotonic` for monotonicity setting.
104+
- `Ulid.New(DateTimeOffset dateTimeOffset, bool? isMonotonic = null)`\
103105
Generates a new ULID using the specified `DateTimeOffset`.
104-
- `Ulid.New(long timestamp, bool isMonotonic = true)`\
106+
- `Ulid.New(long timestamp, bool? isMonotonic = null)`\
105107
Generates a new ULID using the specified Unix timestamp in milliseconds (`long`).
106108
- `Ulid.New(DateTimeOffset dateTimeOffset, Span<byte> random)`\
107109
Generates a new ULID using the specified `DateTimeOffset` and a pre-existing random byte array.

src/ByteAether.Ulid.Tests/Ulid.Comparable.Tests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ public void CompareTo_SameUlid_ShouldReturnZero()
2424
public void CompareTo_CompareToNewerUlid_ShouldReturnNegative()
2525
{
2626
// Arrange
27-
var ulid1 = Ulid.New();
28-
var ulid2 = Ulid.New();
27+
var ulid1 = Ulid.New(true);
28+
var ulid2 = Ulid.New(true);
2929

3030
// Act
3131
var comparisonResult = ulid1.CompareTo(ulid2);
@@ -42,8 +42,8 @@ public void CompareTo_CompareToNewerUlid_ShouldReturnNegative()
4242
public void CompareTo_CompareToOlderUlid_ShouldReturnPositive()
4343
{
4444
// Arrange
45-
var ulid1 = Ulid.New();
46-
var ulid2 = Ulid.New();
45+
var ulid1 = Ulid.New(true);
46+
var ulid2 = Ulid.New(true);
4747

4848
// Act
4949
var comparisonResult = ulid2.CompareTo(ulid1);

src/ByteAether.Ulid.Tests/Ulid.New.Tests.cs

Lines changed: 75 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -37,57 +37,75 @@ public void New_WithDateTime_ShouldGenerateUniqueUlids(bool isMonotonic)
3737
{
3838
// Arrange
3939
var dateTimeOffset = DateTimeOffset.UtcNow;
40+
var timestamp = dateTimeOffset.ToUnixTimeMilliseconds();
4041

4142
// Act
4243
var ulid1 = Ulid.New(dateTimeOffset, isMonotonic);
4344
var ulid2 = Ulid.New(dateTimeOffset, isMonotonic);
4445

4546
// Assert
46-
Assert.Equal(dateTimeOffset.ToUnixTimeMilliseconds(), ulid1.Time.ToUnixTimeMilliseconds());
47-
Assert.Equal(ulid1.Time, ulid2.Time);
48-
Assert.Equal(ulid1.TimeBytes.ToArray(), ulid2.TimeBytes.ToArray());
49-
Assert.NotEqual(ulid1.Random.ToArray(), ulid2.Random.ToArray());
5047
Assert.NotEqual(ulid1, ulid2);
48+
49+
Assert.True(ulid1.Time.ToUnixTimeMilliseconds() <= ulid2.Time.ToUnixTimeMilliseconds());
50+
Assert.True(timestamp <= ulid1.Time.ToUnixTimeMilliseconds());
51+
Assert.True(timestamp <= ulid2.Time.ToUnixTimeMilliseconds());
52+
53+
if (isMonotonic)
54+
{
55+
Assert.True(MemoryExtensions.SequenceCompareTo(ulid1.AsByteSpan(), ulid2.AsByteSpan()) < 0);
56+
Assert.True(ulid1 < ulid2);
57+
}
5158
}
5259

53-
[Fact]
54-
public void New_WithDateTimeAndRandom_ShouldGenerateSameUlid()
60+
[Theory]
61+
[InlineData(true)]
62+
[InlineData(false)]
63+
public void New_WithTimestamp_ShouldGenerateUniqueUlids(bool isMonotonic)
5564
{
5665
// Arrange
5766
var dateTimeOffset = DateTimeOffset.UtcNow;
58-
var random = new byte[10];
67+
var timestamp = dateTimeOffset.ToUnixTimeMilliseconds();
5968

6069
// Act
61-
var ulid1 = Ulid.New(dateTimeOffset, random);
62-
var ulid2 = Ulid.New(dateTimeOffset, random);
70+
var ulid1 = Ulid.New(timestamp, isMonotonic);
71+
var ulid2 = Ulid.New(timestamp, isMonotonic);
6372

6473
// Assert
65-
Assert.Equal(dateTimeOffset.ToUnixTimeMilliseconds(), ulid1.Time.ToUnixTimeMilliseconds());
66-
Assert.Equal(ulid1.Time, ulid2.Time);
67-
Assert.Equal(ulid1.TimeBytes.ToArray(), ulid2.TimeBytes.ToArray());
68-
Assert.Equal(ulid1.Random.ToArray(), ulid2.Random.ToArray());
69-
Assert.Equal(ulid1, ulid2);
74+
Assert.NotEqual(ulid1, ulid2);
75+
76+
Assert.True(ulid1.Time.ToUnixTimeMilliseconds() <= ulid2.Time.ToUnixTimeMilliseconds());
77+
Assert.True(timestamp <= ulid1.Time.ToUnixTimeMilliseconds());
78+
Assert.True(timestamp <= ulid2.Time.ToUnixTimeMilliseconds());
79+
80+
if (isMonotonic)
81+
{
82+
Assert.True(MemoryExtensions.SequenceCompareTo(ulid1.AsByteSpan(), ulid2.AsByteSpan()) < 0);
83+
Assert.True(ulid1 < ulid2);
84+
}
7085
}
7186

72-
[Theory]
73-
[InlineData(true)]
74-
[InlineData(false)]
75-
public void New_WithTimestamp_ShouldGenerateUniqueUlids(bool isMonotonic)
87+
[Fact]
88+
public void New_WithDateTimeAndRandom_ShouldGenerateSameUlid()
7689
{
7790
// Arrange
7891
var dateTimeOffset = DateTimeOffset.UtcNow;
7992
var timestamp = dateTimeOffset.ToUnixTimeMilliseconds();
93+
var random = new byte[10];
8094

8195
// Act
82-
var ulid1 = Ulid.New(timestamp, isMonotonic);
83-
var ulid2 = Ulid.New(timestamp, isMonotonic);
96+
var ulid1 = Ulid.New(dateTimeOffset, random);
97+
var ulid2 = Ulid.New(dateTimeOffset, random);
8498

8599
// Assert
100+
Assert.Equal(ulid1, ulid2);
101+
86102
Assert.Equal(timestamp, ulid1.Time.ToUnixTimeMilliseconds());
103+
Assert.Equal(timestamp, ulid2.Time.ToUnixTimeMilliseconds());
104+
87105
Assert.Equal(ulid1.Time, ulid2.Time);
88106
Assert.Equal(ulid1.TimeBytes.ToArray(), ulid2.TimeBytes.ToArray());
89-
Assert.NotEqual(ulid1.Random.ToArray(), ulid2.Random.ToArray());
90-
Assert.NotEqual(ulid1, ulid2);
107+
108+
Assert.Equal(ulid1.Random.ToArray(), ulid2.Random.ToArray());
91109
}
92110

93111
[Fact]
@@ -103,10 +121,44 @@ public void New_WithTimestampAndRandom_ShouldGenerateSameUlid()
103121
var ulid2 = Ulid.New(timestamp, random);
104122

105123
// Assert
124+
Assert.Equal(ulid1, ulid2);
125+
106126
Assert.Equal(timestamp, ulid1.Time.ToUnixTimeMilliseconds());
127+
Assert.Equal(timestamp, ulid2.Time.ToUnixTimeMilliseconds());
128+
107129
Assert.Equal(ulid1.Time, ulid2.Time);
108130
Assert.Equal(ulid1.TimeBytes.ToArray(), ulid2.TimeBytes.ToArray());
131+
109132
Assert.Equal(ulid1.Random.ToArray(), ulid2.Random.ToArray());
110-
Assert.Equal(ulid1, ulid2);
133+
}
134+
135+
[Theory]
136+
[InlineData(false)]
137+
[InlineData(true)]
138+
[InlineData(null, true)]
139+
[InlineData(null, false)]
140+
public void New_WithTimestampAndMonotonicSet_ShouldGenerateUniqueUlids(bool? isMonotonic, bool defaultIsMonotonic = true)
141+
{
142+
// Arrange
143+
Ulid.DefaultIsMonotonic = defaultIsMonotonic;
144+
var dateTimeOffset = DateTimeOffset.UtcNow;
145+
var timestamp = dateTimeOffset.ToUnixTimeMilliseconds();
146+
147+
// Act
148+
var ulid1 = Ulid.New(timestamp, isMonotonic);
149+
var ulid2 = Ulid.New(timestamp, isMonotonic);
150+
151+
// Assert
152+
Assert.NotEqual(ulid1, ulid2);
153+
154+
Assert.True(ulid1.Time.ToUnixTimeMilliseconds() <= ulid2.Time.ToUnixTimeMilliseconds());
155+
Assert.True(timestamp <= ulid1.Time.ToUnixTimeMilliseconds());
156+
Assert.True(timestamp <= ulid2.Time.ToUnixTimeMilliseconds());
157+
158+
if (isMonotonic ?? defaultIsMonotonic)
159+
{
160+
Assert.True(MemoryExtensions.SequenceCompareTo(ulid1.AsByteSpan(), ulid2.AsByteSpan()) < 0);
161+
Assert.True(ulid1 < ulid2);
162+
}
111163
}
112164
}

src/ByteAether.Ulid/PACKAGE.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,13 @@ The `Ulid` implementation provides the following properties and methods:
7575

7676
### Creation
7777

78-
- `Ulid.New(bool isMonotonic = true)`\
79-
Generates a new ULID. If `isMonotonic` is `true`, ensures monotonicity during timestamp collisions.
80-
- `Ulid.New(DateTimeOffset dateTimeOffset, bool isMonotonic = true)`\
78+
- `Ulid.DefaultIsMonotonic = true`\
79+
Sets the default behavior for generating ULIDs unless overridden during generation. If `true` (default), ensures monotonicity during timestamp collisions.
80+
- `Ulid.New(bool? isMonotonic = null)`\
81+
Generates a new ULID. If `isMonotonic` is `null` (default), uses `Ulid.DefaultIsMonotonic` for monotonicity setting.
82+
- `Ulid.New(DateTimeOffset dateTimeOffset, bool? isMonotonic = null)`\
8183
Generates a new ULID using the specified `DateTimeOffset`.
82-
- `Ulid.New(long timestamp, bool isMonotonic = true)`\
84+
- `Ulid.New(long timestamp, bool? isMonotonic = null)`\
8385
Generates a new ULID using the specified Unix timestamp in milliseconds (`long`).
8486
- `Ulid.New(DateTimeOffset dateTimeOffset, Span<byte> random)`\
8587
Generates a new ULID using the specified `DateTimeOffset` and a pre-existing random byte array.

src/ByteAether.Ulid/Ulid.New.cs

Lines changed: 51 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,19 @@ namespace ByteAether.Ulid;
99

1010
public readonly partial struct Ulid
1111
{
12+
/// <summary>
13+
/// Whether <see cref="Ulid"/>s should be generated in a monotonic manner by default.<br />
14+
/// Initial value is set to <c>true</c>.<br/>
15+
/// <b>This setting applies globally without any scoping.</b>
16+
/// </summary>
17+
/// <remarks>
18+
/// When set to <c>true</c> (default), <see cref="Ulid"/>s generated without explicitly specifying monotonicity
19+
/// will ensure that they are monotonically increasing.<br />
20+
/// When set to <c>false</c>, <see cref="Ulid"/>s generated without explicitly specifying monotonicity will be
21+
/// generated with random <see cref="Random" /> value.
22+
/// </remarks>
23+
public static bool DefaultIsMonotonic { get; set; } = true;
24+
1225
private static readonly byte[] _lastUlid = new byte[_ulidSize];
1326
private static readonly RandomNumberGenerator _rng = RandomNumberGenerator.Create();
1427

@@ -19,56 +32,69 @@ public readonly partial struct Ulid
1932
#endif
2033

2134
/// <summary>
22-
/// Initializes a new instance of the <see cref="ByteAether.Ulid"/> struct using the specified byte array.
35+
/// Initializes a new instance of the <see cref="Ulid"/> struct using the specified byte array.
2336
/// </summary>
24-
/// <param name="bytes">The byte array to initialize the ULID with.</param>
37+
/// <param name="bytes">The byte array to initialize the <see cref="Ulid"/> with.</param>
38+
/// <returns>Given bytes as an <see cref="Ulid"/> instance.</returns>
2539
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2640
public static Ulid New(ReadOnlySpan<byte> bytes)
2741
=> MemoryMarshal.Read<Ulid>(bytes);
2842

2943
/// <summary>
30-
/// Creates a new ULID with the current timestamp.
44+
/// Creates a new <see cref="Ulid"/> with the current timestamp.
3145
/// </summary>
32-
/// <param name="isMonotonic">If true, ensures the ULID is monotonically increasing.</param>
33-
/// <returns>A new ULID instance.</returns>
46+
/// <param name="isMonotonic">
47+
/// If <c>null</c> (default), the value of <see cref="DefaultIsMonotonic"/> is used to determine monotonicity.<br />
48+
/// If <c>true</c>, ensures the <see cref="Ulid"/> is monotonically increasing.<br />
49+
/// If <c>false</c>, generates a random <see cref="Random" /> part in <see cref="Ulid"/>.
50+
/// </param>
51+
/// <returns>A new <see cref="Ulid"/> instance.</returns>
3452
[MethodImpl(MethodImplOptions.AggressiveInlining)]
35-
public static Ulid New(bool isMonotonic = true)
53+
public static Ulid New(bool? isMonotonic = null)
3654
=> New(DateTimeOffset.UtcNow, isMonotonic);
3755

3856
/// <summary>
39-
/// Creates a new ULID with the specified timestamp.
57+
/// Creates a new <see cref="Ulid"/> with the specified timestamp.
4058
/// </summary>
41-
/// <param name="dateTimeOffset">The timestamp to use for the ULID.</param>
42-
/// <param name="isMonotonic">If true, ensures the ULID is monotonically increasing.</param>
43-
/// <returns>A new ULID instance.</returns>
59+
/// <param name="dateTimeOffset">The timestamp to use for the <see cref="Ulid"/>.</param>
60+
/// <param name="isMonotonic">
61+
/// If <c>null</c> (default), the value of <see cref="DefaultIsMonotonic"/> is used to determine monotonicity.<br />
62+
/// If <c>true</c>, ensures the <see cref="Ulid"/> is monotonically increasing.<br />
63+
/// If <c>false</c>, generates a random <see cref="Random" /> part in <see cref="Ulid"/>.
64+
/// </param>
65+
/// <returns>A new <see cref="Ulid"/> instance.</returns>
4466
[MethodImpl(MethodImplOptions.AggressiveInlining)]
45-
public static Ulid New(DateTimeOffset dateTimeOffset, bool isMonotonic = true)
67+
public static Ulid New(DateTimeOffset dateTimeOffset, bool? isMonotonic = null)
4668
=> New(dateTimeOffset.ToUnixTimeMilliseconds(), isMonotonic);
4769

4870
/// <summary>
49-
/// Creates a new ULID with the specified timestamp.
71+
/// Creates a new <see cref="Ulid"/> with the specified timestamp.
5072
/// </summary>
51-
/// <param name="dateTimeOffset">The timestamp to use for the ULID.</param>
73+
/// <param name="dateTimeOffset">The timestamp to use for the <see cref="Ulid"/>.</param>
5274
/// <param name="random" >
53-
/// A span containing the random component of the ULID.
54-
/// It must be at least 10 bytes long, as the last 10 bytes of the ULID are derived from this span.
75+
/// A span containing the random component of the <see cref="Ulid"/>.
76+
/// It must be at least 10 bytes long, as the last 10 bytes of the <see cref="Ulid"/> are derived from this span.
5577
/// </param>
56-
/// <returns>A new ULID instance.</returns>
78+
/// <returns>A new <see cref="Ulid"/> instance.</returns>
5779
[MethodImpl(MethodImplOptions.AggressiveInlining)]
5880
public static Ulid New(DateTimeOffset dateTimeOffset, Span<byte> random)
5981
=> New(dateTimeOffset.ToUnixTimeMilliseconds(), random);
6082

6183
/// <summary>
62-
/// Creates a new ULID with the specified timestamp in milliseconds.
84+
/// Creates a new <see cref="Ulid"/> with the specified timestamp in milliseconds.
6385
/// </summary>
64-
/// <param name="timestamp">The timestamp in milliseconds to use for the ULID.</param>
65-
/// <param name="isMonotonic">If true, ensures the ULID is monotonically increasing.</param>
66-
/// <returns>A new ULID instance.</returns>
86+
/// <param name="timestamp">The timestamp in milliseconds to use for the <see cref="Ulid"/>.</param>
87+
/// <param name="isMonotonic">
88+
/// If <c>null</c> (default), the value of <see cref="DefaultIsMonotonic"/> is used to determine monotonicity.<br />
89+
/// If <c>true</c>, ensures the <see cref="Ulid"/> is monotonically increasing.<br />
90+
/// If <c>false</c>, generates a random <see cref="Random" /> part in <see cref="Ulid"/>.
91+
/// </param>
92+
/// <returns>A new <see cref="Ulid"/> instance.</returns>
6793
#if NET5_0_OR_GREATER
6894
[SkipLocalsInit]
6995
#endif
7096
[MethodImpl(MethodImplOptions.AggressiveInlining)]
71-
public static Ulid New(long timestamp, bool isMonotonic = true)
97+
public static Ulid New(long timestamp, bool? isMonotonic = null)
7298
{
7399
Ulid ulid = default;
74100

@@ -77,7 +103,7 @@ public static Ulid New(long timestamp, bool isMonotonic = true)
77103
var ulidBytes = new Span<byte>(Unsafe.AsPointer(ref Unsafe.AsRef(in ulid)), _ulidSize);
78104

79105
FillTime(ulidBytes, timestamp);
80-
FillRandom(ulidBytes, isMonotonic);
106+
FillRandom(ulidBytes, isMonotonic ?? DefaultIsMonotonic);
81107
}
82108

83109
return ulid;
@@ -88,11 +114,11 @@ public static Ulid New(long timestamp, bool isMonotonic = true)
88114
/// </summary>
89115
/// <param name="timestamp">
90116
/// A 64-bit integer representing the timestamp in milliseconds since the Unix epoch (1970-01-01T00:00:00Z).
91-
/// This value will be encoded into the first 6 bytes of the ULID.
117+
/// This value will be encoded into the first 6 bytes of the <see cref="Ulid"/>.
92118
/// </param>
93119
/// <param name="random">
94-
/// A span containing the random component of the ULID.
95-
/// It must be at least 10 bytes long, as the last 10 bytes of the ULID are derived from this span.
120+
/// A span containing the random component of the <see cref="Ulid"/>.
121+
/// It must be at least 10 bytes long, as the last 10 bytes of the <see cref="Ulid"/> are derived from this span.
96122
/// </param>
97123
/// <returns>
98124
/// A new <see cref="Ulid"/> instance composed of the given timestamp and random byte sequence.

0 commit comments

Comments
 (0)