Skip to content

#105 UUIDUtil.extractTimestamp() is broken #106

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 3 commits into from
May 21, 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
2 changes: 2 additions & 0 deletions release-notes/VERSION
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
14 changes: 14 additions & 0 deletions src/main/java/com/fasterxml/uuid/UUIDTimer.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
31 changes: 24 additions & 7 deletions src/main/java/com/fasterxml/uuid/impl/UUIDUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.UUID;

import com.fasterxml.uuid.UUIDTimer;
import com.fasterxml.uuid.UUIDType;

public class UUIDUtil
Expand Down Expand Up @@ -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
*/
Expand All @@ -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
*<p>
* 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;
Expand All @@ -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.
*<p>
* 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;
Expand All @@ -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;
Expand Down
28 changes: 26 additions & 2 deletions src/test/java/com/fasterxml/uuid/impl/UUIDUtilTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.UUID;

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

/**
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down