cowtowncoder / java-uuid-generator

Java Uuid Generator (JUG) is a Java library for generating all standard UUID versions (v1 - v7)
Apache License 2.0
730 stars 104 forks source link

Microsecond / nanosecond part of time based uuid set even if UUIDClock is millisecond precision #61

Closed djarnis73 closed 1 year ago

djarnis73 commented 1 year ago

Hi

For some legacy stuff that I'm working on, I needed to be able to create UUID's for a given timestamp, so I created the following utility class create time based UUID's and convert them back to Instant:

public class UUIDUtil {
    /**
     * Converts a time based (type 1) uuid into a epoch nanos.
     * Inspiration stolen from https://stackoverflow.com/questions/15179428/how-do-i-extract-a-date-from-a-uuid-using-java
     */
    public static long toEpocNano(UUID uuid) {
        long ts = uuid.timestamp();
        return ((ts / 10l) - 12_219_292_800_000_000l) * 1000;
    }

    /**
     * Converts a time based (type 1) uuid into an Instant.
     */
    public static Instant toInstant(UUID uuid) {
        return Instant.ofEpochSecond(0, toEpocNano(uuid));
    }

    /**
     * Generate a time based UUID that matches the given instant time wise.
     */
    public static UUID timeBased(Instant forInstant) {
        try {
            UUIDTimer timer = new UUIDTimer(new Random(), null, new FixedUUIDClock(forInstant));
            return new TimeBasedGenerator(null, timer).generate();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @RequiredArgsConstructor
    private static class FixedUUIDClock extends UUIDClock {
        final Instant i;

        @Override
        public long currentTimeMillis() {
            return i.toEpochMilli();
        }
    }
}

But while doing some testing of it I discovered that the generated UUID (when converved back to an Instant) has micro/nano-second precision.

Following unit test fails because of the 22 micros:

@Test
public void testNanos() {
    Instant i = Instant.parse("2022-12-21T12:11:16.979Z");
    UUID uuid = UUIDUtil.timeBased(i);
    // 2022-12-21T12:11:16.979Z -> 2022-12-21T12:11:16.979022Z
    assertThat(UUIDUtil.toInstant(uuid)).isEqualTo(i);
}

Am I doing something wrong here or should the generated UUID not have the same precision as the UUIDClock?

cowtowncoder commented 1 year ago

If I remember correctly this is intentional: without generating virtual 100 nsec units it would not be possible to generate millions of UUIDs per second. So the 100nsec units use basically use counter from 0 to 9999. So this sounds like intended behavior.

If you want only actual resolution you would need to mask that part of timestamp.

djarnis73 commented 1 year ago

Ok, I will find a way to work around the issue, thanks for the explanation.