From dbf31d3be7877e8542be6cf6bd54dd4cd118ab1e Mon Sep 17 00:00:00 2001 From: Fabio Lima Date: Sat, 2 Sep 2023 21:28:15 -0300 Subject: [PATCH] Initialize the monotonic factory with the minimum timestamp Now the `MonotonicFunction` inner class is initialized with the instant 1970-01-01 00:00:00.000 UTC, which is represented by the value 0L. Also updated the documentation and the unit tests. --- .../java/com/github/f4b6a3/ulid/Ulid.java | 10 +++++-- .../com/github/f4b6a3/ulid/UlidFactory.java | 3 +- .../f4b6a3/ulid/UlidFactoryMonotonicTest.java | 30 +++++++++---------- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/github/f4b6a3/ulid/Ulid.java b/src/main/java/com/github/f4b6a3/ulid/Ulid.java index dd8f819..1c655e1 100644 --- a/src/main/java/com/github/f4b6a3/ulid/Ulid.java +++ b/src/main/java/com/github/f4b6a3/ulid/Ulid.java @@ -149,10 +149,14 @@ public final class Ulid implements Serializable, Comparable { /** * Creates a new ULID. *

- * Time parameter is the number of milliseconds since 1970-01-01 (Unix epoch). - * It must be a positive number not larger than 2^48-1. + * The time parameter is the number of milliseconds since 1970-01-01, also known + * as Unix epoch. It must be a positive number not larger than 2^48-1. *

- * Random parameter must be an array of 10 bytes. + * The random parameter must be an arbitrary array of 10 bytes. + *

+ * Note: ULIDs cannot be composed of dates before 1970-01-01, as their embedded + * timestamp is internally treated as an unsigned integer, i.e., it can only + * represent the set of natural numbers including zero, up to 2^48-1. * * @param time the number of milliseconds since 1970-01-01 * @param random an array of 10 bytes diff --git a/src/main/java/com/github/f4b6a3/ulid/UlidFactory.java b/src/main/java/com/github/f4b6a3/ulid/UlidFactory.java index 9ec5e97..256e261 100644 --- a/src/main/java/com/github/f4b6a3/ulid/UlidFactory.java +++ b/src/main/java/com/github/f4b6a3/ulid/UlidFactory.java @@ -360,7 +360,8 @@ public final class UlidFactory { } void initialize(LongSupplier timeFunction) { - this.lastUlid = new Ulid(timeFunction.getAsLong(), this.random.nextBytes(Ulid.RANDOM_BYTES)); + // initialize the factory with the instant 1970-01-01 00:00:00.000 UTC + this.lastUlid = new Ulid(0L, this.random.nextBytes(Ulid.RANDOM_BYTES)); } @Override diff --git a/src/test/java/com/github/f4b6a3/ulid/UlidFactoryMonotonicTest.java b/src/test/java/com/github/f4b6a3/ulid/UlidFactoryMonotonicTest.java index cd93a9d..ee80ee3 100644 --- a/src/test/java/com/github/f4b6a3/ulid/UlidFactoryMonotonicTest.java +++ b/src/test/java/com/github/f4b6a3/ulid/UlidFactoryMonotonicTest.java @@ -39,8 +39,7 @@ public class UlidFactoryMonotonicTest extends UlidFactoryTest { long diff = UlidFactory.MonotonicFunction.CLOCK_DRIFT_TOLERANCE; long time = Instant.parse("2021-12-31T23:59:59.000Z").toEpochMilli(); - long times[] = { /* init */ 0L, time + 0, time + 1, time + 2, time + 3, time + 4 - diff, time + 5 - diff, - time + 6 - diff }; + long times[] = { time + 0, time + 1, time + 2, time + 3, time + 4 - diff, time + 5 - diff, time + 6 - diff }; Clock clock = new Clock() { private int i; @@ -113,8 +112,11 @@ public class UlidFactoryMonotonicTest extends UlidFactoryTest { @Test public void testGetMonotonicUlidAfterLeapSecond() { + // The best article about leap seconds: + // (Unfortunately it can't be translated) + // https://ntp.br/conteudo/artigo-leap-second/ long time = Instant.parse("2021-12-31T23:59:59.000Z").toEpochMilli(); - long leap = time - 1000; // simulate a leap second + long leap = time - 1000; // moving the clock hands 1 second backwards long times[] = { time, leap }; Clock clock = new Clock() { @@ -144,28 +146,26 @@ public class UlidFactoryMonotonicTest extends UlidFactoryTest { LongSupplier randomFunction = () -> 0; UlidFactory factory = UlidFactory.newMonotonicInstance(randomFunction, clock); + // the clock moved normally Ulid ulid1 = factory.create(); - Ulid ulid2 = factory.create(); - long t1 = ulid1.getTime(); - long t2 = ulid2.getTime(); // leap second - long r1 = ulid1.getLeastSignificantBits(); - long r2 = ulid2.getLeastSignificantBits(); // leap second - assertEquals(time, t1); - assertEquals(time, t2); // leap second - - assertEquals(1, r1); - assertEquals(2, r2); + assertEquals(0, r1); + // the clock moved backwards + Ulid ulid2 = factory.create(); + long t2 = ulid2.getTime(); + long r2 = ulid2.getLeastSignificantBits(); + assertEquals(time, t2); // should freeze + assertEquals(1, r2); // should increment } @Test public void testGetMonotonicUlidAfterRandomBitsOverflowFollowedByTimeBitsIncrement() { long time = Instant.parse("2021-12-31T23:59:59.999Z").toEpochMilli(); - long times[] = { /* init */ 0L, time + 1, time + 2, time + 3, time, time, time }; + long times[] = { time + 1, time + 2, time + 3, time, time, time }; Clock clock = new Clock() { private int i; @@ -194,8 +194,6 @@ public class UlidFactoryMonotonicTest extends UlidFactoryTest { LongSupplier randomSupplier = () -> 0xffffffffffffffffL; UlidFactory factory = UlidFactory.newMonotonicInstance(randomSupplier, clock); - // System.out.println("time: " + time); // 1640995199999 - Ulid ulid1 = factory.create(); Ulid ulid2 = factory.create(); Ulid ulid3 = factory.create();