diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c7778e..a1ee02c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,11 @@ All notable changes to this project will be documented in this file. ## [Unreleased] -Nothing unreleased. +Add a MIN and MAX constants and methods. #26 + +## [5.2.0] - 2023-??-?? + +To be released. ## [5.1.0] - 2022-10-22 @@ -302,7 +306,8 @@ Project created as an alternative Java implementation of [ULID spec](https://git - Added `LICENSE` - Added test cases -[unreleased]: https://github.com/f4b6a3/ulid-creator/compare/ulid-creator-5.1.0...HEAD +[unreleased]: https://github.com/f4b6a3/ulid-creator/compare/ulid-creator-5.2.0...HEAD +[5.2.0]: https://github.com/f4b6a3/ulid-creator/compare/ulid-creator-5.1.0...ulid-creator-5.2.0 [5.1.0]: https://github.com/f4b6a3/ulid-creator/compare/ulid-creator-5.0.2...ulid-creator-5.1.0 [5.0.2]: https://github.com/f4b6a3/ulid-creator/compare/ulid-creator-5.0.1...ulid-creator-5.0.2 [5.0.1]: https://github.com/f4b6a3/ulid-creator/compare/ulid-creator-5.0.0...ulid-creator-5.0.1 diff --git a/benchmark/src/main/java/benchmark/Throughput.java b/benchmark/src/main/java/benchmark/Throughput.java index b29566d..b807cce 100644 --- a/benchmark/src/main/java/benchmark/Throughput.java +++ b/benchmark/src/main/java/benchmark/Throughput.java @@ -66,4 +66,14 @@ public class Throughput { public String UlidCreator_getMonotonicUlid_toString() { return UlidCreator.getMonotonicUlid().toString(); } + + @Benchmark + public Ulid UlidCreator_getHashUlid() { + return UlidCreator.getHashUlid(0L, "this is a test"); + } + + @Benchmark + public Ulid UlidCreator_getHashUlidString() { + return UlidCreator.getHashUlid(0L, "this is a test").toString(); + } } diff --git a/src/main/java/com/github/f4b6a3/ulid/Ulid.java b/src/main/java/com/github/f4b6a3/ulid/Ulid.java index e9ecc2b..743b27f 100644 --- a/src/main/java/com/github/f4b6a3/ulid/Ulid.java +++ b/src/main/java/com/github/f4b6a3/ulid/Ulid.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2020-2022 Fabio Lima + * Copyright (c) 2020-2023 Fabio Lima * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -26,8 +26,8 @@ package com.github.f4b6a3.ulid; import java.io.Serializable; import java.time.Instant; -import java.util.SplittableRandom; import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; /** * A class that represents ULIDs. @@ -79,6 +79,14 @@ public final class Ulid implements Serializable, Comparable { * Number of bytes of the random component of a ULID. */ public static final int RANDOM_BYTES = 10; + /** + * A special ULID that has all 128 bits set to ZERO. + */ + public static final Ulid MIN = new Ulid(0x0000000000000000L, 0x0000000000000000L); + /** + * A special ULID that has all 128 bits set to ONE. + */ + public static final Ulid MAX = new Ulid(0xffffffffffffffffL, 0xffffffffffffffffL); private static final char[] ALPHABET_UPPERCASE = // { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', // @@ -243,22 +251,64 @@ public final class Ulid implements Serializable, Comparable { *

