798

I am trying to convert an ISO 8601 formatted String to a java.util.Date.

I found the pattern yyyy-MM-dd'T'HH:mm:ssZ to be ISO8601-compliant if used with a Locale (compare sample).

However, using the java.text.SimpleDateFormat, I cannot convert the correctly formatted String 2010-01-01T12:00:00+01:00. I have to convert it first to 2010-01-01T12:00:00+0100, without the colon.

So, the current solution is

SimpleDateFormat ISO8601DATEFORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.GERMANY);
String date = "2010-01-01T12:00:00+01:00".replaceAll("\\+0([0-9]){1}\\:00", "+0$100");
System.out.println(ISO8601DATEFORMAT.parse(date));

which obviously isn't that nice. Am I missing something or is there a better solution?


Answer

Thanks to JuanZe's comment, I found the Joda-Time magic, it is also described here.

So, the solution is

DateTimeFormatter parser2 = ISODateTimeFormat.dateTimeNoMillis();
String jtdate = "2010-01-01T12:00:00+01:00";
System.out.println(parser2.parseDateTime(jtdate));

Or more simply, use the default parser via the constructor:

DateTime dt = new DateTime( "2010-01-01T12:00:00+01:00" ) ;

To me, this is nice.

wittich
  • 2,079
  • 2
  • 27
  • 50
Ice09
  • 8,951
  • 4
  • 23
  • 25
  • 281
    Be ready to receive a lot of "Use JodaTime" answers... – JuanZe Feb 04 '10 at 17:54
  • 3
    @Ice09: If the API documentation for DateTimeFormat is correct (the JoDa documentation can be misleading, wrong or incomplete though), the pattern you've used in your own "answer" is not compatible with ISO8601. – jarnbjo Feb 04 '10 at 22:47
  • @jarnbjo: thanks for the comment - you might be right, I looked it up again and came up with the much better solution ISODateTimeFormat.dateTimeNoMillis(), which kind of guarantees that the correct format is used. – Ice09 Feb 05 '10 at 10:13
  • See: http://stackoverflow.com/questions/1554852/parsing-a-date-string-using-java-text-simpledateformat and http://stackoverflow.com/questions/2580925/simpledateformat-parsing and http://stackoverflow.com/questions/4013681/parse-this-type-of-date-format-in-java-date-with-z-literal and – andersoj Nov 03 '10 at 15:47
  • 24
    I'm not sure when this was added, but the 'X' appears to solve this problem within SimpleDateFormat. The pattern "yyyy-MM-dd'T'HH:mm:ssX" successfully parses the example in the question. – mlohbihler Jul 18 '12 at 19:20
  • 12
    The 'X' is available since Java 7. – Lars Grammel Oct 03 '12 at 17:58
  • 1
    The embedded answer with [Joda-Time](http://www.joda.org/joda-time/) can be much shorter, a single line of code, no need to call parse method: `new DateTime( "2010-01-01T12:00:00+01:00" )`. See [my answer](http://stackoverflow.com/a/20578792/642706) for details. – Basil Bourque Dec 14 '13 at 02:14
  • I found an answer that worked for me on [this particular answer][1]. [1]: http://stackoverflow.com/a/10615059/2413303 – EpicPandaForce Jul 06 '15 at 11:59
  • 4
    Java 8 makes it easy! There is a hidden gem by Adam in the answers below: http://stackoverflow.com/a/27479533/1262901 – Fabian Kleiser Apr 27 '16 at 11:59
  • Joda DateTime didn't support 20190531T194819Z(yyyyMMdd'T'HHmmssSSS'Z') example from https://en.wikipedia.org/wiki/ISO_8601 and hence added the solution below: https://stackoverflow.com/questions/2201925/converting-iso-8601-compliant-string-to-java-util-date/56400421#56400421 – raok1997 May 31 '19 at 20:20
  • [Apache Jackrabbit](http://jackrabbit.apache.org) uses the ISO 8601 format for persisting dates, and there is a helper class to parse them: [org.apache.jackrabbit.util.ISO8601](https://svn.apache.org/repos/asf/jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/ISO8601.java) Comes with [jackrabbit-jcr-commons](http://mvnrepository.com/artifact/org.apache.jackrabbit/jackrabbit-jcr-commons). – Alexander Klimetschek Apr 22 '13 at 10:28
  • what is the format of 2023-06-12T10:49:010Z, can someone help on this – Girish Jun 14 '23 at 10:27

31 Answers31

513

Unfortunately, the time zone formats available to SimpleDateFormat (Java 6 and earlier) are not ISO 8601 compliant. SimpleDateFormat understands time zone strings like "GMT+01:00" or "+0100", the latter according to RFC # 822.

Even if Java 7 added support for time zone descriptors according to ISO 8601, SimpleDateFormat is still not able to properly parse a complete date string, as it has no support for optional parts.

Reformatting your input string using regexp is certainly one possibility, but the replacement rules are not as simple as in your question:

  • Some time zones are not full hours off UTC, so the string does not necessarily end with ":00".
  • ISO8601 allows only the number of hours to be included in the time zone, so "+01" is equivalent to "+01:00"
  • ISO8601 allows the usage of "Z" to indicate UTC instead of "+00:00".

The easier solution is possibly to use the data type converter in JAXB, since JAXB must be able to parse ISO8601 date string according to the XML Schema specification. javax.xml.bind.DatatypeConverter.parseDateTime("2010-01-01T12:00:00Z") will give you a Calendar object and you can simply use getTime() on it, if you need a Date object.

You could probably use Joda-Time as well, but I don't know why you should bother with that (Update 2022; maybe because the entire javax.xml.bind section is missing from Android's javax.xml package).

Top-Master
  • 7,611
  • 5
  • 39
  • 71
jarnbjo
  • 33,923
  • 7
  • 70
  • 94
  • 19
    The JAXB-solution is a really creative approach! It works as well, I have tested it with my sample. However, for whoever faces the problem and is allowed to use JodaTime, I would advise to use it, since it feels more natural. But your solution requires not additional libraries (at least with Java 6). – Ice09 Feb 05 '10 at 10:22
  • 38
    Here's the reverse: Calendar c = GregorianCalendar.getInstance();c.setTime(aDate);return javax.xml.bind.DatatypeConverter.printDateTime(c); – Alexander Ljungberg Oct 13 '10 at 18:13
  • Wow, that's a really non-obvious place to put such a useful thing. Been searching for a few days. – gtrak Dec 01 '10 at 20:29
  • 4
    Actually it's not so simple b/c you have to initialize the jaxb datatypeConverter. I ended up using DatatypeFactory myself as DataTypeConverterImpl did internally. What a headache. – gtrak Dec 01 '10 at 21:04
  • If that's all true, you should update Wikipedia: http://en.wikipedia.org/wiki/ISO_8601#Time_zone_designators as it indicates that you are (at least partially) incorrect. – straya Mar 01 '12 at 02:05
320

The way that is blessed by Java 7 documentation:

DateFormat df1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
String string1 = "2001-07-04T12:08:56.235-0700";
Date result1 = df1.parse(string1);

DateFormat df2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
String string2 = "2001-07-04T12:08:56.235-07:00";
Date result2 = df2.parse(string2);

You can find more examples in section Examples at SimpleDateFormat javadoc.

UPD 02/13/2020: There is a completely new way to do this in Java 8

Anthony
  • 12,407
  • 12
  • 64
  • 88
  • 7
    Your answer helped me to convert MongoDB's ISODate to local date. Regards. – Blue Sky Sep 18 '13 at 11:15
  • 1
    this is valid as of java 7 – linski Oct 09 '13 at 13:13
  • 10
    @b.long Java added more than a constant for such ISO 8601 compliant formats. Java got an entire new framework for date-time work that includes built-in default support for such formats. See the new [`java.time` framework](http://docs.oracle.com/javase/8/docs/api/java/time/package-summary.html) in Java 8, inspired by [Joda-Time](http://www.joda.org/joda-time/), supplanting the troublesome java.util.Date, .Calendar, and SimpleDateFormat classes. – Basil Bourque Jul 20 '14 at 17:04
  • Awesome, thanks @BasilBourque. I should say, I'm glad there's a Java 7 solution to this that the community has accepted. – blong Jul 21 '14 at 18:28
  • Also this works better than javax.xml.bind.DatatypeConverter.printDateTime(c) because it doesn't truncate the fractional seconds if milliseconds is 0. – Carlos Rendon May 19 '15 at 23:06
  • 2
    Doesn't this mean you need to know the date format in advance? What if you have to accept `string1` and `string2` but don't know which you will get. – Timmmm Aug 11 '15 at 14:47
  • 21
    'Z' needs to be in quotes – kervin Jun 20 '16 at 01:11
  • 10
    @kervin If the Z is in quotes, won't the formatter be looking specifically for the character Z, not all of the offset strings it can represent? It seems like quoting Z would only work by coincidence, if your date strings happened to be in UTC. – spaaarky21 Aug 09 '16 at 17:47
  • 1
    I'm not sure why this is not the approved answer to this post instead of the one from 2010 by @jarnbjo – Maritim May 21 '19 at 07:55
  • well this is worked for my case. `SimpleDateFormat` + `yyyy-MM-dd'T'HH:mm:ss.SSSXXX` is enough before we migrate to OffsetDateTime (Java 8 API) – mochadwi Jun 08 '20 at 09:14
  • it solved my 3 days of work just in a moment – Moktahid Al Faisal Feb 02 '22 at 23:54
  • @spaaarky21 If you read accepted answer: "`ISO8601 allows the usage of "Z" to indicate UTC instead of "+00:00"`", hence if input is UTC, the Z should be quoted with single-quotes. – Top-Master May 21 '22 at 07:45
  • Yes, ISO 8601 define Z as shorthand for +00:00. But there is a difference between a format string that is simply looking for the string "Z" without any regard for what it *represents*, and the parser/formatter's Z **pattern**, which handles the wide variety of ways offset can be represented. – spaaarky21 May 26 '22 at 19:46
210

Okay, this question is already answered, but I'll drop my answer anyway. It might help someone.

I've been looking for a solution for Android (API 7).

  • Joda was out of the question - it is huge and suffers from slow initialization. It also seemed a major overkill for that particular purpose.
  • Answers involving javax.xml won't work on Android API 7.

Ended up implementing this simple class. It covers only the most common form of ISO 8601 strings, but this should be enough in some cases (when you're quite sure that the input will be in this format).

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

/**
 * Helper class for handling a most common subset of ISO 8601 strings
 * (in the following format: "2008-03-01T13:00:00+01:00"). It supports
 * parsing the "Z" timezone, but many other less-used features are
 * missing.
 */
public final class ISO8601 {
    /** Transform Calendar to ISO 8601 string. */
    public static String fromCalendar(final Calendar calendar) {
        Date date = calendar.getTime();
        String formatted = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
            .format(date);
        return formatted.substring(0, 22) + ":" + formatted.substring(22);
    }

    /** Get current date and time formatted as ISO 8601 string. */
    public static String now() {
        return fromCalendar(GregorianCalendar.getInstance());
    }

    /** Transform ISO 8601 string to Calendar. */
    public static Calendar toCalendar(final String iso8601string)
            throws ParseException {
        Calendar calendar = GregorianCalendar.getInstance();
        String s = iso8601string.replace("Z", "+00:00");
        try {
            s = s.substring(0, 22) + s.substring(23);  // to get rid of the ":"
        } catch (IndexOutOfBoundsException e) {
            throw new ParseException("Invalid length", 0);
        }
        Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").parse(s);
        calendar.setTime(date);
        return calendar;
    }
}

Performance note: I instantiate new SimpleDateFormat every time as means to avoid a bug in Android 2.1. If you're as astonished as I was, see this riddle. For other Java engines, you may cache the instance in a private static field (using ThreadLocal, to be thread safe).

Community
  • 1
  • 1
wrygiel
  • 5,080
  • 3
  • 24
  • 29
  • 3
    Perhaps this should have been made into a question of its own, with its own answer? – Thorbear Aug 01 '12 at 13:25
  • 5
    This was the first page I've stubled upon when I was looking for the answer, so it seemed fit. For most Java developers, Android is not exactly Java. However, in most cases, one works the same as the other, so many Android developers will search for "java" when looking for this. – wrygiel Aug 02 '12 at 06:24
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/31198/discussion-between-marcus-junius-brutus-and-wrygiel) – Marcus Junius Brutus Jun 04 '13 at 15:56
  • 6
    i had to add .SSS for fractional seconds but works great thx. Why do you do `s = s.substring(0, 22) + s.substring(23);` - i dont see the point in this – Dori Sep 13 '13 at 15:31
147

java.time

The java.time API (built into Java 8 and later), makes this a little easier.

If you know the input is in UTC, such as the Z (for Zulu) on the end, the Instant class can parse.

java.util.Date date = Date.from( Instant.parse( "2014-12-12T10:39:40Z" ));

If your input may be another offset-from-UTC values rather than UTC indicated by the Z (Zulu) on the end, use the OffsetDateTime class to parse.

OffsetDateTime odt = OffsetDateTime.parse( "2010-01-01T12:00:00+01:00" );

Then extract an Instant, and convert to a java.util.Date by calling from.

Instant instant = odt.toInstant();  // Instant is always in UTC.
java.util.Date date = java.util.Date.from( instant );
Community
  • 1
  • 1
Adam
  • 35,919
  • 9
  • 100
  • 137
  • 13
    This answer is working too hard. A java.util.Date by definition has no time zone. So no need for all that time zone related code: the `LocalDateTime` and `ZoneId` and `atZone`. This simple one-liner will do: `java.util.Date date = Date.from( ZonedDateTime.parse( "2014-12-12T10:39:40Z" ).toInstant() );` – Basil Bourque Dec 15 '14 at 09:01
  • @BasilBourque Thanks, I knew going via LocalDateTime was wrong but couldn't find the quick way, answer updated. – Adam Dec 15 '14 at 10:19
  • 7
    @BasilBourque This is unnecessarily complicated: `Date.from(Instant.parse("2014-12-12T10:39:40Z" ));` is enough. – assylias Nov 13 '15 at 19:07
  • 7
    @assylias you are correct but that will only work when the date string is UTC zone, ISO8601 allows any timezone... – Adam Nov 13 '15 at 19:13
  • 3
    @Adam My bad - I hadn't realised the question was more general than your example. As a side comment an `OffsetDateTime` would be enough to parse ISO8601 (which does not contain time zone information but only an offset). – assylias Nov 13 '15 at 19:18
  • 2
    @assylias Thanks for your comment about letting `Instant` do the parsing. While not sufficient for this particular Question, it is an important distinction worth pointing out. So I added a second example of code. Oops, just noticed this is not originally my Answer; I hope Adam approves. – Basil Bourque Nov 13 '15 at 22:26
  • @BasilBourque yes I was planning to update answer in a similar way, you've saved me a job, thanks – Adam Nov 14 '15 at 05:13
77

Starting from Java 8, there is a completely new officially supported way to do this:

    String s = "2020-02-13T18:51:09.840Z";
    TemporalAccessor ta = DateTimeFormatter.ISO_INSTANT.parse(s);
    Instant i = Instant.from(ta);
    Date d = Date.from(i);
Anthony
  • 12,407
  • 12
  • 64
  • 88
  • 2
    If the string is in the instant format, with trailing `Z` as offset, we don’t need to specify this explicitly. Just `Instant i = Instant.parse(s);`. The string in the question had `+01:00`, in which case `DateTimeFormatter.ISO_INSTANT` does not work (at least not on my Java 11). – Ole V.V. Feb 14 '20 at 02:37
  • 5
    @OleV.V. You can use `ISO_OFFSET_DATE_TIME` to format dates with offsets, like `+01:00` (https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html#predefined) – Lucas Basquerotto Mar 16 '20 at 14:30
  • 1
    That’s true, @LucasBasquerotto. Though not mentioning that formatter explicitly, the answers [by Adam](https://stackoverflow.com/a/27479533/5772882) and [by Basil Bourque](https://stackoverflow.com/a/20578792/5772882) already do something similar. – Ole V.V. Mar 16 '20 at 14:38
  • 3
    This cannot parse "2020-06-01T14:34:00-05:00" which is a string produced by Javascript's toISOString() method. – Jose Solorzano Jun 11 '20 at 19:50
76

tl;dr

OffsetDateTime.parse ( "2010-01-01T12:00:00+01:00" )

Or for Java 12+, use Instant.parse as seen in the Answer by Arvind Kumar Avinash.

Using java.time

The new java.time package in Java 8 and later was inspired by Joda-Time.

The OffsetDateTime class represents a moment on the timeline with an offset-from-UTC but not a time zone.

OffsetDateTime odt = OffsetDateTime.parse ( "2010-01-01T12:00:00+01:00" );

Calling toString generates a string in standard ISO 8601 format:

2010-01-01T12:00+01:00

To see the same value through the lens of UTC, extract an Instant or adjust the offset from +01:00 to 00:00.

Instant instant = odt.toInstant();  

…or…

OffsetDateTime odtUtc = odt.withOffsetSameInstant( ZoneOffset.UTC );

Adjust into a time zone if desired. A time zone is a history of offset-from-UTC values for a region, with a set of rules for handling anomalies such as Daylight Saving Time (DST). So apply a time zone rather than a mere offset whenever possible.

ZonedDateTime zonedDateTimeMontréal = odt.atZoneSameInstant( ZoneId.of( "America/Montreal" ) );

For a date-only value, use LocalDate.

LocalDate ld = LocalDate.of( 2010 , Month.JANUARY , 1 ) ;

Or:

LocalDate ld = LocalDate.parse( "2010-01-01" ) ;

About java.time

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes. Hibernate 5 & JPA 2.2 support java.time.

Where to obtain the java.time classes?


Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • 2
    I don't think this is helpful, as it does not parse ISO 8601 dates, only a very specific subset. For example, `java.time.OffsetDateTime.parse ( "2010-01-01" )` fails – phil294 Sep 28 '21 at 00:41
  • @phil294 All of the *java.time* classes parse ISO 8601 standard format inputs as appropriate to each class. Your example of `"2010-01-01"` is a date alone, so will be parsed by the date-only class, `LocalDate`. Ex: `LocalDate.parse( "2010-01-01" )`. Your example class of `OffsetDateTime` is for a date, with a time-of-day, with an offset (a number of hours-minutes-seconds ahead/behind UTC). So `OffsetDateTime` parses ISO 8601 formatted inputs with a date, a time, and an offset. Ex: `OffsetDateTime.parse( "2010-01-01T12:30:00-08:00" )`. [Run code live at IdeOne.com](https://ideone.com/SiqcZl). – Basil Bourque Sep 28 '21 at 05:01
73

The Jackson-databind library also has ISO8601DateFormat class that does that (actual implementation in ISO8601Utils.

ISO8601DateFormat df = new ISO8601DateFormat();
Date d = df.parse("2010-07-28T22:25:51Z");
david_p
  • 5,722
  • 1
  • 32
  • 26
  • It fails to parse this date: `2015-08-11T13:10:00`. I get `String index out of range: 19`. Looking at the code it seems that it requires the milliseconds to be specified, and the timezone. Those should be optional. – Timmmm Aug 11 '15 at 14:54
  • 3
    To quote the documentation, the parse format is: `[yyyy-MM-dd|yyyyMMdd][T(hh:mm[:ss[.sss]]|hhmm[ss[.sss]])]?[Z|[+-]hh:mm]]`. In other words, the milliseconds is optional but the timezone is mandatory. – david_p Aug 12 '15 at 09:23
  • 2
    Ah yes actually it looks like you are right. Still, I'm pretty sure ISO8601 allows you to omit the timezone so it's still wrong. JodaTime works though: `new DateTime("2015-08-11T13:10:00").toDate()` – Timmmm Aug 12 '15 at 10:56
  • 3
    That class is now deprecated, the new one is StdDateFormat. Otherwise it works the same. – JohnEye Mar 14 '18 at 13:59
  • ISO8601DateFormat is deprecated use StdDateFormat instead – Dániel Kis Nov 13 '20 at 10:27
35

For Java version 7

You can follow Oracle documentation: http://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html

X - is used for ISO 8601 time zone

TimeZone tz = TimeZone.getTimeZone("UTC");
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");
df.setTimeZone(tz);
String nowAsISO = df.format(new Date());

System.out.println(nowAsISO);

DateFormat df1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");
//nowAsISO = "2013-05-31T00:00:00Z";
Date finalResult = df1.parse(nowAsISO);

System.out.println(finalResult);
d.danailov
  • 9,594
  • 4
  • 51
  • 36
23

The DatatypeConverter solution doesn't work in all VMs. The following works for me:

javax.xml.datatype.DatatypeFactory.newInstance().newXMLGregorianCalendar("2011-01-01Z").toGregorianCalendar().getTime()

I've found that joda does not work out of the box (specifically for the example I gave above with the timezone on a date, which should be valid)

Oleg Pavliv
  • 20,462
  • 7
  • 59
  • 75
James Scriven
  • 7,784
  • 1
  • 32
  • 36
17

I think we should use

DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")

for Date 2010-01-01T12:00:00Z

Jonik
  • 80,077
  • 70
  • 264
  • 372
Toby
  • 389
  • 2
  • 2
  • 5
    Why is this a better answer than the others, included the accepted answer with 76 upvotes? – Erick Robertson Sep 28 '12 at 14:28
  • 3
    @ErickRobertson: It is simple, out of the box, flexible, no conversions, and most people don't care about time zones. – TWiStErRob Aug 27 '13 at 21:02
  • 7
    not much point working with times if you dont care about timezones! – Dori Sep 13 '13 at 15:30
  • 19
    This **IGNORES** the timezone completely. Was using this until I realized this was happening, so I switched to JodaTime. – Joshua Pinter Dec 10 '13 at 01:10
  • 4
    Throwing away the time zone will simply result in errors at some point. – Bart van Kuik Mar 02 '14 at 13:20
  • 3
    -1. This just adds a 'Z' (literal) to the format but doesn't really take timezone into account .. as others have pointed out already. Don't use! – peterh Mar 08 '14 at 17:17
  • 1
    This is quite dangerous actually. By appending a Z to the string you're saying it's a UTC time (0 offset). Please consider removing our answer. – Zoomzoom Mar 10 '14 at 20:25
  • If your application follows the convention of always producing UTC dates then this is fine for reproducing dates. The only risk is that somehow non-UTC dates end op in your database and get blindly produced as if they were UTC. For dates received from clients this will only work if they send UTC dates with the 'Z' (not +00:00) notation. Dates with non-UTC timezones or different format will not be parsed correctly by this formatter. – Adriaan Koster Mar 31 '14 at 10:09
  • 1
    A warning should be added to this solution as to not misguide people: Unless this is running in a UTC timezone it will produce incorrect results. – kassim Feb 08 '15 at 15:48
  • 1
    It should be `X`, or `XX`, or `XXX`, for zoned string `+02`, `+0200`, and `+02:00`, respectively. In this way, it takes timezone into consideration. – WesternGun Jan 22 '19 at 13:15
10

Another very simple way to parse ISO8601 timestamps is to use org.apache.commons.lang.time.DateUtils:

import static org.junit.Assert.assertEquals;

import java.text.ParseException;
import java.util.Date;
import org.apache.commons.lang.time.DateUtils;
import org.junit.Test;

public class ISO8601TimestampFormatTest {
  @Test
  public void parse() throws ParseException {
    Date date = DateUtils.parseDate("2010-01-01T12:00:00+01:00", new String[]{ "yyyy-MM-dd'T'HH:mm:ssZZ" });
    assertEquals("Fri Jan 01 12:00:00 CET 2010", date.toString());
  }
}
tmandry
  • 375
  • 3
  • 13
10

The workaround for Java 7+ is using SimpleDateFormat:
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX", Locale.US);

This code can parse ISO8601 format like:

  • 2017-05-17T06:01:43.785Z
  • 2017-05-13T02:58:21.391+01:00

But on Java6, SimpleDateFormat doesn't understand X character and will throw
IllegalArgumentException: Unknown pattern character 'X'
We need to normalize ISO8601 date to the format readable in Java 6 with SimpleDateFormat.

public static Date iso8601Format(String formattedDate) throws ParseException {
    try {
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX", Locale.US);
        return df.parse(formattedDate);
    } catch (IllegalArgumentException ex) {
        // error happen in Java 6: Unknown pattern character 'X'
        if (formattedDate.endsWith("Z")) formattedDate = formattedDate.replace("Z", "+0000");
        else formattedDate = formattedDate.replaceAll("([+-]\\d\\d):(\\d\\d)\\s*$", "$1$2");
        DateFormat df1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US);
        return df1.parse(formattedDate);
    }
}

Method above to replace [Z with +0000] or [+01:00 with +0100] when error occurs in Java 6 (you can detect Java version and replace try/catch with if statement).

Khang .NT
  • 1,504
  • 6
  • 24
  • 46
  • No, the troublesome old date-time classes such as `Date` and `SimpleDateFormat` are poorly designed, confusing, and flawed. They are now legacy, supplanted by the java.time classes built into Java 8 and later. For Java 6 and Java 7, much of the java.time functionality is back-ported in the [ThreeTen-Backport](http://www.threeten.org/threetenbp/) project. Much better to add that library to your app than use those legacy classes. One-line solution in java.time: `OffsetDateTime.parse( "2010-01-01T12:00:00+01:00" )` – Basil Bourque May 16 '17 at 15:43
10

After I searched a lot to convert ISO8601 to date I suddenly found a java class that is ISO8601Util.java and this was part of com.google.gson.internal.bind.util. So you can use it to convert dates.

ISO8601Utils.parse("2010-01-01T12:00:00Z" , ParsePosition(0))

and you can simply use this kotlin extension function

fun String.getDateFromString() : Date? = ISO8601Utils.parse(this , 
ParsePosition(0))
mmdreza baqalpour
  • 1,106
  • 9
  • 18
8

Java 8+

Simple one liner that I didn't found in answers:

Date date = Date.from(ZonedDateTime.parse("2010-01-01T12:00:00+01:00").toInstant());

Date doesn't contain timezone, it will be stored in UTC, but will be properly converted to your JVM timezone even during simple output with System.out.println(date).

pavelety
  • 746
  • 6
  • 8
  • I think you’re right, it’s not exactly in the other answers. It’s still better if one can avoid `Date` completely since that class is poorly designed and long outdated. It’s better to use `ZonedDateTime.withZoneSameInstant()` for converting to one’s own time zone, for example. Only use this answer if you necessarily need a `Date` for a legacy API that you cannot afford to upgrade to java.time just now. – Ole V.V. Jul 20 '20 at 19:43
7

Java has a dozen different ways to parse a date-time, as the excellent answers here demonstrate. But somewhat amazingly, none of Java's time classes fully implement ISO 8601!

With Java 8, I'd recommend:

ZonedDateTime zp = ZonedDateTime.parse(string);
Date date = Date.from(zp.toInstant());

That will handle examples both in UTC and with an offset, like "2017-09-13T10:36:40Z" or "2017-09-13T10:36:40+01:00". It will do for most use cases.

But it won't handle examples like "2017-09-13T10:36:40+01", which is a valid ISO 8601 date-time.
It also won't handle date only, e.g. "2017-09-13".

If you have to handle those, I'd suggest using a regex first to sniff the syntax.

There's a nice list of ISO 8601 examples here with lots of corner cases: https://www.myintervals.com/blog/2009/05/20/iso-8601-date-validation-that-doesnt-suck/ I'm not aware of any Java class that could cope with all of them.

Daniel Winterstein
  • 2,418
  • 1
  • 29
  • 41
  • `OffsetDateTime` will do and conceptually matches a date-time with an offset better. – Ole V.V. Apr 08 '18 at 17:04
  • Hey @OleV.V. thanks for the suggestion. Sadly no: OffsetDateTime.parse() will throw an exception for several valid ISO 8601 strings, e.g. "2017-09-13T10:36:40+01" or "2017-09-13" – Daniel Winterstein Apr 09 '18 at 17:01
  • I only meant to say that `OffsetDateTime` handles the examples that you handle with `ZonedDateTime`. I believe that it doesn’t handle any of the examples that `ZonedDateTime` doesn’t. In that sense it’s no improvement (but also no worse). Sorry, I wasn’t perfectly clear. – Ole V.V. Apr 09 '18 at 17:55
  • 1
    This should be the accepted answer in 2020, given the state of things. – slashCoder Feb 13 '20 at 19:03
6

java.time

Note that in Java 8, you can use the java.time.ZonedDateTime class and its static parse(CharSequence text) method.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
Martin Rust
  • 91
  • 1
  • 4
  • 1
    The input strings in the Question have only an offset-from-UTC, not a full time zone. So `Instant` and `ZonedDateTime` are appropriate here, not `ZonedDateTime`. – Basil Bourque Oct 19 '16 at 07:23
6

I faced the same problem and solved it by the following code .

 public static Calendar getCalendarFromISO(String datestring) {
    Calendar calendar = Calendar.getInstance(TimeZone.getDefault(), Locale.getDefault()) ;
    SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault());
    try {
        Date date = dateformat.parse(datestring);
        date.setHours(date.getHours() - 1);
        calendar.setTime(date);

        String test = dateformat.format(calendar.getTime());
        Log.e("TEST_TIME", test);

    } catch (ParseException e) {
        e.printStackTrace();
    }

    return calendar;
}

Earlier I was using SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.getDefault());

But later i found the main cause of the exception was the yyyy-MM-dd'T'HH:mm:ss.SSSZ ,

So i used

SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault());

It worked fine for me .

Abhay Kumar
  • 5,048
  • 2
  • 16
  • 21
5

Also you can use the following class -

org.springframework.extensions.surf.util.ISO8601DateFormat


Date date = ISO8601DateFormat.parse("date in iso8601");

Link to the Java Doc - Hierarchy For Package org.springframework.extensions.surf.maven.plugin.util

soumya
  • 3,801
  • 9
  • 35
  • 69
4

Java 12

Since Java 12, Instant#parse can parse a date-time string containing time-zone offset.

Date dt = Date.from(Instant.parse(your-date-time-string));

Parse your ISO 8601 formatted date-time strings using Instant#parse and convert the result into java.util.Date using Date#from. See this code run at Ideone.com.

Demo:

import java.time.Instant;
import java.util.Date;
import java.util.stream.Stream;

public class Main {
  public static void main(String[] args) {
    Stream.of(
        "2010-01-01T12:00:00+01:00",
        "2010-01-01T12:00:00-01:00",
        "2010-01-01T12:00:00Z"
      )
      .map(Instant::parse)
      .map(Date::from)
      .forEach(System.out::println);
  }
}

See this code run at Ideone.com.

Learn more about the modern Date-Time API from Trail: Date Time.

Arvind Kumar Avinash
  • 71,965
  • 6
  • 74
  • 110
  • 2
    If one indispensably needs an old-fashioned `Date` for a legacy API, this is the modern and elegant solution. I can’t remember exactly from which Java version `Instant.parse("2010-01-01T12:00:00+01:00")` works with an offset other than `Z`? – Ole V.V. Oct 31 '22 at 06:36
  • I suggest changing your heading from "TL;DR" to `Instant.parse` – Basil Bourque Oct 31 '22 at 20:52
  • 2
    @OleV.V. The Javadoc for [`Instant.parse`](https://docs.oracle.com/en/java/javase/19/docs/api/java.base/java/time/Instant.html#parse(java.lang.CharSequence)) for Java 17 & 19 still says the input must be in UTC. To quote: "The string must represent a valid instant in UTC". However, the Javadoc for [`DateTimeFormatter.ISO_INSTANT`](https://docs.oracle.com/en/java/javase/19/docs/api/java.base/java/time/format/DateTimeFormatter.html#ISO_INSTANT) says it will "parse the offset, converting the instant to UTC". Seems Java 12 is the first Javadoc for `ISO_INSTANT` to mention parsing the offset. – Basil Bourque Oct 31 '22 at 21:04
  • Thanks, @BasilBourque. Java 12 it is. Trying the code in Java 11 I do get `Text '2010-01-01T12:00:00+01:00' could not be parsed at index 19`. The ideone link demonstrates that it does work in Java 12. – Ole V.V. Nov 01 '22 at 08:49
3

As others have mentioned Android does not have a good way to support parsing/formatting ISO 8601 dates using classes included in the SDK. I have written this code multiple times so I finally created a Gist that includes a DateUtils class that supports formatting and parsing ISO 8601 and RFC 1123 dates. The Gist also includes a test case showing what it supports.

https://gist.github.com/mraccola/702330625fad8eebe7d3

Matt Accola
  • 4,090
  • 4
  • 28
  • 37
2

SimpleDateFormat for JAVA 1.7 has a cool pattern for ISO 8601 format.

Class SimpleDateFormat

Here is what I did:

Date d = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSSZ",
         Locale.ENGLISH).format(System.currentTimeMillis());
soumya
  • 3,801
  • 9
  • 35
  • 69
Eesha
  • 92
  • 8
2

Use string like LocalDate.parse(((String) data.get("d_iso8601")),DateTimeFormatter.ISO_DATE)

Stepan
  • 1,391
  • 18
  • 40
2

I am surprised that not even one java library supports all ISO 8601 date formats as per https://en.wikipedia.org/wiki/ISO_8601. Joda DateTime was supporting most of them, but not all and hence I added custom logic to handle all of them. Here is my implementation.

import java.text.ParseException;
import java.util.Date;

import org.apache.commons.lang3.time.DateUtils;
import org.joda.time.DateTime;

public class ISO8601DateUtils {
 
 /**
  * It parses all the date time formats from https://en.wikipedia.org/wiki/ISO_8601 and returns Joda DateTime.
  * Zoda DateTime does not support dates of format 20190531T160233Z, and hence added custom logic to handle this using SimpleDateFormat.
  * @param dateTimeString ISO 8601 date time string
  * @return
  */
 public static DateTime parse(String dateTimeString) {
  try {
   return new DateTime( dateTimeString );
  } catch(Exception e) {
   try {
    Date dateTime = DateUtils.parseDate(dateTimeString, JODA_NOT_SUPPORTED_ISO_DATES);
    return new DateTime(dateTime.getTime());
   } catch (ParseException e1) {
    throw new RuntimeException(String.format("Date %s could not be parsed to ISO date", dateTimeString));
   }
  }
 }
  
   private static String[] JODA_NOT_SUPPORTED_ISO_DATES = new String[] {
   // upto millis
   "yyyyMMdd'T'HHmmssSSS'Z'",
   "yyyyMMdd'T'HHmmssSSSZ",
   "yyyyMMdd'T'HHmmssSSSXXX",
   
   "yyyy-MM-dd'T'HHmmssSSS'Z'",
   "yyyy-MM-dd'T'HHmmssSSSZ",
   "yyyy-MM-dd'T'HHmmssSSSXXX",
   
   // upto seconds
   "yyyyMMdd'T'HHmmss'Z'",
   "yyyyMMdd'T'HHmmssZ",
   "yyyyMMdd'T'HHmmssXXX",
   
   "yyyy-MM-dd'T'HHmmss'Z'", 
   "yyyy-MM-dd'T'HHmmssZ",
   "yyyy-MM-dd'T'HHmmssXXX",
   
   // upto minutes
   "yyyyMMdd'T'HHmm'Z'",
   "yyyyMMdd'T'HHmmZ",
   "yyyyMMdd'T'HHmmXXX",

   "yyyy-MM-dd'T'HHmm'Z'",
   "yyyy-MM-dd'T'HHmmZ",
   "yyyy-MM-dd'T'HHmmXXX",
   
   //upto hours is already supported by Joda DateTime
 };
}
raok1997
  • 369
  • 5
  • 12
  • 1
    Half of those are not valid ISO-8601 date/times. From wikipedia: `Either basic or extended formats may be used, but both date and time must use the same format` – john16384 Oct 08 '20 at 06:38
2

When want to convert from UTC to format that we want. It will change depend on the zone/location we stay

//utcDate = "2021-06-05T02:46:29Z"
fun converterUtcToReadableDateTime(utcDate: String): String {
    val offsetDateTime = OffsetDateTime.ofInstant(Instant.parse(utcDate), ZoneId.systemDefault())
    val patternDate = "dd MMM yyyy h:mm a"
    return DateTimeFormatter.ofPattern(patternDate).format(offsetDateTime)
}

fun converterUtcToReadableDate(utcDate: String): String {
    val offsetDateTime = OffsetDateTime.ofInstant(Instant.parse(utcDate), ZoneId.systemDefault())
    val patternDate = "d MMM yyyy"
    return DateTimeFormatter.ofPattern(patternDate).format(offsetDateTime)
}

fun converterUtcToReadableTime(utcDate: String): String {
    val offsetDateTime = OffsetDateTime.ofInstant(Instant.parse(utcDate), ZoneId.systemDefault())
    val patternDate = "h:mm a"
    return DateTimeFormatter.ofPattern(patternDate).format(offsetDateTime)
}
Ticherhaz FreePalestine
  • 2,738
  • 4
  • 20
  • 46
1

Do it like this:

public static void main(String[] args) throws ParseException {

    String dateStr = "2016-10-19T14:15:36+08:00";
    Date date = javax.xml.bind.DatatypeConverter.parseDateTime(dateStr).getTime();

    System.out.println(date);

}

Here is the output:

Wed Oct 19 15:15:36 CST 2016

yinhaomin
  • 327
  • 2
  • 4
1

A little test that shows how to parse a date in ISO8601 and that LocalDateTime does not handle DSTs.

 @Test
    public void shouldHandleDaylightSavingTimes() throws ParseException {

        //ISO8601 UTC date format
        SimpleDateFormat utcFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");

        // 1 hour of difference between 2 dates in UTC happening at the Daylight Saving Time
        Date d1 = utcFormat.parse("2019-10-27T00:30:00.000Z");
        Date d2 = utcFormat.parse("2019-10-27T01:30:00.000Z");

        //Date 2 is before date 2
        Assert.assertTrue(d1.getTime() < d2.getTime());
        // And there is 1 hour difference between the 2 dates
        Assert.assertEquals(1000*60*60, d2.getTime() - d1.getTime());

        //Print the dates in local time
        SimpleDateFormat localFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm z Z", Locale.forLanguageTag("fr_CH"));
        localFormat.setTimeZone(TimeZone.getTimeZone("Europe/Zurich"));

        //Both dates are at 02h30 local time (because of DST), but one is CEST +0200 and the other CET +0100 (clock goes backwards)
        Assert.assertEquals("2019-10-27 02:30 CEST +0200", localFormat.format(d1));
        Assert.assertEquals("2019-10-27 02:30 CET +0100", localFormat.format(d2));

        //Small test that shows that LocalDateTime does not handle DST (and should not be used for storing timeseries data)
        LocalDateTime ld1 = LocalDateTime.ofInstant(d1.toInstant(), ZoneId.of("Europe/Zurich"));
        LocalDateTime ld2 = LocalDateTime.ofInstant(d2.toInstant(), ZoneId.of("Europe/Zurich"));

        //Note that a localdatetime does not handle DST, therefore the 2 dates are the same
        Assert.assertEquals(ld1, ld2);

        //They both have the following local values
        Assert.assertEquals(2019, ld1.getYear());
        Assert.assertEquals(27, ld1.getDayOfMonth());
        Assert.assertEquals(10, ld1.getMonthValue());
        Assert.assertEquals(2, ld1.getHour());
        Assert.assertEquals(30, ld1.getMinute());
        Assert.assertEquals(0, ld1.getSecond());

    }
ddtxra
  • 35
  • 5
  • 3
    FYI, the terribly troublesome date-time classes such as [`java.util.Date`](https://docs.oracle.com/javase/10/docs/api/java/util/Date.html), [`java.util.Calendar`](https://docs.oracle.com/javase/10/docs/api/java/util/Calendar.html), and `java.text.SimpleDateFormat` are now [legacy](https://en.wikipedia.org/wiki/Legacy_system), supplanted by the [*java.time*](https://docs.oracle.com/javase/10/docs/api/java/time/package-summary.html) classes built into Java 8 and later. See [*Tutorial* by Oracle](https://docs.oracle.com/javase/tutorial/datetime/TOC.html). – Basil Bourque Aug 30 '19 at 19:12
  • 1
    You are correct that `LocalDateTime` doesn’t handle summer time (DST) since it doesn’t handle a time zone at all. For that we need `ZonedDateTime`. Proposing `Date` and `SimpleDateFormat` is — IMHO bad. – Ole V.V. Aug 31 '19 at 17:42
  • 1
    Indeed ZonedDateTime works. And java.time.Instant is also a good alternative to handle DST. I know that java.util.Date is deprecated and should not be used, but I was just answering the original question: How to convert a String in 8601 to java.util.date.... – ddtxra Sep 01 '19 at 21:36
0

I had a similar need: I needed to be able to parse any date ISO8601 compliant without knowing the exact format in advance, and I wanted a lightweight solution which would also work on Android.

When I googled my needs I stumbled upon this question, and noticed that AFAIU, no answer completely fit my needs. So I developed jISO8601 and pushed it on maven central.

Just add in you pom.xml:

<dependency>
  <groupId>fr.turri</groupId>
  <artifactId>jISO8601</artifactId>
  <version>0.2</version>
</dependency>

and then you're good to go:

import fr.turri.jiso8601.*;
...
Calendar cal = Iso8601Deserializer.toCalendar("1985-03-04");
Date date = Iso8601Deserializer.toDate("1985-03-04T12:34:56Z");

Hopes it help.

gturri
  • 13,807
  • 9
  • 40
  • 57
0

To just format a date like this the following worked for me in a Java 6 based application. There is a DateFormat class JacksonThymeleafISO8601DateFormat in the thymeleaf project which inserts the missing colon:

https://github.com/thymeleaf/thymeleaf/blob/40d27f44df7b52eda47d1bc6f1b3012add6098b3/src/main/java/org/thymeleaf/standard/serializer/StandardJavaScriptSerializer.java

I used it for ECMAScript date format compatibilty.

ekip
  • 3
  • 2
0

I couldn't use Java 8 features, so only java.util.Date was available. I already had a dependency on gson library but didn't want to use ISO8601Utils directly. ISO8601Utils is an internal API, gson's authors warns not to use it.

I parsed a ISO8601 date using gson's public API:

fun parseISO8601DateToLocalTimeOrNull(date: String): Date? {
    return try {
        GsonBuilder()
            .create()
            .getAdapter(Date::class.java)
            .fromJson("\"$date\"")
     } catch (t: Throwable) {
        null
     }
}

Under the hood, the adapter still uses ISO8601Utils. But if you're using the adapter you can be sure that a different compatible version of gson won't break your project.

I worried that creation of adapter may be slow so I measured execution time on Pixel 3a with debuggable=false.parseISO8601DateToLocalTimeOrNull took ~0.5 milliseconds to parse a date.

VadzimV
  • 1,111
  • 12
  • 13
  • I wonder what on Earth would keep you from using Java 8 features? An evil boss forcing you to use Java 5?? – Ole V.V. May 31 '22 at 14:28
  • 1
    I develop an SDK that targets Android API 21+. Java8 classes on Android have been available since API 24. As an SDK developer, I don't want to bring new dependencies like ThreeTenABP nor force SDK users to turn on desugaring in their applications. So I decided to suffer with `java.util.Date`. – VadzimV Jun 02 '22 at 08:42
-1

Base Function Courtesy : @wrygiel.

This function can convert ISO8601 format to Java Date which can handle the offset values. As per the definition of ISO 8601 the offset can be mentioned in different formats.

±[hh]:[mm]
±[hh][mm]
±[hh]

Eg:  "18:30Z", "22:30+04", "1130-0700", and "15:00-03:30" all mean the same time. - 06:30PM UTC

This class has static methods to convert

  • ISO8601 string to Date(Local TimeZone) object
  • Date to ISO8601 string
  • Daylight Saving is automatically calc

Sample ISO8601 Strings

/*       "2013-06-25T14:00:00Z";
         "2013-06-25T140000Z";
         "2013-06-25T14:00:00+04";
         "2013-06-25T14:00:00+0400";
         "2013-06-25T140000+0400";
         "2013-06-25T14:00:00-04";
         "2013-06-25T14:00:00-0400";
         "2013-06-25T140000-0400";*/


public class ISO8601DateFormatter {

private static final DateFormat DATE_FORMAT_1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
private static final DateFormat DATE_FORMAT_2 = new SimpleDateFormat("yyyy-MM-dd'T'HHmmssZ");
private static final String UTC_PLUS = "+";
private static final String UTC_MINUS = "-";

public static Date toDate(String iso8601string) throws ParseException {
    iso8601string = iso8601string.trim();
    if(iso8601string.toUpperCase().indexOf("Z")>0){
        iso8601string = iso8601string.toUpperCase().replace("Z", "+0000");
    }else if(((iso8601string.indexOf(UTC_PLUS))>0)){
        iso8601string = replaceColon(iso8601string, iso8601string.indexOf(UTC_PLUS));
        iso8601string = appendZeros(iso8601string, iso8601string.indexOf(UTC_PLUS), UTC_PLUS);
    }else if(((iso8601string.indexOf(UTC_MINUS))>0)){
        iso8601string = replaceColon(iso8601string, iso8601string.indexOf(UTC_MINUS));
        iso8601string = appendZeros(iso8601string, iso8601string.indexOf(UTC_MINUS), UTC_MINUS);
    }

    Date date = null;
    if(iso8601string.contains(":"))
        date = DATE_FORMAT_1.parse(iso8601string);
    else{
        date = DATE_FORMAT_2.parse(iso8601string);
    }
    return date;
}

public static String toISO8601String(Date date){
    return DATE_FORMAT_1.format(date);
}

private static String replaceColon(String sourceStr, int offsetIndex){
    if(sourceStr.substring(offsetIndex).contains(":"))
        return sourceStr.substring(0, offsetIndex) + sourceStr.substring(offsetIndex).replace(":", "");
    return sourceStr;
}

private static String appendZeros(String sourceStr, int offsetIndex, String offsetChar){
    if((sourceStr.length()-1)-sourceStr.indexOf(offsetChar,offsetIndex)<=2)
        return sourceStr + "00";
    return sourceStr;
}

}

Akh
  • 5,961
  • 14
  • 53
  • 82
  • 2
    Watch out -- DateFormat and derived classes are not multithread compatible! Using static SimpleDateFormat objects such as DATE_FORMAT_1 and DATE_FORMAT_2 means that multiple threads calling the ISO8601DateFormatter functions will share the same DateFormat object. This leads to data corruption and incorrect dates being returned from the DateFormat calls. To fix this, you should just make the pattern strings constants and create local SimpleDateFormat variables whenever needed. This will ensure that each object is just used by one thread. – Theo May 22 '14 at 20:55
  • A better fix for thread-safety is to instead use a date-time library built for thread-safety. In Java that world be either Joda-Time or java.time. – Basil Bourque Dec 15 '14 at 18:41
-2

I think what a lot of people want to do is parse JSON date strings. There is a good chance if you come to this page that you might want to convert a JavaScript JSON date to a Java date.

To show what a JSON date string looks like:

    var d=new Date();
    var s = JSON.stringify(d);

    document.write(s);
    document.write("<br />"+d);


    "2013-12-14T01:55:33.412Z"
    Fri Dec 13 2013 17:55:33 GMT-0800 (PST)

The JSON date string is 2013-12-14T01:55:33.412Z.

Dates are not covered by JSON spec per say, but the above is a very specific ISO 8601 format, while ISO_8601 is much much bigger and that is a mere subset albeit a very important one.

See http://www.json.org See http://en.wikipedia.org/wiki/ISO_8601 See http://www.w3.org/TR/NOTE-datetime

As it happens I wrote a JSON parser and a PLIST parser both of which use ISO-8601 but not the same bits.

/*
    var d=new Date();
    var s = JSON.stringify(d);

    document.write(s);
    document.write("<br />"+d);


    "2013-12-14T01:55:33.412Z"
    Fri Dec 13 2013 17:55:33 GMT-0800 (PST)


 */
@Test
public void jsonJavaScriptDate() {
    String test =  "2013-12-14T01:55:33.412Z";

    Date date = Dates.fromJsonDate ( test );
    Date date2 = Dates.fromJsonDate_ ( test );

    assertEquals(date2.toString (), "" + date);

    puts (date);
}

I wrote two ways to do this for my project. One standard, one fast.

Again, JSON date string is a very specific implementation of ISO 8601....

(I posted the other one in the other answer which should work for PLIST dates, which are a different ISO 8601 format).

The JSON date is as follows:

public static Date fromJsonDate_( String string ) {

    try {

        return new SimpleDateFormat ( "yyyy-MM-dd'T'HH:mm:ss.SSSXXX").parse ( string );
    } catch ( ParseException e ) {
        return Exceptions.handle (Date.class, "Not a valid JSON date", e);
    }


}

PLIST files (ASCII non GNUNext) also uses ISO 8601 but no miliseconds so... not all ISO-8601 dates are the same. (At least I have not found one that uses milis yet and the parser I have seen skip the timezone altogether OMG).

Now for the fast version (you can find it in Boon).

public static Date fromJsonDate( String string ) {

    return fromJsonDate ( Reflection.toCharArray ( string ), 0, string.length () );

}

Note that Reflection.toCharArray uses unsafe if available but defaults to string.toCharArray if not.

(You can take it out of the example by replacing Reflection.toCharArray ( string ) with string.toCharArray()).

public static Date fromJsonDate( char[] charArray, int from, int to ) {

    if (isJsonDate ( charArray, from, to )) {
        int year = CharScanner.parseIntFromTo ( charArray, from + 0, from + 4 );
        int month = CharScanner.parseIntFromTo ( charArray,  from +5,  from +7 );
        int day = CharScanner.parseIntFromTo ( charArray,  from +8,  from +10 );
        int hour = CharScanner.parseIntFromTo ( charArray,  from +11,  from +13 );

        int minute = CharScanner.parseIntFromTo ( charArray,  from +14,  from +16 );

        int second = CharScanner.parseIntFromTo ( charArray,  from +17,  from +19 );

        int miliseconds = CharScanner.parseIntFromTo ( charArray,  from +20,  from +23 );

        TimeZone tz = TimeZone.getTimeZone ( "GMT" );


        return toDate ( tz, year, month, day, hour, minute, second, miliseconds );

    }   else {
        return null;
    }

}

The isJsonDate is implemented as follows:

public static boolean isJsonDate( char[] charArray, int start, int to ) {
    boolean valid = true;
    final int length = to -start;

    if (length != JSON_TIME_LENGTH) {
        return false;
    }

    valid &=  (charArray [ start + 19 ]  == '.');

    if (!valid) {
        return false;
    }


    valid &=  (charArray[  start +4 ]  == '-') &&
            (charArray[  start +7 ]  == '-') &&
            (charArray[  start +10 ] == 'T') &&
            (charArray[  start +13 ] == ':') &&
            (charArray[  start +16 ] == ':');

    return valid;
}

Anyway... my guess is that quite a few people who come here.. might be looking for the JSON Date String and although it is an ISO-8601 date, it is a very specific one that needs a very specific parse.

public static int parseIntFromTo ( char[] digitChars, int offset, int to ) {
    int num = digitChars[ offset ] - '0';
    if ( ++offset < to ) {
        num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
        if ( ++offset < to ) {
            num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
            if ( ++offset < to ) {
                num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
                if ( ++offset < to ) {
                    num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
                    if ( ++offset < to ) {
                        num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
                        if ( ++offset < to ) {
                            num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
                            if ( ++offset < to ) {
                                num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
                                if ( ++offset < to ) {
                                    num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    return num;
}

See https://github.com/RichardHightower/boon Boon has a PLIST parser (ASCII) and a JSON parser.

The JSON parser is the fastest Java JSON parser that I know of.

Independently verified by the Gatling Performance dudes.

https://github.com/gatling/json-parsers-benchmark

Benchmark                               Mode Thr     Count  Sec         Mean   Mean error        Units
BoonCharArrayBenchmark.roundRobin      thrpt  16        10    1   724815,875    54339,825    ops/s
JacksonObjectBenchmark.roundRobin      thrpt  16        10    1   580014,875   145097,700    ops/s
JsonSmartBytesBenchmark.roundRobin     thrpt  16        10    1   575548,435    64202,618    ops/s
JsonSmartStringBenchmark.roundRobin    thrpt  16        10    1   541212,220    45144,815    ops/s
GSONStringBenchmark.roundRobin         thrpt  16        10    1   522947,175    65572,427    ops/s
BoonDirectBytesBenchmark.roundRobin    thrpt  16        10    1   521528,912    41366,197    ops/s
JacksonASTBenchmark.roundRobin         thrpt  16        10    1   512564,205   300704,545    ops/s
GSONReaderBenchmark.roundRobin         thrpt  16        10    1   446322,220    41327,496    ops/s
JsonSmartStreamBenchmark.roundRobin    thrpt  16        10    1   276399,298   130055,340    ops/s
JsonSmartReaderBenchmark.roundRobin    thrpt  16        10    1    86789,825    17690,031    ops/s

It has the fastest JSON parser for streams, readers, bytes[], char[], CharSequence (StringBuilder, CharacterBuffer), and String.

See more benchmarks at:

https://github.com/RichardHightower/json-parsers-benchmark

RickHigh
  • 1,808
  • 20
  • 16
  • This Answer about JSON is off-topic from the Question. Furthermore, this Question is incorrect as there is no such thing as a “JSON date” amongst the very few [JSON data types](https://en.wikipedia.org/wiki/JSON#Data_types.2C_syntax_and_example). And nowadays, all this code can be replaced with a single-line call to built-in Java feature: `Instant.parse( "2013-12-14T01:55:33.412Z" )` – Basil Bourque Oct 19 '16 at 07:15