From adec3e88490614530b116e1595c9e7206b90a533 Mon Sep 17 00:00:00 2001 From: Pavel Raev Date: Fri, 16 Feb 2024 23:39:35 +0300 Subject: [PATCH 1/4] Resolves #81 Adds utility method to get timestamp from time-based UUIDs --- .../com/fasterxml/uuid/impl/UUIDUtil.java | 55 +++++++++++++++++++ .../com/fasterxml/uuid/impl/UUIDUtilTest.java | 38 +++++++++++++ 2 files changed, 93 insertions(+) diff --git a/src/main/java/com/fasterxml/uuid/impl/UUIDUtil.java b/src/main/java/com/fasterxml/uuid/impl/UUIDUtil.java index 41c8984..bc12c79 100644 --- a/src/main/java/com/fasterxml/uuid/impl/UUIDUtil.java +++ b/src/main/java/com/fasterxml/uuid/impl/UUIDUtil.java @@ -353,4 +353,59 @@ private final static void _checkUUIDByteArray(byte[] bytes, int offset) throw new IllegalArgumentException("Invalid offset ("+offset+") passed: not enough room in byte array (need 16 bytes)"); } } + + /** + * Extract timestamp from time-based UUIDs. If UUID is not time-based type return 0; + * + * @param uuid uuid timestamp to extract from + * @return time in milliseconds, or 0 if type does not support timestamps + */ + public static long extractTimestamp(UUID uuid) { + UUIDType type = typeOf(uuid); + if (type == null) + return 0; + switch (type) { + case NAME_BASED_SHA1: + case UNKNOWN: + case DCE: + case RANDOM_BASED: + case FREE_FORM: + case NAME_BASED_MD5: + return 0; + case TIME_BASED: + return getTimestampFromUuidV1(uuid); + case TIME_BASED_REORDERED: + return getTimestampFromUuidV6(uuid); + case TIME_BASED_EPOCH: + return getTimestampFromUuidV7(uuid); + default: + throw new IllegalArgumentException("Unexpected type " + type); + } + } + + private static long getTimestampFromUuidV1(UUID uuid) { + long mostSignificantBits = uuid.getMostSignificantBits(); + mostSignificantBits = mostSignificantBits & 0b1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1110_1111_1111_1111L; + long low = mostSignificantBits >>> 32; + long lowOfHigher = mostSignificantBits & 0xFFFF0000L; + lowOfHigher = lowOfHigher >>> 16; + long highOfHigher = mostSignificantBits & 0xFFFFL; + return highOfHigher << 48 | lowOfHigher << 32 | low; + } + + private static long getTimestampFromUuidV6(UUID uuid) { + long mostSignificantBits = uuid.getMostSignificantBits(); + mostSignificantBits = mostSignificantBits & 0b1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1001_1111_1111_1111L; + long lowL = mostSignificantBits & 0xFFFL; + long lowH = mostSignificantBits & 0xFFFF0000L; + lowH = lowH >>> 16; + long high = mostSignificantBits & 0xFFFFFFFF00000000L; + return high >>> 4 | lowH << 12 | lowL; + } + + private static long getTimestampFromUuidV7(UUID uuid) { + long mostSignificantBits = uuid.getMostSignificantBits(); + mostSignificantBits = mostSignificantBits & 0b1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1001_1111_1111_1111L; + return mostSignificantBits >>> 16; + } } diff --git a/src/test/java/com/fasterxml/uuid/impl/UUIDUtilTest.java b/src/test/java/com/fasterxml/uuid/impl/UUIDUtilTest.java index 2e43517..7b89818 100644 --- a/src/test/java/com/fasterxml/uuid/impl/UUIDUtilTest.java +++ b/src/test/java/com/fasterxml/uuid/impl/UUIDUtilTest.java @@ -1,8 +1,12 @@ package com.fasterxml.uuid.impl; import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; +import com.fasterxml.uuid.Generators; import junit.framework.TestCase; +import org.junit.Test; +import org.junit.runners.JUnit4; /** * Test class focusing on verifying functionality provided by @@ -26,4 +30,38 @@ public void testMaxUUID() { assertEquals(~0, max.getMostSignificantBits()); assertEquals(~0, max.getLeastSignificantBits()); } + + public void testExtractTimestampUUIDTimeBased() { + TimeBasedGenerator generator = Generators.timeBasedGenerator(); + for (int i = 0; i < 9000; i++) { + long rawTimestamp = ThreadLocalRandom.current().nextLong() >>> 60; + UUID uuid = generator.construct(rawTimestamp); + assertEquals(rawTimestamp, UUIDUtil.extractTimestamp(uuid)); + } + } + + public void testExtractTimestampUUIDTimeBasedReordered() { + TimeBasedReorderedGenerator generator = Generators.timeBasedReorderedGenerator(); + for (int i = 0; i < 9000; i++) { + long rawTimestamp = ThreadLocalRandom.current().nextLong() >>> 60; + UUID uuid = generator.construct(rawTimestamp); + assertEquals(rawTimestamp, UUIDUtil.extractTimestamp(uuid)); + } + } + + public void testExtractTimestampUUIDEpochBased() { + TimeBasedEpochGenerator generator = Generators.timeBasedEpochGenerator(); + for (int i = 0; i < 9000; i++) { + long rawTimestamp = ThreadLocalRandom.current().nextLong() >>> 60; + UUID uuid = generator.construct(rawTimestamp); + assertEquals(rawTimestamp, UUIDUtil.extractTimestamp(uuid)); + } + } + + public void testExtractTimestampUUIDOnOtherValues() { + assertEquals(0L, UUIDUtil.extractTimestamp(null)); + assertEquals(0L, UUIDUtil.extractTimestamp(UUID.fromString("00000000-0000-0000-0000-000000000000"))); + assertEquals(0L, UUIDUtil.extractTimestamp(UUIDUtil.nilUUID())); + assertEquals(0L, UUIDUtil.extractTimestamp(UUIDUtil.maxUUID())); + } } From 6b8d99c76d51046c3924dc07614a2d878ef6a16c Mon Sep 17 00:00:00 2001 From: Pavel Raev Date: Sat, 17 Feb 2024 09:56:11 +0300 Subject: [PATCH 2/4] tests fix --- src/test/java/com/fasterxml/uuid/impl/UUIDUtilTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/fasterxml/uuid/impl/UUIDUtilTest.java b/src/test/java/com/fasterxml/uuid/impl/UUIDUtilTest.java index 7b89818..e8e9de4 100644 --- a/src/test/java/com/fasterxml/uuid/impl/UUIDUtilTest.java +++ b/src/test/java/com/fasterxml/uuid/impl/UUIDUtilTest.java @@ -34,7 +34,7 @@ public void testMaxUUID() { public void testExtractTimestampUUIDTimeBased() { TimeBasedGenerator generator = Generators.timeBasedGenerator(); for (int i = 0; i < 9000; i++) { - long rawTimestamp = ThreadLocalRandom.current().nextLong() >>> 60; + long rawTimestamp = ThreadLocalRandom.current().nextLong() >>> 4; UUID uuid = generator.construct(rawTimestamp); assertEquals(rawTimestamp, UUIDUtil.extractTimestamp(uuid)); } @@ -43,7 +43,7 @@ public void testExtractTimestampUUIDTimeBased() { public void testExtractTimestampUUIDTimeBasedReordered() { TimeBasedReorderedGenerator generator = Generators.timeBasedReorderedGenerator(); for (int i = 0; i < 9000; i++) { - long rawTimestamp = ThreadLocalRandom.current().nextLong() >>> 60; + long rawTimestamp = ThreadLocalRandom.current().nextLong() >>> 4; UUID uuid = generator.construct(rawTimestamp); assertEquals(rawTimestamp, UUIDUtil.extractTimestamp(uuid)); } @@ -52,7 +52,7 @@ public void testExtractTimestampUUIDTimeBasedReordered() { public void testExtractTimestampUUIDEpochBased() { TimeBasedEpochGenerator generator = Generators.timeBasedEpochGenerator(); for (int i = 0; i < 9000; i++) { - long rawTimestamp = ThreadLocalRandom.current().nextLong() >>> 60; + long rawTimestamp = ThreadLocalRandom.current().nextLong() >>> 16; UUID uuid = generator.construct(rawTimestamp); assertEquals(rawTimestamp, UUIDUtil.extractTimestamp(uuid)); } From 22882208dae294dc15f4de72e353abc9bc94958b Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Mon, 19 Feb 2024 13:32:42 -0800 Subject: [PATCH 3/4] Add release notes, minor clean up --- release-notes/VERSION | 4 +++ .../com/fasterxml/uuid/impl/UUIDUtil.java | 27 +++++++++++-------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/release-notes/VERSION b/release-notes/VERSION index 64add4f..f621b53 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -7,6 +7,10 @@ Releases 5.0.0 (not yet released) #53: Increase JDK baseline to JDK 8 +#81: Add `UUIDUtil.extractTimestamp()` for extracting 64-bit timestamp for + all timestamp-based versions + (requested by @gabrielbalan) + (contributed by @magdel) #85: Fix `LazyRandom` for native code generation tools (contributed by @Maia-Everett) diff --git a/src/main/java/com/fasterxml/uuid/impl/UUIDUtil.java b/src/main/java/com/fasterxml/uuid/impl/UUIDUtil.java index bc12c79..2acc123 100644 --- a/src/main/java/com/fasterxml/uuid/impl/UUIDUtil.java +++ b/src/main/java/com/fasterxml/uuid/impl/UUIDUtil.java @@ -359,11 +359,16 @@ private final static void _checkUUIDByteArray(byte[] bytes, int offset) * * @param uuid uuid timestamp to extract from * @return time in milliseconds, or 0 if type does not support timestamps + * + * @since 5.0.0 */ - public static long extractTimestamp(UUID uuid) { + public static long extractTimestamp(UUID uuid) + { UUIDType type = typeOf(uuid); - if (type == null) - return 0; + if (type == null) { + // Likely null UUID: + return 0L; + } switch (type) { case NAME_BASED_SHA1: case UNKNOWN: @@ -371,19 +376,19 @@ public static long extractTimestamp(UUID uuid) { case RANDOM_BASED: case FREE_FORM: case NAME_BASED_MD5: - return 0; + return 0L; case TIME_BASED: - return getTimestampFromUuidV1(uuid); + return _getTimestampFromUuidV1(uuid); case TIME_BASED_REORDERED: - return getTimestampFromUuidV6(uuid); + return _getTimestampFromUuidV6(uuid); case TIME_BASED_EPOCH: - return getTimestampFromUuidV7(uuid); + return _getTimestampFromUuidV7(uuid); default: - throw new IllegalArgumentException("Unexpected type " + type); + throw new IllegalArgumentException("Invalid `UUID`: unexpected type " + type); } } - private static long getTimestampFromUuidV1(UUID uuid) { + private static long _getTimestampFromUuidV1(UUID uuid) { long mostSignificantBits = uuid.getMostSignificantBits(); mostSignificantBits = mostSignificantBits & 0b1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1110_1111_1111_1111L; long low = mostSignificantBits >>> 32; @@ -393,7 +398,7 @@ private static long getTimestampFromUuidV1(UUID uuid) { return highOfHigher << 48 | lowOfHigher << 32 | low; } - private static long getTimestampFromUuidV6(UUID uuid) { + private static long _getTimestampFromUuidV6(UUID uuid) { long mostSignificantBits = uuid.getMostSignificantBits(); mostSignificantBits = mostSignificantBits & 0b1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1001_1111_1111_1111L; long lowL = mostSignificantBits & 0xFFFL; @@ -403,7 +408,7 @@ private static long getTimestampFromUuidV6(UUID uuid) { return high >>> 4 | lowH << 12 | lowL; } - private static long getTimestampFromUuidV7(UUID uuid) { + private static long _getTimestampFromUuidV7(UUID uuid) { long mostSignificantBits = uuid.getMostSignificantBits(); mostSignificantBits = mostSignificantBits & 0b1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1001_1111_1111_1111L; return mostSignificantBits >>> 16; From 15258ba8748b839a5945cc90a7b9974c5cadf3cd Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Mon, 19 Feb 2024 13:37:19 -0800 Subject: [PATCH 4/4] Minor change to testing --- .../com/fasterxml/uuid/impl/UUIDUtil.java | 6 ++++-- .../com/fasterxml/uuid/impl/UUIDUtilTest.java | 21 +++++++++++-------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/fasterxml/uuid/impl/UUIDUtil.java b/src/main/java/com/fasterxml/uuid/impl/UUIDUtil.java index 2acc123..60ce671 100644 --- a/src/main/java/com/fasterxml/uuid/impl/UUIDUtil.java +++ b/src/main/java/com/fasterxml/uuid/impl/UUIDUtil.java @@ -355,10 +355,12 @@ private final static void _checkUUIDByteArray(byte[] bytes, int offset) } /** - * Extract timestamp from time-based UUIDs. If UUID is not time-based type return 0; + * Extract 64-bit timestamp from time-based UUIDs (if time-based type); + * returns 0 for other types. * * @param uuid uuid timestamp to extract from - * @return time in milliseconds, or 0 if type does not support timestamps + * + * @return timestamp in milliseconds (since Epoch), or 0 if type does not support timestamps * * @since 5.0.0 */ diff --git a/src/test/java/com/fasterxml/uuid/impl/UUIDUtilTest.java b/src/test/java/com/fasterxml/uuid/impl/UUIDUtilTest.java index e8e9de4..bcf972f 100644 --- a/src/test/java/com/fasterxml/uuid/impl/UUIDUtilTest.java +++ b/src/test/java/com/fasterxml/uuid/impl/UUIDUtilTest.java @@ -1,12 +1,10 @@ package com.fasterxml.uuid.impl; +import java.util.Random; import java.util.UUID; -import java.util.concurrent.ThreadLocalRandom; import com.fasterxml.uuid.Generators; import junit.framework.TestCase; -import org.junit.Test; -import org.junit.runners.JUnit4; /** * Test class focusing on verifying functionality provided by @@ -17,6 +15,8 @@ */ public class UUIDUtilTest extends TestCase { + final static int TEST_REPS = 1_000_000; + public void testNilUUID() { UUID nil = UUIDUtil.nilUUID(); // Should be all zeroes: @@ -33,8 +33,9 @@ public void testMaxUUID() { public void testExtractTimestampUUIDTimeBased() { TimeBasedGenerator generator = Generators.timeBasedGenerator(); - for (int i = 0; i < 9000; i++) { - long rawTimestamp = ThreadLocalRandom.current().nextLong() >>> 4; + final Random rnd = new Random(1); + for (int i = 0; i < TEST_REPS; i++) { + long rawTimestamp = rnd.nextLong() >>> 4; UUID uuid = generator.construct(rawTimestamp); assertEquals(rawTimestamp, UUIDUtil.extractTimestamp(uuid)); } @@ -42,8 +43,9 @@ public void testExtractTimestampUUIDTimeBased() { public void testExtractTimestampUUIDTimeBasedReordered() { TimeBasedReorderedGenerator generator = Generators.timeBasedReorderedGenerator(); - for (int i = 0; i < 9000; i++) { - long rawTimestamp = ThreadLocalRandom.current().nextLong() >>> 4; + final Random rnd = new Random(2); + for (int i = 0; i < TEST_REPS; i++) { + long rawTimestamp = rnd.nextLong() >>> 4; UUID uuid = generator.construct(rawTimestamp); assertEquals(rawTimestamp, UUIDUtil.extractTimestamp(uuid)); } @@ -51,8 +53,9 @@ public void testExtractTimestampUUIDTimeBasedReordered() { public void testExtractTimestampUUIDEpochBased() { TimeBasedEpochGenerator generator = Generators.timeBasedEpochGenerator(); - for (int i = 0; i < 9000; i++) { - long rawTimestamp = ThreadLocalRandom.current().nextLong() >>> 16; + final Random rnd = new Random(3); + for (int i = 0; i < TEST_REPS; i++) { + long rawTimestamp = rnd.nextLong() >>> 16; UUID uuid = generator.construct(rawTimestamp); assertEquals(rawTimestamp, UUIDUtil.extractTimestamp(uuid)); }