98

I am trying to parse a date that looks like this:

2010-04-05T17:16:00Z

This is a valid date per http://www.ietf.org/rfc/rfc3339.txt. The 'Z' literal (quote) "imply that UTC is the preferred reference point for the specified time."

If I try to parse it using SimpleDateFormat and this pattern:

yyyy-MM-dd'T'HH:mm:ss

it will be parsed as a Mon Apr 05 17:16:00 EDT 2010


SimpleDateFormat is unable to parse the string with these patterns:

yyyy-MM-dd'T'HH:mm:ssz
yyyy-MM-dd'T'HH:mm:ssZ

I can explicitly set the TimeZone to use on the SimpleDateFormat to get the expected output, but I don't think that should be necessary. Is there something I am missing? Is there an alternative date parser?

informatik01
  • 16,038
  • 10
  • 74
  • 104
DanInDC
  • 5,019
  • 8
  • 31
  • 25
  • 12
    The 'Z' suffix in the time stamp should not be confused with the Z or z in the pattern. In java 7 you can parse an ISO8601 suffix with SimpleDateFormat using the 'X' pattern letter. – Dave Patterson Jun 24 '14 at 18:53
  • FYI, the troublesome old date-time classes such as `SimpleTextFormat` are now [legacy](https://en.wikipedia.org/wiki/Legacy_system), supplanted by the [java.time](https://docs.oracle.com/javase/8/docs/api/java/time/package-summary.html) classes. See [Tutorial by Oracle](https://docs.oracle.com/javase/tutorial/datetime/TOC.html). – Basil Bourque Aug 29 '17 at 00:29
  • [`'Z'` is not the same as `Z`](https://stackoverflow.com/a/67953075/10819573) – Arvind Kumar Avinash Jun 13 '21 at 18:21

12 Answers12

69

The date you are parsing is in ISO 8601 format.

In Java 7 the pattern to read and apply the timezone suffix should read yyyy-MM-dd'T'HH:mm:ssX

informatik01
  • 16,038
  • 10
  • 74
  • 104
Dave Patterson
  • 1,357
  • 1
  • 11
  • 16
  • 14
    This should have been the correct answer instead of accepted answer. – Ejaz Ahmed Jul 27 '16 at 12:59
  • 1
    Also, if you want to parse the minutes in the timezone, simply add a second or third `X`: `yyyy-MM-dd'T'HH:mm:ssXX` or `yyyy-MM-dd'T'HH:mm:ssXXX`. More details see [SimpleDateFormat - ISO 8601 Time zone](https://docs.oracle.com/javase/8/docs/api/java/text/SimpleDateFormat.html#iso8601timezone). – mateuscb May 05 '17 at 14:07
  • This should be the most pertinent answer than any other highly voted answers for the question – Prasanth Rajendran Nov 25 '20 at 11:56
64

Java doesn't parse ISO dates correctly.

Similar to McKenzie's answer.

Just fix the Z before parsing.

Code

String string = "2013-03-05T18:05:05.000Z";
String defaultTimezone = TimeZone.getDefault().getID();
Date date = (new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ")).parse(string.replaceAll("Z$", "+0000"));

System.out.println("string: " + string);
System.out.println("defaultTimezone: " + defaultTimezone);
System.out.println("date: " + (new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ")).format(date));

Result

string: 2013-03-05T18:05:05.000Z
defaultTimezone: America/New_York
date: 2013-03-05T13:05:05.000-0500
Alex
  • 5,909
  • 2
  • 35
  • 25
44

tl;dr

Instant.parse ( "2010-04-05T17:16:00Z" )

ISO 8601 Standard

Your String complies with the ISO 8601 standard (of which the mentioned RFC 3339 is a profile).

Avoid java.util.Date

The java.util.Date and .Calendar classes bundled with Java are notoriously troublesome. Avoid them.

Instead use either the Joda-Time library or the new java.time package in Java 8. Both use ISO 8601 as their defaults for parsing and generating string representations of date-time values.

java.time

The java.time framework built into Java 8 and later supplants the troublesome old java.util.Date/.Calendar classes. The new classes are inspired by the highly successful Joda-Time framework, intended as its successor, similar in concept but re-architected. Defined by JSR 310. Extended by the ThreeTen-Extra project. See the Tutorial.

The Instant class in java.time represents a moment on the timeline in UTC time zone.

The Z at the end of your input string means Zulu which stands for UTC. Such a string can be directly parsed by the Instant class, with no need to specify a formatter.

String input = "2010-04-05T17:16:00Z";
Instant instant = Instant.parse ( input );

Dump to console.

System.out.println ( "instant: " + instant );

instant: 2010-04-05T17:16:00Z

From there you can apply a time zone (ZoneId) to adjust this Instant into a ZonedDateTime. Search Stack Overflow for discussion and examples.

If you must use a java.util.Date object, you can convert by calling the new conversion methods added to the old classes such as the static method java.util.Date.from( Instant ).

java.util.Date date = java.util.Date.from( instant );

Joda-Time

Example in Joda-Time 2.5.

DateTimeZone timeZone = DateTimeZone.forID( "Europe/Paris" ):
DateTime dateTime = new DateTime( "2010-04-05T17:16:00Z", timeZone );

Convert to UTC.

DateTime dateTimeUtc = dateTime.withZone( DateTimeZone.UTC );

Convert to a java.util.Date if necessary.

java.util.Date date = dateTime.toDate();
informatik01
  • 16,038
  • 10
  • 74
  • 104
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • Please mind this article by Stephen Colebourne: [Why JSR-310 isn't Joda-Time](http://blog.joda.org/2009/11/why-jsr-310-isn-joda-time_4941.html) – JJD Mar 24 '16 at 14:05
33

In the pattern, the inclusion of a 'z' date-time component indicates that timezone format needs to conform to the General time zone "standard", examples of which are Pacific Standard Time; PST; GMT-08:00.

A 'Z' indicates that the timezone conforms to the RFC 822 time zone standard, e.g. -0800.

I think you need a DatatypeConverter ...

@Test
public void testTimezoneIsGreenwichMeanTime() throws ParseException {
    final Calendar calendar = javax.xml.bind.DatatypeConverter.parseDateTime("2010-04-05T17:16:00Z");
    TestCase.assertEquals("gotten timezone", "GMT+00:00", calendar.getTimeZone().getID());
}
Paul McKenzie
  • 19,646
  • 25
  • 76
  • 120
25

According to last row on the Date and Time Patterns table of the Java 7 API

X Time zone ISO 8601 time zone -08; -0800; -08:00

For ISO 8601 time zone you should use:

  • X for (-08 or Z),
  • XX for (-0800 or Z),
  • XXX for (-08:00 or Z);

so to parse your "2010-04-05T17:16:00Z" you can use either "yyyy-MM-dd'T'HH:mm:ssX" or "yyyy-MM-dd'T'HH:mm:ssXX" or "yyyy-MM-dd'T'HH:mm:ssXXX" .

    System.out.println(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX").parse("2010-04-05T17:16:00Z"));
    System.out.println(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXX").parse("2010-04-05T17:16:00Z"));
    System.out.println(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX").parse("2010-04-05T17:16:00Z"));

will correctly print out 'Mon Apr 05 13:16:00 EDT 2010'

Legna
  • 1,632
  • 15
  • 15
  • 1
    This should be the accepted answer, because it shows how to parse the date string using SimpleDateFormat like the question asked. – davidgyoung Dec 21 '17 at 16:38
7

The 'X' only works if partial seconds are not present: i.e. SimpleDateFormat pattern of

"yyyy-MM-dd'T'HH:mm:ssX"

Will correctly parse

"2008-01-31T00:00:00Z"

but

"yyyy-MM-dd'T'HH:mm:ss.SX"

Will NOT parse

"2008-01-31T00:00:00.000Z"

Sad but true, a date-time with partial seconds does not appear to be a valid ISO date: http://en.wikipedia.org/wiki/ISO_8601

  • 2
    This is terribly annoying. – Daniel F Jul 20 '15 at 14:02
  • Quote from referenced wiki page: "Decimal fractions may be added to any of the three time elements. [...] There is no limit on the number of decimal places for the decimal fraction. However, the number of decimal places needs to be agreed to by the communicating parties." The "Z" is an independent notion for time zones. So partial seconds ARE valid ISO dates. It would be nice to parse. (Maybe it is not a "Simple Date Format"? ;) ) – MGM Dec 03 '15 at 23:23
6

Under Java 8 use the predefined DateTimeFormatter.ISO_DATE_TIME

 DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME;
 ZonedDateTime result = ZonedDateTime.parse("2010-04-05T17:16:00Z", formatter);

I guess its the easiest way

JavaBohne
  • 171
  • 2
  • 4
4

The time zone should be something like "GMT+00:00" or 0000 in order to be properly parsed by the SimpleDateFormat - you can replace Z with this construction.

Cornel Creanga
  • 5,311
  • 1
  • 22
  • 28
3

Since java 8 just use ZonedDateTime.parse("2010-04-05T17:16:00Z")

2

The restlet project includes an InternetDateFormat class that can parse RFC 3339 dates.

Restlet InternetDateFormat

Though, you might just want to replace the trailing 'Z' with "UTC" before you parse it.

Marcus Adams
  • 53,009
  • 9
  • 91
  • 143
2

I provide another answer that I found by api-client-library by Google

try {
    DateTime dateTime = DateTime.parseRfc3339(date);
    dateTime = new DateTime(new Date(dateTime.getValue()), TimeZone.getDefault());
    long timestamp = dateTime.getValue();  // get date in timestamp
    int timeZone = dateTime.getTimeZoneShift();  // get timezone offset
} catch (NumberFormatException e) {
    e.printStackTrace();
}

Installation guide,
https://developers.google.com/api-client-library/java/google-api-java-client/setup#download

Here is API reference,
https://developers.google.com/api-client-library/java/google-http-java-client/reference/1.20.0/com/google/api/client/util/DateTime

Source code of DateTime Class,
https://github.com/google/google-http-java-client/blob/master/google-http-client/src/main/java/com/google/api/client/util/DateTime.java

DateTime unit tests,
https://github.com/google/google-http-java-client/blob/master/google-http-client/src/test/java/com/google/api/client/util/DateTimeTest.java#L121

Johnny
  • 1,824
  • 23
  • 16
2

With regards to JSR-310 another project of interest might be threetenbp.

JSR-310 provides a new date and time library for Java SE 8. This project is the backport to Java SE 6 and 7.

In case you are working on an Android project you might want to checkout the ThreeTenABP library.

compile "com.jakewharton.threetenabp:threetenabp:${version}"

JSR-310 was included in Java 8 as the java.time.* package. It is a full replacement for the ailing Date and Calendar APIs in both Java and Android. JSR-310 was backported to Java 6 by its creator, Stephen Colebourne, from which this library is adapted.

JJD
  • 50,076
  • 60
  • 203
  • 339