Monday, February 18, 2019

Java date-time timezone formats

Java has excellent date-time formatting with the arrival of java.time (JSR310) in Java 8. I point out that release as it came with a usable, safe API. (Let us never speak of Calendar again).

However, I never recall how to format timezone. There are so many options, and it is easy to get is "almost right", but not exactly right.

Problem

I'd like to append a "Z" character on the end of a UTC timestamp. OK, let's look at the options, showing only those for timezone/offset:

Symbol Meaning Presentation Examples
V time-zone ID zone-id America/Los_Angeles; Z; -08:30
v generic time-zone name zone-name Pacific Time; PT
z time-zone name zone-name Pacific Standard Time; PST
O localized zone-offset offset-O GMT+8; GMT+08:00; UTC-08:00
X zone-offset 'Z' for zero offset-X Z; -08; -0830; -08:30; -083015; -08:30:15
x zone-offset offset-x +0000; -08; -0830; -08:30; -083015; -08:30:15
Z zone-offset offset-Z +0000; -0800; -08:00

One thing to be wary of: formatting characters can be doubled, tripled, or quadrupled, and it changes the result. Further, some characters have special rules on repeating (eg, "VV", and "O" vs "OOOO").

The best way to understand what to use is to try them all:

final var when = ZonedDateTime.of(
        LocalDate.of(2011, 2, 3),
        LocalTime.of(14, 5, 6, 7_000_000),
        ZoneId.of("UTC"))
        .toInstant();
for (final String tzFormat
        : List.of("VV", "v", "z", "zz", "zzz", "zzzz", "O", "OOOO", "X", "XX", "XXX",
        "XXXX", "x", "xx", "xxx", "xxxx", "Z", "ZZ", "ZZZ", "ZZZZ")) {
    System.out.println(
            tzFormat + " - " + DateTimeFormatter
                    .ofPattern("yyyy-MM-dd'T'HH:mm:ss" + tzFormat)
                    .withZone(ZoneId.of("UTC"))
                    .format(when));
}

Producing:

VV - 2011-02-03T14:05:06UTC
v - 2011-02-03T14:05:06UTC
z - 2011-02-03T14:05:06UTC
zz - 2011-02-03T14:05:06UTC
zzz - 2011-02-03T14:05:06UTC
zzzz - 2011-02-03T14:05:06Coordinated Universal Time
O - 2011-02-03T14:05:06GMT
OOOO - 2011-02-03T14:05:06GMT
X - 2011-02-03T14:05:06Z
XX - 2011-02-03T14:05:06Z
XXX - 2011-02-03T14:05:06Z
XXXX - 2011-02-03T14:05:06Z
x - 2011-02-03T14:05:06+00
xx - 2011-02-03T14:05:06+0000
xxx - 2011-02-03T14:05:06+00:00
xxxx - 2011-02-03T14:05:06+0000
Z - 2011-02-03T14:05:06+0000
ZZ - 2011-02-03T14:05:06+0000
ZZZ - 2011-02-03T14:05:06+0000
ZZZZ - 2011-02-03T14:05:06GMT

What an exciting list! "zzzz" is rather wordy, and it's unclear what "ZZZZ" is doing. Actually, the whole list is even more iteresting for timezones other than UTC.

Solution

Since the goal is to append a "Z", the simplest choice is: yyyy-MM-dd'T'HH:mm:ssX.

Addendum

Why didn't I just use DateTimeFormatter.ISO_INSTANT, which is documented to produce the "Z"? I want a timestamp that is to only seconds-precision, and the format for "ISO_INSTANT" includes milliseconds.