Skip to content

Commit 191f37f

Browse files
authored
Adds utility method to get timestamp from time-based UUIDs (#95)
1 parent 31408f5 commit 191f37f

File tree

3 files changed

+107
-0
lines changed

3 files changed

+107
-0
lines changed

release-notes/VERSION

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ Releases
77
5.0.0 (not yet released)
88

99
#53: Increase JDK baseline to JDK 8
10+
#81: Add `UUIDUtil.extractTimestamp()` for extracting 64-bit timestamp for
11+
all timestamp-based versions
12+
(requested by @gabrielbalan)
13+
(contributed by @magdel)
1014
#85: Fix `LazyRandom` for native code generation tools
1115
(contributed by @Maia-Everett)
1216

src/main/java/com/fasterxml/uuid/impl/UUIDUtil.java

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,4 +353,66 @@ private final static void _checkUUIDByteArray(byte[] bytes, int offset)
353353
throw new IllegalArgumentException("Invalid offset ("+offset+") passed: not enough room in byte array (need 16 bytes)");
354354
}
355355
}
356+
357+
/**
358+
* Extract 64-bit timestamp from time-based UUIDs (if time-based type);
359+
* returns 0 for other types.
360+
*
361+
* @param uuid uuid timestamp to extract from
362+
*
363+
* @return timestamp in milliseconds (since Epoch), or 0 if type does not support timestamps
364+
*
365+
* @since 5.0.0
366+
*/
367+
public static long extractTimestamp(UUID uuid)
368+
{
369+
UUIDType type = typeOf(uuid);
370+
if (type == null) {
371+
// Likely null UUID:
372+
return 0L;
373+
}
374+
switch (type) {
375+
case NAME_BASED_SHA1:
376+
case UNKNOWN:
377+
case DCE:
378+
case RANDOM_BASED:
379+
case FREE_FORM:
380+
case NAME_BASED_MD5:
381+
return 0L;
382+
case TIME_BASED:
383+
return _getTimestampFromUuidV1(uuid);
384+
case TIME_BASED_REORDERED:
385+
return _getTimestampFromUuidV6(uuid);
386+
case TIME_BASED_EPOCH:
387+
return _getTimestampFromUuidV7(uuid);
388+
default:
389+
throw new IllegalArgumentException("Invalid `UUID`: unexpected type " + type);
390+
}
391+
}
392+
393+
private static long _getTimestampFromUuidV1(UUID uuid) {
394+
long mostSignificantBits = uuid.getMostSignificantBits();
395+
mostSignificantBits = mostSignificantBits & 0b1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1110_1111_1111_1111L;
396+
long low = mostSignificantBits >>> 32;
397+
long lowOfHigher = mostSignificantBits & 0xFFFF0000L;
398+
lowOfHigher = lowOfHigher >>> 16;
399+
long highOfHigher = mostSignificantBits & 0xFFFFL;
400+
return highOfHigher << 48 | lowOfHigher << 32 | low;
401+
}
402+
403+
private static long _getTimestampFromUuidV6(UUID uuid) {
404+
long mostSignificantBits = uuid.getMostSignificantBits();
405+
mostSignificantBits = mostSignificantBits & 0b1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1001_1111_1111_1111L;
406+
long lowL = mostSignificantBits & 0xFFFL;
407+
long lowH = mostSignificantBits & 0xFFFF0000L;
408+
lowH = lowH >>> 16;
409+
long high = mostSignificantBits & 0xFFFFFFFF00000000L;
410+
return high >>> 4 | lowH << 12 | lowL;
411+
}
412+
413+
private static long _getTimestampFromUuidV7(UUID uuid) {
414+
long mostSignificantBits = uuid.getMostSignificantBits();
415+
mostSignificantBits = mostSignificantBits & 0b1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1001_1111_1111_1111L;
416+
return mostSignificantBits >>> 16;
417+
}
356418
}

src/test/java/com/fasterxml/uuid/impl/UUIDUtilTest.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package com.fasterxml.uuid.impl;
22

3+
import java.util.Random;
34
import java.util.UUID;
45

6+
import com.fasterxml.uuid.Generators;
57
import junit.framework.TestCase;
68

79
/**
@@ -13,6 +15,8 @@
1315
*/
1416
public class UUIDUtilTest extends TestCase
1517
{
18+
final static int TEST_REPS = 1_000_000;
19+
1620
public void testNilUUID() {
1721
UUID nil = UUIDUtil.nilUUID();
1822
// Should be all zeroes:
@@ -26,4 +30,41 @@ public void testMaxUUID() {
2630
assertEquals(~0, max.getMostSignificantBits());
2731
assertEquals(~0, max.getLeastSignificantBits());
2832
}
33+
34+
public void testExtractTimestampUUIDTimeBased() {
35+
TimeBasedGenerator generator = Generators.timeBasedGenerator();
36+
final Random rnd = new Random(1);
37+
for (int i = 0; i < TEST_REPS; i++) {
38+
long rawTimestamp = rnd.nextLong() >>> 4;
39+
UUID uuid = generator.construct(rawTimestamp);
40+
assertEquals(rawTimestamp, UUIDUtil.extractTimestamp(uuid));
41+
}
42+
}
43+
44+
public void testExtractTimestampUUIDTimeBasedReordered() {
45+
TimeBasedReorderedGenerator generator = Generators.timeBasedReorderedGenerator();
46+
final Random rnd = new Random(2);
47+
for (int i = 0; i < TEST_REPS; i++) {
48+
long rawTimestamp = rnd.nextLong() >>> 4;
49+
UUID uuid = generator.construct(rawTimestamp);
50+
assertEquals(rawTimestamp, UUIDUtil.extractTimestamp(uuid));
51+
}
52+
}
53+
54+
public void testExtractTimestampUUIDEpochBased() {
55+
TimeBasedEpochGenerator generator = Generators.timeBasedEpochGenerator();
56+
final Random rnd = new Random(3);
57+
for (int i = 0; i < TEST_REPS; i++) {
58+
long rawTimestamp = rnd.nextLong() >>> 16;
59+
UUID uuid = generator.construct(rawTimestamp);
60+
assertEquals(rawTimestamp, UUIDUtil.extractTimestamp(uuid));
61+
}
62+
}
63+
64+
public void testExtractTimestampUUIDOnOtherValues() {
65+
assertEquals(0L, UUIDUtil.extractTimestamp(null));
66+
assertEquals(0L, UUIDUtil.extractTimestamp(UUID.fromString("00000000-0000-0000-0000-000000000000")));
67+
assertEquals(0L, UUIDUtil.extractTimestamp(UUIDUtil.nilUUID()));
68+
assertEquals(0L, UUIDUtil.extractTimestamp(UUIDUtil.maxUUID()));
69+
}
2970
}

0 commit comments

Comments
 (0)