Skip to content

Adds utility method to get timestamp from time-based UUIDs #95

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Feb 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions release-notes/VERSION
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
62 changes: 62 additions & 0 deletions src/main/java/com/fasterxml/uuid/impl/UUIDUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -353,4 +353,66 @@ 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 64-bit timestamp from time-based UUIDs (if time-based type);
* returns 0 for other types.
*
* @param uuid uuid timestamp to extract from
*
* @return timestamp in milliseconds (since Epoch), or 0 if type does not support timestamps
*
* @since 5.0.0
*/
public static long extractTimestamp(UUID uuid)
{
UUIDType type = typeOf(uuid);
if (type == null) {
// Likely null UUID:
return 0L;
}
switch (type) {
case NAME_BASED_SHA1:
case UNKNOWN:
case DCE:
case RANDOM_BASED:
case FREE_FORM:
case NAME_BASED_MD5:
return 0L;
case TIME_BASED:
return _getTimestampFromUuidV1(uuid);
case TIME_BASED_REORDERED:
return _getTimestampFromUuidV6(uuid);
case TIME_BASED_EPOCH:
return _getTimestampFromUuidV7(uuid);
default:
throw new IllegalArgumentException("Invalid `UUID`: 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;
}
}
41 changes: 41 additions & 0 deletions src/test/java/com/fasterxml/uuid/impl/UUIDUtilTest.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.fasterxml.uuid.impl;

import java.util.Random;
import java.util.UUID;

import com.fasterxml.uuid.Generators;
import junit.framework.TestCase;

/**
Expand All @@ -13,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:
Expand All @@ -26,4 +30,41 @@ public void testMaxUUID() {
assertEquals(~0, max.getMostSignificantBits());
assertEquals(~0, max.getLeastSignificantBits());
}

public void testExtractTimestampUUIDTimeBased() {
TimeBasedGenerator generator = Generators.timeBasedGenerator();
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));
}
}

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));
}
}

public void testExtractTimestampUUIDEpochBased() {
TimeBasedEpochGenerator generator = Generators.timeBasedEpochGenerator();
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));
}
}

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()));
}
}