Ultimate Guide about my best practices managing Date & Time in Java project.

You can jump straight to conclusion with practical recommendations.

Prequel

Java actually had rough time with time. Okay, jokes aside Java really had troubles with Dates especially with Dates APIs. You can ask many people who have experience with Java 7 or less, not surprising Joda Time even have its own website, great documentation, developers love and almost 5k starts on GitHub.

I won’t dig into details why old API is poor or into Joda Time, thanks Java 8 we don’t have such problems anymore.

Java 8+ have java.time package with a lot of useful and Joda Time inspired Date & Time APIs in standard library.

In this article I want to focus on Date & Times formats for java.time package APIs only, what is way to go format from my point of view and little details about Date & Times in general. You most likely won’t find a lot of new information been Senior Developer, but who knows.

What is the problem?

Actually, if standard DateTimeFormatter and standard ISO formatters are working for you and there is no need for supporting old API, then great, no problems for you.

If not then lets dig into it.

We have java.time APIs, what’s the problem?

Problem is how to use Date & Time APIs, cause dates have formats as we know and as a developer we are faced with one of crucial architectural decision starting new project, that’s Date & Time format we choose. Sometimes we also forced to support old Java Date & Time API like Timestamp or java.util.Date, so we want consistency across both worlds.

Most likely our decision made on start will stick with us for quite a time if even not the whole project lifecycle. You won’t let it go easily or management and support of such decision may be quite expensive in the future.

Format Criteria

First things first, lets point what are criteria and use cases for Date & Times:

  1. Date & Time should meet some kind of widely used standard.
  2. Date & Time should be supported by different languages, frameworks, databases, etc.
  3. Date & Time should be precise.
  4. Date & Time should be sortable.
  5. Date & Time should be human-readable and easily understandable.

There a lot more, here a lot of useful tips for Date & Time management from community.

Format Standards

Most used and widely spread is ISO8601 standard and that is no coincidence.

Due to proper order of Date & Time fields dates are sortable:

  • YYYY-MM-DD - first sort by year, then by month, then by day (just compare to American format MM/DD/YYYY or EU DD.MM.YYYY)
  • Widely used standard.
  • Supported by many frameworks, languages and other tools.
  • Human-readable.

Not that precise, but still up to seconds. Great. We are using Java and want to use such format, other formats lack one or other and not suitable.

What about Java?

We knew what to use in general, that’s ISO8601 standard, but what about Java?

If we try to look for formats to use (lets think that we forget about standard Java ISO formatters library provides for now) we will find something like this, this, this, this and this.

Old, but Gold.

Here we have some mix of formats like:

  • yyyy-MM-dd’T’HH:mm:ss.SSS’Z'
  • yyyy-MM-dd’T’HH:mm:ssZ
  • yyyy-MM-ddThh:mm:ssZ
  • yyyy-MM-dd’T’HH:mm:ss.SSSXXX
  • yyyy-MM-dd’T’HH:mm:ssXXX
  • yyyy-MM-dd'T'HH:mm:ss.SSS Z

Doesn’t look that standard, quite a mess, especially for a newcomer.

If we think a bit, compare what we have to ISO8601 standard we will come up with candidates:

  1. yyyy-MM-dd’T’HH:mm:ssXXX
  2. yyyy-MM-dd’T’HH:mm:ssZ

The Devil is in the Details

Offset

We have to similar candidates, main difference is in time zone, lets compare parsed datetimes:

  1. 2021-11-17T11:45:50+03:00
  2. 2021-11-17T11:45:50+0300

In second case parsing ZonedDateTime will fail with something like this:

java.time.format.DateTimeParseException: Text '2021-11-17T11:45:50+0300' could not be parsed, unparsed text found at index 13

Also, ISO-8601 requires one of the following:

  • ±HH:mm if any of HH or mm is non-zero
  • Z to designate UTC

Take a look at UTC offset:

  1. 2021-11-17T11:45:50Z
  2. 2021-11-17T11:45:50+0000
