f4b6a3 / uuid-creator

UUID Creator is a Java library for generating Universally Unique Identifiers.
MIT License
410 stars 44 forks source link

Dates before the Epoch should not be supported #76

Closed efenderbosch closed 1 year ago

efenderbosch commented 1 year ago

All values of (Epoch - 9999 ms) through the Epoch generate the same UUID, given the same "random" supplier.

At (Epoch - 10,000 ms), it wraps around back to the max encodable date of +10889-08-02T05:31:50.655Z.

Test code to show behavior:

    @Test
    public void uuid() {
        final var utc = ZoneId.of("UTC");
        final var min = TimeOrderedEpochFactory.builder()
                .withClock(Clock.fixed(Instant.ofEpochMilli(Long.MIN_VALUE), utc))
                .withRandomFunction(() -> Long.MIN_VALUE)
                .build()
                .create();

        final var minPlusOne = TimeOrderedEpochFactory.builder()
                .withClock(Clock.fixed(Instant.ofEpochMilli(Long.MIN_VALUE + 1), utc))
                .withRandomFunction(() -> Long.MIN_VALUE)
                .build()
                .create();

        final var epochMinusOne = TimeOrderedEpochFactory.builder()
                .withClock(Clock.fixed(Instant.EPOCH.minusMillis(1), utc))
                .withRandomFunction(() -> 0)
                .build()
                .create();

        final var epoch = TimeOrderedEpochFactory.builder()
                .withClock(Clock.fixed(Instant.EPOCH, ZoneId.of("UTC")))
                .withRandomFunction(() -> 0)
                .build()
                .create();

        final var epochPlusOne = TimeOrderedEpochFactory.builder()
                .withClock(Clock.fixed(Instant.EPOCH.plusMillis(1), utc))
                .withRandomFunction(() -> 0)
                .build()
                .create();

        final var maxMinusOne = TimeOrderedEpochFactory.builder()
                .withClock(Clock.fixed(Instant.ofEpochMilli(Long.MAX_VALUE - 1), utc))
                .withRandomFunction(() -> Long.MAX_VALUE)
                .build()
                .create();

        final var max = TimeOrderedEpochFactory.builder()
                .withClock(Clock.fixed(Instant.ofEpochMilli(Long.MAX_VALUE), utc))
                .withRandomFunction(() -> Long.MAX_VALUE)
                .build()
                .create();

        System.out.println("min      : " + min + " " + UuidUtil.getInstant(min));
        System.out.println("min + 1  : " + minPlusOne + " " + UuidUtil.getInstant(minPlusOne));
        System.out.println("epoch - 1: " + epochMinusOne + " " + UuidUtil.getInstant(epochMinusOne));
        System.out.println("epoch    : " + epoch + " " + UuidUtil.getInstant(epoch));
        System.out.println("epoch + 1: " + epochPlusOne + " " + UuidUtil.getInstant(epochPlusOne));
        System.out.println("max - 1  : " + maxMinusOne + " " + UuidUtil.getInstant(maxMinusOne));
        System.out.println("max      : " + max + " " + UuidUtil.getInstant(max));

        final var uuids = new TreeSet<UUID>();
        for (int i = 0; i <= 10_000; i++) {
            final var uuid = TimeOrderedEpochFactory.builder()
                    .withClock(Clock.fixed(Instant.EPOCH.minusMillis(i), utc))
                    .withRandomFunction(() -> 0)
                    .build()
                    .create();
            uuids.add(uuid);
        }
        uuids.forEach(uuid -> System.out.println(uuid + " " + UuidUtil.getInstant(uuid)));
    }

And the output:

min      : 00000000-0000-7000-8000-000000000000 1970-01-01T00:00:00Z
min + 1  : 00000000-0001-7000-8000-000000000000 1970-01-01T00:00:00.001Z
epoch - 1: 00000000-0000-7000-8001-000000000000 1970-01-01T00:00:00Z
epoch    : 00000000-0000-7000-8001-000000000000 1970-01-01T00:00:00Z
epoch + 1: 00000000-0001-7000-8000-000000000000 1970-01-01T00:00:00.001Z
max - 1  : ffffffff-fffe-7fff-bfff-ffffffffffff +10889-08-02T05:31:50.654Z
max      : ffffffff-ffff-7fff-bfff-ffffffffffff +10889-08-02T05:31:50.655Z
ffffffff-d8f0-7000-8000-000000000000 +10889-08-02T05:31:40.656Z
00000000-0000-7000-8001-000000000000 1970-01-01T00:00:00Z

I think a reasonable solution would be for a fixed clock to throw an IllegalArgumentException if the time is before the Epoch.

fabiolimace commented 1 year ago

Hi @efenderbosch

I made a mistake initializing a field of TimeOrderedEpochFactory. It was supposed to be initialized with the current time, but it was being initialized with zero. This caused the odd behaviour with fixed negative timestamps. I fixed it.

A also used your example to make sure this problem doesn't come back.

Thank you for reporting this.

Best regards

fabiolimace commented 1 year ago

Released v5.2.1. 🎉