Search code examples
javascalatimecassandratimeuuid

get timebased uuid's upto 100s of nanoseconds


I am using this libraryDependencies += "com.datastax.oss" % "java-driver-core" % "4.3.0" library for creating time based uuid's. Although it generates time based uuid's it gives me upto seconds but i am looking for values in 100s of nano seconds

import com.datastax.oss.driver.api.core.uuid.Uuids
println(Uuids.timeBased().toString)

The output uuid is something like f642f350-0230-11ea-a02f-597f2801796a which corresponds to Friday, November 8, 2019 at 2:06:30 PM Greenwich Mean Time

Please help on how to get time in milliseconds seomething like this Friday, November 8, 2019 at 2:06:30:0000000Z PM Greenwich Mean Time

I want timestamp to be converted in uuid format for testing(tests accept only uuid format). i would then convert uuid back to time to measure some time difference.


Solution

  • There are a few steps here. The first is to convert the time-based UUID's timestamp (which is in 100s of nanoseconds from October 15, 1582) to one compatible with Java's date functionality (i.e. milliseconds from January 1, 1970). Notably, you asked for better than millisecond precision.

    Next, we need to interpret that date into the correct time zone.

    Finally, we need to format it into text in the desired format.

    Here's the code:

    // this is the difference between midnight October 15, 1582 UTC and midnight January 1, 1970 UTC as 100 nanosecond units
    private static final long EPOCH_DIFFERENCE = 122192928000000000L;
    
    private static final ZoneId GREENWICH_MEAN_TIME = ZoneId.of("GMT");
    
    private static final DateTimeFormatter FORMATTER = new DateTimeFormatterBuilder()
            .appendText(DAY_OF_WEEK, FULL)
            .appendLiteral(", ")
            .appendText(MONTH_OF_YEAR, FULL)
            .appendLiteral(' ')
            .appendValue(DAY_OF_MONTH)
            .appendLiteral(", ")
            .appendValue(YEAR, 4)
            .appendLiteral(" at ")
            .appendValue(CLOCK_HOUR_OF_AMPM)
            .appendLiteral(':')
            .appendValue(MINUTE_OF_HOUR, 2)
            .appendLiteral(':')
            .appendValue(SECOND_OF_MINUTE, 2)
            .appendLiteral('.')
            .appendFraction(NANO_OF_SECOND, 7, 7, false)
            .appendLiteral(' ')
            .appendText(AMPM_OF_DAY)
            .appendLiteral(' ')
            .appendZoneText(FULL)
            .toFormatter(Locale.getDefault());
    
    public static String formattedDateFromTimeBasedUuid(UUID uuid) {
        ZonedDateTime date = timeBasedUuidToDate(uuid);
        return FORMATTER.format(date);
    }
    
    public static ZonedDateTime timeBasedUuidToDate(UUID uuid) {
        if (uuid.version() != 1) {
            throw new IllegalArgumentException("Provided UUID was not time-based.");
        }
        // the UUID timestamp is in 100 nanosecond units.
        // convert that to nanoseconds
        long nanoseconds = (uuid.timestamp() - EPOCH_DIFFERENCE) * 100;
        long milliseconds = nanoseconds / 1000000000;
        long nanoAdjustment = nanoseconds % 1000000000;
        Instant instant = Instant.ofEpochSecond(milliseconds, nanoAdjustment);
        return ZonedDateTime.ofInstant(instant, GREENWICH_MEAN_TIME);
    }
    

    I'd drop those methods and constants in a utility class for convenient reuse.

    A couple notes:

    • Lots of statically imported constants here. They come from java.time.format.TextStyle and java.time.temporal.ChronoField.
    • I used DateTimeFormatterBuilder instead of the more common DateTimeFormatter.forPattern(String). I find it more readable and am willing to tolerate the resulting verbosity.
    • I tweaked one thing about your desired format: you asked for the time to be 2:06:30:001; this code yields 2:06:30.001 -- a decimal point between the seconds and milliseconds, rather than a colon. This is more correct, but if you prefer the colon, just change the corresponding .appendLiteral('.') to pass a colon instead.
    • You'll often find example code that defines DateTimeFormatters, ZoneIds, etc inline. These classes are thread-safe and reusable, so for best results, you should define them as constants, as I have done here. You'll get better performance and reduced memory usage.
    • Note that the DataStax driver's Uuids class uses the system clock's millisecond-precision value as input, so you're just going to see zeros in the last four positions, unless you implement your own nanosecond-based variant. You could do that using System.nanoTime(), but there are some complications -- check out the note on the JavaDoc for more.

    To determine the amount of time between two ZonedDateTimes, you just do this:

    Duration duration = Duration.between(date1, date2);
    

    The Duration class has several useful methods you can use to interpret the result.