java.time.format.DateTimeParseException: Text '2021-11-17T11:45:50+0000' could not be parsed at index 13

Candidate 2 is retires.

Format so far:

  • yyyy-MM-dd’T’HH:mm:ssXXX

Year of Era

Problem with yyyy is that this is Year of Era according to official documentation.

Symbol  Meaning                     Presentation      Examples
------  -------                     ------------      -------
G       era                         text              AD; Anno Domini; A
u       year                        year              2004; 04
y       year-of-era                 year              2004; 04

What does this mean exactly?

This mean that using strict validation format should include AD\BC era, which is not compliant with ISO8601

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
        .withResolverStyle(ResolverStyle.STRICT);

LocalDate localDate = LocalDate.parse("2020-08-24", formatter);

And result is:

java.time.format.DateTimeParseException: Text '2020-08-24' could not be parsed: Unable to obtain LocalDate from TemporalAccessor: {MonthOfYear=8, DayOfMonth=24, YearOfEra=2020},ISO of type java.time.format.Parsed

We should stick with uuuu for year when using java.time.* package Date & Times formatters.

Format so far:

  • uuuu-MM-dd’T’HH:mm:ssXXX

Precision

Precision up to seconds is good, but there are a lot of events in second, and you would like more precision.

Long story short, Unix Time format which is seconds since Epoch may be also in milliseconds precision. Same story here, but to support compatability with default ISO8601.

  • uuuu-MM-dd’T’HH:mm:ss[.SSS]XXX

If you are quite precise then you try microseconds:

  • uuuu-MM-dd’T’HH:mm:ss[.SSSSSS]XXX

As for me, I would stick with milliseconds as with analogy with Unix Time:

  • uuuu-MM-dd’T’HH:mm:ss[.SSS]XXX

In this case you can safely parse default ISO8601 input like:

1970-01-01T03:00:12+03:00

However, formatted output will be always with millis precision:

1970-01-01T03:00:12.000+03:00

Comparison with Java ISO formatters

Basically final format is standard Java ISO formatter with millis precision, but now you know all little details about java.time.* formatters.

Comparison:

API ISO with millis Java ISO
LocalDateTime uuuu-MM-dd’T’HH:mm:ss[.SSS] uuuu-MM-dd’T’HH:mm:ss
LocalDate uuuu-MM-dd uuuu-MM-dd
LocalTime HH:mm:ss[.SSS] HH:mm:ss
OffsetDateTime uuuu-MM-dd’T’HH:mm:ss[.SSS]XXX uuuu-MM-dd’T’HH:mm:ssXXX
OffsetTime HH:mm:ss[.SSS]XXX HH:mm:ssXXX
ZonedDateTime uuuu-MM-dd’T’HH:mm:ss[.SSS]XXX['[‘VV’]'] uuuu-MM-dd’T’HH:mm:ssXXX['[‘VV’]']

Conclusion

ISO8601 format with millis is the only format you will probably need, as unified Date & Time language for all backend services.

Summing up, final format is:

API Formatter
LocalDateTime uuuu-MM-dd’T’HH:mm:ss[.SSS]
LocalDate uuuu-MM-dd
LocalTime HH:mm:ss[.SSS]
OffsetDateTime uuuu-MM-dd’T’HH:mm:ss[.SSS]XXX
OffsetTime HH:mm:ss[.SSS]XXX
ZonedDateTime uuuu-MM-dd’T’HH:mm:ss[.SSS]XXX['[‘VV’]']
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
        .appendPattern("uuuu-MM-dd'T'HH:mm:ss[.SSS]XXX")
        .toFormatter()
        .withResolverStyle(ResolverStyle.STRICT)  // maximum validation out of formatter
        .withChronology(IsoChronology.INSTANCE);  // ISO Chronology

Java Old API formatter (avoid using old API like Date with all possible ways):

  • yyyy-MM-dd’T’HH:mm:ss.SSSXXX
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");

You can use Gson or Jackson library that goes with such formatters by default.