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.