* This static method is a quick alternative to {@link UlidCreator#getUlid()}. *

- * It employs {@link SplittableRandom} which works very well, although not - * cryptographically strong. + * It employs {@link ThreadLocalRandom} which works very well, although not + * cryptographically strong. It can be useful, for example, for logging. *

* Security-sensitive applications that require a cryptographically secure * pseudo-random generator should use {@link UlidCreator#getUlid()}. * * @return a ULID - * @see {@link SplittableRandom} + * @see {@link ThreadLocalRandom} * @since 5.1.0 */ public static Ulid fast() { final long time = System.currentTimeMillis(); - final SplittableRandom random = new SplittableRandom(); + ThreadLocalRandom random = ThreadLocalRandom.current(); return new Ulid((time << 16) | (random.nextLong() & 0xffffL), random.nextLong()); } + /** + * Returns the minimum ULID for a given time. + *

+ * The 48 bits of the time component are filled with the given time and the 80 + * bits of the random component are all set to ZERO. + *

+ * For example, the minimum ULID for 2022-02-22 22:22:22.222 is + * `{@code new Ulid(0x018781ebb25e0000L, 0x0000000000000000L)}`, where + * `{@code 0x018781ebb25e}` is the timestamp in hexadecimal. + *

+ * It can be useful to find all records before or after a specific timestamp in + * a table without a `{@code created_at}` field. + * + * @param time the the number of milliseconds since 1970-01-01 + * @return a ULID + * @since 5.2.0 + */ + public static Ulid min(long time) { + return new Ulid((time << 16) | 0x0000L, 0x0000000000000000L); + } + + /** + * Returns the maximum ULID for a given time. + *

+ * The 48 bits of the time component are filled with the given time and the 80 + * bits or the random component are all set to ONE. + *

+ * For example, the maximum ULID for 2022-02-22 22:22:22.222 is + * `{@code new Ulid(0x018781ebb25effffL, 0xffffffffffffffffL)}`, where + * `{@code 0x018781ebb25e}` is the timestamp in hexadecimal. + *

+ * It can be useful to find all records before or after a specific timestamp in + * a table without a `{@code created_at}` field. + * + * @param time the the number of milliseconds since 1970-01-01 + * @return a ULID + * @since 5.2.0 + */ + public static Ulid max(long time) { + return new Ulid((time << 16) | 0xffffL, 0xffffffffffffffffL); + } + /** * Converts a UUID into a ULID. * diff --git a/src/main/java/com/github/f4b6a3/ulid/UlidFactory.java b/src/main/java/com/github/f4b6a3/ulid/UlidFactory.java index ae98301..3735420 100644 --- a/src/main/java/com/github/f4b6a3/ulid/UlidFactory.java +++ b/src/main/java/com/github/f4b6a3/ulid/UlidFactory.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2020-2022 Fabio Lima + * Copyright (c) 2020-2023 Fabio Lima * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/src/test/java/com/github/f4b6a3/ulid/UlidTest.java b/src/test/java/com/github/f4b6a3/ulid/UlidTest.java index 4a85e0f..0337bf3 100644 --- a/src/test/java/com/github/f4b6a3/ulid/UlidTest.java +++ b/src/test/java/com/github/f4b6a3/ulid/UlidTest.java @@ -3,11 +3,15 @@ package com.github.f4b6a3.ulid; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.math.BigInteger; import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.time.Instant; import java.util.Arrays; import java.util.Random; @@ -278,12 +282,43 @@ public class UlidTest extends UlidFactoryTest { } } + @Test + public void testMinAndMax() { + + long time = 0; + Random random = new Random(); + byte[] bytes = new byte[Ulid.RANDOM_BYTES]; + + for (int i = 0; i < 100; i++) { + + time = random.nextLong() & TIME_MASK; + + { + // Test MIN + Ulid ulid = Ulid.min(time); + assertEquals(time, ulid.getTime()); + for (int j = 0; j < bytes.length; j++) { + assertEquals(0, ulid.getRandom()[j]); + } + } + + { + // Test MAX + Ulid ulid = Ulid.max(time); + assertEquals(time, ulid.getTime()); + for (int j = 0; j < bytes.length; j++) { + assertEquals(-1, ulid.getRandom()[j]); + } + } + } + } + @Test public void testGetTimeAndGetRandom() { long time = 0; - byte[] bytes = new byte[Ulid.RANDOM_BYTES]; Random random = new Random(); + byte[] bytes = new byte[Ulid.RANDOM_BYTES]; for (int i = 0; i < 100; i++) { @@ -292,18 +327,18 @@ public class UlidTest extends UlidFactoryTest { // Instance methods Ulid ulid = new Ulid(time, bytes); - assertEquals(time, ulid.getTime()); // test Ulid.getTime() - assertEquals(Instant.ofEpochMilli(time), ulid.getInstant()); // test Ulid.getInstant() + assertEquals(time, ulid.getTime()); + assertEquals(Instant.ofEpochMilli(time), ulid.getInstant()); for (int j = 0; j < bytes.length; j++) { - assertEquals(bytes[j], ulid.getRandom()[j]); // test Ulid.getRandom() + assertEquals(bytes[j], ulid.getRandom()[j]); } // Static methods String string = new Ulid(time, bytes).toString(); - assertEquals(time, Ulid.getTime(string)); // test Ulid.getTime() - assertEquals(Instant.ofEpochMilli(time), Ulid.getInstant(string)); // test Ulid.getInstant() + assertEquals(time, Ulid.getTime(string)); + assertEquals(Instant.ofEpochMilli(time), Ulid.getInstant(string)); for (int j = 0; j < bytes.length; j++) { - assertEquals(bytes[j], Ulid.getRandom(string)[j]); // test Ulid.getRandom() + assertEquals(bytes[j], Ulid.getRandom(string)[j]); } } }