What pattern should be used to parse RFC 3339 datetime strings in java

alexgophermix picture alexgophermix · Nov 1, 2016 · Viewed 14.6k times · Source

This seems to be a common question with many different answers. Before you answer, I have used both joda-time and atomdate and they work great. My interest here is not what library to use but instead a clarification on how RFC pattern should be defined in java.


Research

From my understanding and this answer RFC 3339 is a profile of ISO 8601. PHP clearly defines the RFC 3339 datetime pattern to be Y-m-d\TH:i:sP. If we were to transfer this definition to java 7 (to my knowledge) we would end up with this (which is also referred to in this answer):

// example "2005-08-15T15:52:01+00:00"
pattern = "yyyy-MM-dd'T'HH:mm:ssXXX";

However, several stack overflow answers like this one point to one of these (or both) as being the correct pattern for RFC 3339

// example "2016-11-01T20:44:39Z"
pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'";

// example "1937-01-01T12:00:27.87Z"
pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";

To further complicate matters the official RFC 3339 documentation lists all of these following examples (I've added in what I think would be their corresponding patterns):

// 1996-12-19T16:39:57-08:00
pattern = "yyyy-MM-dd'T'HH:mm:ssXXX";

// 1990-12-31T23:59:60Z
pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'";

// 1990-12-31T15:59:60-08:00
pattern = "yyyy-MM-dd'T'HH:mm:ssXXX";

// 1937-01-01T12:00:27.87+00:20
pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";

Side note: Android does not support the XXX pattern for timezones but you can use ZZZZZ instead as per this answer.

I think part of what's confusing me is I've always seen RFC 822 and RFC 2822 specifically referred to by one pattern each, so I assumed RFC 3339 could also be boiled down to a single pattern match:

static String RFC_822 = "EEE, dd MMM yy HH:mm:ss zzz";
static String RFC_2822 = "EEE, dd MMM yyyy HH:mm:ss zzz";

My Conclusion

Unlike in php, RFC 3339 cannot be represented in java using only a single matching expression. Instead all of these are valid RFC 3339 patterns and must be checked when parsing a datetime String via SimpleDateFormat:

static String[] RFC_3339_VARIANTS = {
        "yyyy-MM-dd'T'HH:mm:ss'Z'",
        "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
        "yyyy-MM-dd'T'HH:mm:ssXXX",
        "yyyy-MM-dd'T'HH:mm:ss.SSSXXX"
};

Update

To complicate matters SimpleDateFormat does not seem to correctly handle the 'Z' timezone literal. Instead of assuming UTC as it should, it defaults to either PST or your local time (I'm not sure which). That means you might need to manually replace 'Z' literals with +00:00 to correct this behaviour?


Gist

As suggested I have created a utility class Gist which includes my currently running code. This should run on Android and also be compatible with Java 7+. Please feel free to ask any questions or leave comments. If there's enough interest I can move it over to Github so other people can contribute:

https://gist.github.com/oseparovic/d9ee771927ac5f3aefc8ba0b99c0cf38


Am I understanding this correctly or am I completely off? I would really appreciate any clarification you guys can offer about how to parse RFC 3339 strings in java 7.

Answer

Adam Gent picture Adam Gent · Jun 5, 2018

You basically almost answered your own question except that even your gist is not correct for all cases... that is it requires even more patterns than the two you have (e.g. to deal with nano seconds).

And this is why Joda and Java 8 have special parsers for ISO 8601 (a superset).

I know you don't need references to other libraries but for others that are using Java 8 and or want to explicitly limit to RFC 3339 (the joda iso parsers I believe will take even more formats than rfc 3339) there is this library: https://github.com/ethlo/itu