diff --git a/release-notes/VERSION b/release-notes/VERSION index 94cd989..c3f7c47 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -8,6 +8,8 @@ Releases #99: New factory method to create TimeBasedEpochRandomGenerator (contributed by Daniel A) +#105: `UUIDUtil.extractTimestamp()` is broken for versions 1 and 6 + (contributed by @magdel) 5.0.0 (23-Feb-2024) diff --git a/src/main/java/com/fasterxml/uuid/UUIDTimer.java b/src/main/java/com/fasterxml/uuid/UUIDTimer.java index 3822c4b..203f934 100644 --- a/src/main/java/com/fasterxml/uuid/UUIDTimer.java +++ b/src/main/java/com/fasterxml/uuid/UUIDTimer.java @@ -320,6 +320,20 @@ public synchronized long getTimestamp() return systime; } + /** + * Converts a UUID v1 or v6 timestamp (where unit is 100 nanoseconds), + * to Unix epoch timestamp (milliseconds since 01-Jan-1970 UTC) + * + * @param timestamp Timestamp used to create UUID versions 1 and 6 + * + * @return Unix epoch timestamp + * + * @since 5.1 + */ + public static long timestampToEpoch(long timestamp) { + return (timestamp - kClockOffset) / kClockMultiplierL; + } + /* /********************************************************************** /* Test-support methods diff --git a/src/main/java/com/fasterxml/uuid/impl/UUIDUtil.java b/src/main/java/com/fasterxml/uuid/impl/UUIDUtil.java index fffea93..b2d5291 100644 --- a/src/main/java/com/fasterxml/uuid/impl/UUIDUtil.java +++ b/src/main/java/com/fasterxml/uuid/impl/UUIDUtil.java @@ -2,6 +2,7 @@ import java.util.UUID; +import com.fasterxml.uuid.UUIDTimer; import com.fasterxml.uuid.UUIDType; public class UUIDUtil @@ -360,7 +361,7 @@ private final static void _checkUUIDByteArray(byte[] bytes, int offset) * * @param uuid uuid timestamp to extract from * - * @return timestamp in milliseconds (since Epoch), or 0 if type does not support timestamps + * @return Unix timestamp in milliseconds (since Epoch), or 0 if type does not support timestamps * * @since 5.0 */ @@ -380,17 +381,25 @@ public static long extractTimestamp(UUID uuid) case NAME_BASED_MD5: return 0L; case TIME_BASED: - return _getTimestampFromUuidV1(uuid); + return UUIDTimer.timestampToEpoch(_getRawTimestampFromUuidV1(uuid)); case TIME_BASED_REORDERED: - return _getTimestampFromUuidV6(uuid); + return UUIDTimer.timestampToEpoch(_getRawTimestampFromUuidV6(uuid)); case TIME_BASED_EPOCH: - return _getTimestampFromUuidV7(uuid); + return _getRawTimestampFromUuidV7(uuid); default: throw new IllegalArgumentException("Invalid `UUID`: unexpected type " + type); } } - private static long _getTimestampFromUuidV1(UUID uuid) { + /** + * Get raw timestamp, used to create the UUID v1 + *

+ * NOTE: no verification is done to ensure UUID given is of version 1. + * + * @param uuid uuid, to extract timestamp from + * @return timestamp, used to create uuid v1 + */ + static long _getRawTimestampFromUuidV1(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; @@ -400,7 +409,15 @@ private static long _getTimestampFromUuidV1(UUID uuid) { return highOfHigher << 48 | lowOfHigher << 32 | low; } - private static long _getTimestampFromUuidV6(UUID uuid) { + /** + * Get raw timestamp, used to create the UUID v6. + *

+ * NOTE: no verification is done to ensure UUID given is of version 6. + * + * @param uuid uuid, to extract timestamp from + * @return timestamp, used to create uuid v6 + */ + static long _getRawTimestampFromUuidV6(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; @@ -410,7 +427,7 @@ private static long _getTimestampFromUuidV6(UUID uuid) { return high >>> 4 | lowH << 12 | lowL; } - private static long _getTimestampFromUuidV7(UUID uuid) { + static long _getRawTimestampFromUuidV7(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 9186e74..b730c00 100644 --- a/src/test/java/com/fasterxml/uuid/impl/UUIDUtilTest.java +++ b/src/test/java/com/fasterxml/uuid/impl/UUIDUtilTest.java @@ -4,6 +4,7 @@ import java.util.UUID; import com.fasterxml.uuid.Generators; +import com.fasterxml.uuid.NoArgGenerator; import junit.framework.TestCase; /** @@ -37,20 +38,35 @@ public void testExtractTimestampUUIDTimeBased() { for (int i = 0; i < TEST_REPS; i++) { long rawTimestamp = rnd.nextLong() >>> 4; UUID uuid = generator.construct(rawTimestamp); - assertEquals(rawTimestamp, UUIDUtil.extractTimestamp(uuid)); + assertEquals(rawTimestamp, UUIDUtil._getRawTimestampFromUuidV1(uuid)); } } + public void testExtractTimestampUUIDTimeBasedCurrentTimemillis() { + TimeBasedGenerator generator = Generators.timeBasedGenerator(); + long time = System.currentTimeMillis(); + UUID uuid2 = generator.generate(); + assertEquals(time, UUIDUtil.extractTimestamp(uuid2)); + } + + public void testExtractTimestampUUIDTimeBasedReordered() { TimeBasedReorderedGenerator generator = Generators.timeBasedReorderedGenerator(); 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)); + assertEquals(rawTimestamp, UUIDUtil._getRawTimestampFromUuidV6(uuid)); } } + public void testExtractTimestampUUIDTimeBasedReorderedCurrentTimeMillis() { + NoArgGenerator generator = Generators.timeBasedReorderedGenerator(); + long time = System.currentTimeMillis(); + UUID uuid = generator.generate(); + assertEquals(time, UUIDUtil.extractTimestamp(uuid)); + } + public void testExtractTimestampUUIDEpochBased() { TimeBasedEpochGenerator generator = Generators.timeBasedEpochGenerator(); final Random rnd = new Random(3); @@ -61,6 +77,14 @@ public void testExtractTimestampUUIDEpochBased() { } } + public void testExtractTimestampUUIDEpochBasedCurrentTimeMillis() { + NoArgGenerator generator = Generators.timeBasedEpochGenerator(); + long time = System.currentTimeMillis(); + UUID uuid = generator.generate(); + assertEquals(time, UUIDUtil.extractTimestamp(uuid)); + } + + public void testExtractTimestampUUIDEpochRandomBased() { TimeBasedEpochRandomGenerator generator = Generators.timeBasedEpochRandomGenerator(); final Random rnd = new Random(3);