Skip to content

Instantly share code, notes, and snippets.

@fabiolimace
Last active October 24, 2020 20:36
Show Gist options
  • Save fabiolimace/c6e2cfa5393adf24990d78f562d03e23 to your computer and use it in GitHub Desktop.
Save fabiolimace/c6e2cfa5393adf24990d78f562d03e23 to your computer and use it in GitHub Desktop.
Generate time-based and time-ordered UUID in Java
package your.package.name;
import java.security.SecureRandom;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
/**
* A UUID generator that creates time-based and time-ordered UUIDs.
*
* RFC-4122 compliant.
*
* Tags: uuid guid uuid-generator guid-generator generator time order rfc4122 rfc-4122
*
* @author: Fabio Lima 2020
*/
public class TimeUuidCreator {
// Gregorian epoch: 1582-10-15T00:00:00.000Z
private static final long GREGORIAN_MILLISECONDS = -12219292800000L;
private static final Random SECURE_RANDOM = new SecureRandom();
private static long clkseq = getClockSequence();
private static long nodeid = getNodeIdentifier();
private static long prevTime = System.currentTimeMillis();
private static long prevNano = System.nanoTime();
private static long prevTimestamp = 0;
private static final long TICK = 100; // 1 tick = 100ns
private TimeUuidCreator() {
}
/**
* Returns a time-based UUID (version 1).
*
* @return a time-based UUID
*/
public static synchronized UUID getTimeBased() {
return getTimeBased(null, false);
}
/**
* Returns a time-based UUID (version 1).
*
* @return a time-based UUID
*/
public static synchronized UUID getTimeBased(Long nodeIdentifier) {
return getTimeBased(nodeIdentifier, false);
}
/**
* Returns a time-ordered UUID (version 6).
*
* @return a time-ordered UUID
*/
public static synchronized UUID getTimeOrdered() {
return getTimeBased(null, true);
}
/**
* Returns a time-ordered UUID (version 6).
*
* @param nodeIdentifier
* @return a time-ordered UUID
*/
public static synchronized UUID getTimeOrdered(Long nodeIdentifier) {
return getTimeBased(nodeIdentifier, true);
}
/**
* Returns a time-based (version 1) or time-ordered (version 6) UUID.
*
* @param nodeIdentifier
* @param ordered if true, generate a UUID6, otherwise UUID1
* @return a time-based UUID
*/
private static synchronized UUID getTimeBased(Long nodeIdentifier, boolean ordered) {
final long time = getTimestamp();
final long _nodeid;
if (nodeIdentifier != null) {
_nodeid = nodeIdentifier & 0x0000ffffffffffffL;
} else {
_nodeid = nodeid;
}
final long _clkseq = ((clkseq & 0x3fff) | 0x8000); // apply variant
final long msb;
if (ordered) {
msb = ((time & 0x0ffffffffffff000L) << 4) //
| (time & 0x0000000000000fffL) //
| 0x0000000000006000L; // apply version 6
} else {
msb = ((time & 0x0fff_0000_00000000L) >>> 48) //
| ((time & 0x0000_ffff_00000000L) >>> 16) //
| ((time & 0x0000_0000_ffffffffL) << 32) //
| 0x0000000000001000L; // apply version 1
}
final long lsb = (_clkseq << 48) | _nodeid;
return new UUID(msb, lsb);
}
/**
* Return the current time in Gregorian epoch.
*
* The value returned is the count of 100 nanoseconds intervals since
* 1582-10-15T00:00:00.000Z.
*
* @return a number
*/
private static synchronized long getTimestamp() {
long time = System.currentTimeMillis();
long nano = System.nanoTime();
long ticks = 0;
if (time == prevTime) {
// calculate the ticks when the ms is the same
ticks = (nano - prevNano) / TICK;
} else {
// update it when the time changes
prevNano = nano;
}
// calculate the timestamp using the current time plus ticks
long timestamp = ((time - GREGORIAN_MILLISECONDS) * 10_000L) + ticks;
// increment the clock sequence if timestamp is backwards or repeated
if (timestamp <= prevTimestamp) {
clkseq = ++clkseq & 0x3fff;
}
prevTime = time;
prevTimestamp = timestamp;
return timestamp;
}
/**
* Returns a random node identifier.
*
* @return a number
*/
private static synchronized long getNodeIdentifier() {
return (SECURE_RANDOM.nextLong() & 0x0000ffffffffffffL) | 0x010000000000L;
}
/**
* Returns a random clock sequence from 0 to 16383.
*
* @return a number
*/
private static synchronized int getClockSequence() {
return SECURE_RANDOM.nextInt() & 0x3fff;
}
/**
* For tests!
*/
public static void main(String[] args) {
int LOOP_MAX = 100_000;
// ---TEST 1----------------------------------------------
System.out.println();
System.out.println("TEST 1: Listing 5 time-based UUIDs");
System.out.println();
for (int i = 0; i < 5; i++) {
System.out.println(" - " + TimeUuidCreator.getTimeBased());
}
// ---TEST 2----------------------------------------------
System.out.println();
System.out.println("TEST 2: Listing 5 time-ordered UUIDs");
System.out.println();
for (int i = 0; i < 5; i++) {
System.out.println(" - " + TimeUuidCreator.getTimeOrdered());
}
// ---TEST 3----------------------------------------------
System.out.println();
System.out.println(String.format("TEST 3: Generating %s time-based UUIDs", LOOP_MAX));
Set<UUID> list = new HashSet<>();
UUID[] array = new UUID[LOOP_MAX];
System.out.println("Warming up to measure time...");
for (int i = 0; i < 100_000_000; i++) {
TimeUuidCreator.getTimeBased();
}
long a = System.nanoTime();
for (int i = 0; i < LOOP_MAX; i++) {
array[i] = TimeUuidCreator.getTimeBased();
}
long b = System.nanoTime();
int duplicates = 0;
for (int i = 0; i < LOOP_MAX; i++) {
if (!list.add(array[i])) {
duplicates++;
}
}
System.out.println();
System.out.println(" - Elapsed: " + (b - a) / 1000000 + " ms");
System.out.println(" - Unique: " + list.size());
System.out.println(" - Duplicates: " + duplicates);
}
}
//
// TEST 1: Listing 5 time-based UUIDs
//
// - cbd6dabe-162e-11eb-be00-d76078e937d5
// - cbd6df4f-162e-11eb-be00-d76078e937d5
// - cbd6e179-162e-11eb-be00-d76078e937d5
// - cbd6e2f0-162e-11eb-be00-d76078e937d5
// - cbd6e45f-162e-11eb-be00-d76078e937d5
//
// TEST 2: Listing 5 time-ordered UUIDs
//
// - 1eb162ec-bd6e-6800-be00-d76078e937d5
// - 1eb162ec-bd6f-60e0-be00-d76078e937d5
// - 1eb162ec-bd6f-62b2-be00-d76078e937d5
// - 1eb162ec-bd6f-6451-be00-d76078e937d5
// - 1eb162ec-bd6f-65c6-be00-d76078e937d5
//
// TEST 3: Generating 100000 time-based UUIDs
//
// - Elapsed: 11 ms
// - Unique: 100000
// - Duplicates: 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment