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.
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).
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.
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
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
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;
}
}
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)
Use string like
LocalDate.parse(((String) data.get("d_iso8601")),DateTimeFormatter.ISO_DATE)
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
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());
}
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());
}
}
SimpleDateFormat for JAVA 1.7 has a cool pattern for ISO 8601 format.
Here is what I did:
Date d = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSSZ",
Locale.ENGLISH).format(System.currentTimeMillis());
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 .
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:
I used it for ECMAScript date format compatibilty.
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))
This seemed to work best for me:
public static Date fromISO8601_( String string ) {
try {
return new SimpleDateFormat ( "yyyy-MM-dd'T'HH:mm:ssXXX").parse ( string );
} catch ( ParseException e ) {
return Exceptions.handle (Date.class, "Not a valid ISO8601", e);
}
}
I needed to convert to/fro JavaScript date strings to Java. I found the above works with the recommendation. There were some examples using SimpleDateFormat that were close but they did not seem to be the subset as recommended by:
http://www.w3.org/TR/NOTE-datetime
and supported by PLIST and JavaScript Strings and such which is what I needed.
This seems to be the most common form of ISO8601 string out there, and a good subset.
The examples they give are:
1994-11-05T08:15:30-05:00 corresponds
November 5, 1994, 8:15:30 am, US Eastern Standard Time.
1994-11-05T13:15:30Z corresponds to the same instant.
I also have a fast version:
final static int SHORT_ISO_8601_TIME_LENGTH = "1994-11-05T08:15:30Z".length ();
// 01234567890123456789012
final static int LONG_ISO_8601_TIME_LENGTH = "1994-11-05T08:15:30-05:00".length ();
public static Date fromISO8601( String string ) {
if (isISO8601 ( string )) {
char [] charArray = Reflection.toCharArray ( string );//uses unsafe or string.toCharArray if unsafe is not available
int year = CharScanner.parseIntFromTo ( charArray, 0, 4 );
int month = CharScanner.parseIntFromTo ( charArray, 5, 7 );
int day = CharScanner.parseIntFromTo ( charArray, 8, 10 );
int hour = CharScanner.parseIntFromTo ( charArray, 11, 13 );
int minute = CharScanner.parseIntFromTo ( charArray, 14, 16 );
int second = CharScanner.parseIntFromTo ( charArray, 17, 19 );
TimeZone tz ;
if (charArray[19] == 'Z') {
tz = TimeZone.getTimeZone ( "GMT" );
} else {
StringBuilder builder = new StringBuilder ( 9 );
builder.append ( "GMT" );
builder.append( charArray, 19, LONG_ISO_8601_TIME_LENGTH - 19);
String tzStr = builder.toString ();
tz = TimeZone.getTimeZone ( tzStr ) ;
}
return toDate ( tz, year, month, day, hour, minute, second );
} else {
return null;
}
}
...
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;
}
public static boolean isISO8601( String string ) {
boolean valid = true;
if (string.length () == SHORT_ISO_8601_TIME_LENGTH) {
valid &= (string.charAt ( 19 ) == 'Z');
} else if (string.length () == LONG_ISO_8601_TIME_LENGTH) {
valid &= (string.charAt ( 19 ) == '-' || string.charAt ( 19 ) == '+');
valid &= (string.charAt ( 22 ) == ':');
} else {
return false;
}
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4
// "1 9 9 4 - 1 1 - 0 5 T 0 8 : 1 5 : 3 0 - 0 5 : 0 0
valid &= (string.charAt ( 4 ) == '-') &&
(string.charAt ( 7 ) == '-') &&
(string.charAt ( 10 ) == 'T') &&
(string.charAt ( 13 ) == ':') &&
(string.charAt ( 16 ) == ':');
return valid;
}
I have not benchmarked it, but I am guess it will be pretty fast. It seems to work. :)
@Test
public void testIsoShortDate() {
String test = "1994-11-05T08:15:30Z";
Date date = Dates.fromISO8601 ( test );
Date date2 = Dates.fromISO8601_ ( test );
assertEquals(date2.toString (), date.toString ());
puts (date);
}
@Test
public void testIsoLongDate() {
String test = "1994-11-05T08:11:22-05:00";
Date date = Dates.fromISO8601 ( test );
Date date2 = Dates.fromISO8601_ ( test );
assertEquals(date2.toString (), date.toString ());
puts (date);
}
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.
I think we should use
DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")
for Date 2010-01-01T12:00:00Z
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
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:
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.
Note that in Java 8, you can use the java.time.ZonedDateTime class and its static parse(CharSequence text)
method.
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;_x000D_
import java.util.Date;_x000D_
_x000D_
import org.apache.commons.lang3.time.DateUtils;_x000D_
import org.joda.time.DateTime;_x000D_
_x000D_
public class ISO8601DateUtils {_x000D_
_x000D_
/**_x000D_
* It parses all the date time formats from https://en.wikipedia.org/wiki/ISO_8601 and returns Joda DateTime._x000D_
* Zoda DateTime does not support dates of format 20190531T160233Z, and hence added custom logic to handle this using SimpleDateFormat._x000D_
* @param dateTimeString ISO 8601 date time string_x000D_
* @return_x000D_
*/_x000D_
public static DateTime parse(String dateTimeString) {_x000D_
try {_x000D_
return new DateTime( dateTimeString );_x000D_
} catch(Exception e) {_x000D_
try {_x000D_
Date dateTime = DateUtils.parseDate(dateTimeString, JODA_NOT_SUPPORTED_ISO_DATES);_x000D_
return new DateTime(dateTime.getTime());_x000D_
} catch (ParseException e1) {_x000D_
throw new RuntimeException(String.format("Date %s could not be parsed to ISO date", dateTimeString));_x000D_
}_x000D_
}_x000D_
}_x000D_
_x000D_
private static String[] JODA_NOT_SUPPORTED_ISO_DATES = new String[] {_x000D_
// upto millis_x000D_
"yyyyMMdd'T'HHmmssSSS'Z'",_x000D_
"yyyyMMdd'T'HHmmssSSSZ",_x000D_
"yyyyMMdd'T'HHmmssSSSXXX",_x000D_
_x000D_
"yyyy-MM-dd'T'HHmmssSSS'Z'",_x000D_
"yyyy-MM-dd'T'HHmmssSSSZ",_x000D_
"yyyy-MM-dd'T'HHmmssSSSXXX",_x000D_
_x000D_
// upto seconds_x000D_
"yyyyMMdd'T'HHmmss'Z'",_x000D_
"yyyyMMdd'T'HHmmssZ",_x000D_
"yyyyMMdd'T'HHmmssXXX",_x000D_
_x000D_
"yyyy-MM-dd'T'HHmmss'Z'", _x000D_
"yyyy-MM-dd'T'HHmmssZ",_x000D_
"yyyy-MM-dd'T'HHmmssXXX",_x000D_
_x000D_
// upto minutes_x000D_
"yyyyMMdd'T'HHmm'Z'",_x000D_
"yyyyMMdd'T'HHmmZ",_x000D_
"yyyyMMdd'T'HHmmXXX",_x000D_
_x000D_
"yyyy-MM-dd'T'HHmm'Z'",_x000D_
"yyyy-MM-dd'T'HHmmZ",_x000D_
"yyyy-MM-dd'T'HHmmXXX",_x000D_
_x000D_
//upto hours is already supported by Joda DateTime_x000D_
};_x000D_
}
_x000D_
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");
Apache Jackrabbit uses the ISO 8601 format for persisting dates, and there is a helper class to parse them:
org.apache.jackrabbit.util.ISO8601
Comes with jackrabbit-jcr-commons.
OffsetDateTime.parse ( "2010-01-01T12:00:00+01:00" )
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" ) );
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
.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
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.
Where to obtain the java.time classes?
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval
, YearWeek
, YearQuarter
, and more.
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).
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).
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);
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);
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)
.
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 );
Source: Stackoverflow.com