Java 8 Date and Time: Parse Iso 8601 String Without Colon in Offset

Java 8 Date and Time: parse ISO 8601 string without colon in offset

If you want to parse all valid formats of offsets (Z, ±hh:mm, ±hhmm and ±hh), one alternative is to use a java.time.format.DateTimeFormatterBuilder with optional patterns (unfortunatelly, it seems that there's no single pattern letter to match them all):

DateTimeFormatter formatter = new DateTimeFormatterBuilder()
// date/time
.append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
// offset (hh:mm - "+00:00" when it's zero)
.optionalStart().appendOffset("+HH:MM", "+00:00").optionalEnd()
// offset (hhmm - "+0000" when it's zero)
.optionalStart().appendOffset("+HHMM", "+0000").optionalEnd()
// offset (hh - "Z" when it's zero)
.optionalStart().appendOffset("+HH", "Z").optionalEnd()
// create formatter
.toFormatter();
System.out.println(OffsetDateTime.parse("2022-03-17T23:00:00.000+0000", formatter));
System.out.println(OffsetDateTime.parse("2022-03-17T23:00:00.000+00", formatter));
System.out.println(OffsetDateTime.parse("2022-03-17T23:00:00.000+00:00", formatter));
System.out.println(OffsetDateTime.parse("2022-03-17T23:00:00.000Z", formatter));

All the four cases above will parse it to 2022-03-17T23:00Z.


You can also define a single string pattern if you want, using [] to delimiter the optional sections:

// formatter with all possible offset patterns
DateTimeFormatter formatter = DateTimeFormatter
.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS[xxx][xx][X]");

This formatter also works for all cases, just like the previous formatter above. Check the javadoc to get more details about each pattern.


Notes:

  • A formatter with optional sections like the above is good for parsing, but not for formatting. When formatting, it'll print all the optional sections, which means it'll print the offset many times. So, to format the date, just use another formatter.
  • The second formatter accepts exactly 3 digits after the decimal point (because of .SSS). On the other hand, ISO_LOCAL_DATE_TIME is more flexible: the seconds and nanoseconds are optional, and it also accepts from 0 to 9 digits after the decimal point. Choose the one that works best for your input data.

Cannot parse String in ISO 8601 format, lacking colon in offset, to Java 8 Date

tl;dr

Until bug is fixed:

OffsetDateTime.parse( 
"2018-02-13T10:20:12.120+0000" ,
DateTimeFormatter.ofPattern( "uuuu-MM-dd'T'HH:mm:ss.SSSX" )
)

When bug is fixed:

OffsetDateTime.parse( "2018-02-13T10:20:12.120+0000" )

Details

You are using the wrong classes.

Avoid the troublesome old legacy classes such as Date, Calendar, and SimpleDateFormat. Now supplanted by the java.time classes.

The ZonedDateTime class you used is good, it is part of java.time. But it is intended for a full time zone. Your input string has merely an offset-from-UTC. A full time zone, in contrast, is a collection of offsets in effect for a region at different points in time, past, present, and future. For example, with Daylight Saving Time (DST) in most of North America, the offsets change twice a year growing smaller in the Spring as we shift clocks forward an hour, and restoring to a longer value in the Autumn when we shift clocks back an hour.

OffsetDateTime

For only an offset rather than a time zone, use the OffsetDateTime class.

Your input string complies with the ISO 8601 standard. The java.time classes use the standard formats by default when parsing/generating strings. So no need to specify a formatting pattern.

OffsetDateTime odt = OffsetDateTime.parse( "2018-02-13T10:20:12.120+0000" );

Well, that should have worked. Unfortunately, there is a bug in Java 8 (at least up through Java 8 Update 121) where that class fails to parse an offset omitting the colon between hours and minutes. So the bug bites on +0000 but not +00:00. So until a fix arrives, you have a choice of two workarounds: (a) a hack, manipulating the input string, or (b) define an explicit formatting pattern.

The hack: Manipulate the input string to insert the colon.

String input = "2018-02-13T10:20:12.120+0000".replace( "+0000" , "+00:00" );
OffsetDateTime odt = OffsetDateTime.parse( input );

DateTimeFormatter

The more robust workaround is to define and pass a formatting pattern in a DateTimeFormatter object.

String input = "2018-02-13T10:20:12.120+0000" ;
DateTimeFormatter f = DateTimeFormatter.ofPattern( "uuuu-MM-dd'T'HH:mm:ss.SSSX" );
OffsetDateTime odt = OffsetDateTime.parse( input , f );

odt.toString(): 2018-02-13T10:20:12.120Z

By the way, here is a tip: I have found that with many protocols and libraries, your life is easier if your offsets always have the colon, always have both hours and minutes (even if minutes are zero), and always use a padding zero (-05:00 rather than -5).

DateTimeFormatterBuilder

For a more flexible formatter, created via DateTimeFormatterBuilder, see this excellent Answer on a duplicate Question.

Instant

If you want to work with values that are always in UTC (and you should), extract an Instant object.

Instant instant = odt.toInstant();

ZonedDateTime

If you want to view that moment through the lens of some region’s wall-clock time, apply a time zone.

ZoneId z = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = odt.atZoneSameInstant( z );

See this code run live at IdeOne.com.

All of this has been covered many times in many Answers for many Questions. Please search Stack Overflow thoroughly before posting. You would have discovered many dozens, if not hundreds, of examples.


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.

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?

  • Java SE 8, Java SE 9, Java SE 10, and later

    • Built-in.
    • Part of the standard Java API with a bundled implementation.
    • Java 9 adds some minor features and fixes.
  • Java SE 6 and Java SE 7
    • Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
  • Android
    • Later versions of Android bundle implementations of the java.time classes.
    • For earlier Android (<26), the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….

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.

Parsing ISO-8601 DateTime with offset with colon in Java

The "strange" format in question is ISO-8601 - its very widely used. You can use SimpleDateFormat to reformat it in most way you please:

SimpleDateFormat inFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
DateTime dtIn = inFormat.parse(dateString}); //where dateString is a date in ISO-8601 format
SimpleDateFormat outFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm");
String dtOut = outFormat.format(dtIn);
//parse it into a DateTime object if you need to interact with it as such

will give you the format you mentioned.

Converting ISO 8601-compliant String to java.util.Date

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).

Whats the recommended way to validate ISO8601 datetime string in java

ISO 8601 is so many things, so I am not going to write an exhaustive answer. I am trying to give an overview of the options that you will likely choose from. After that you should research the relevant one/s further. And maybe before doing that you will need to research what ISO 8601 is, what it can sometimes be and what it isn’t.

For many (most) purposes trying to parse your string with built-in means of java.time, the modern Java date and time API, will give you a satisfactory validation, as already stated in the other answers. Options are:

  • Simpler and most often enough: Use the one-arg parse method of the appropriate date-time class from java.time. They generally parse ISO 8601 format and throw a DateTimeParseException if the string is not in ISO 8601 format. Depending on the information required to be present in your string (date and/or time, UTC offset) you may for example use OffsetDateTime.parse() or LocalDate.parse().
  • For special or exotic needs use one of the ISO_XXXX constants of the DateTimeFormatter class.

There are at least three ways that the above may not be enough for you:

  1. The built-it means mentioned parse and accept the most common ISO 8601 variants. For example, OffsetDateTime.parse(CharSequence) requires a colon in the offset from UTC (if it is not Z), as in +07:00. ISO 8601 also allows the offset to be written without a colon, as in +0700. If you need to accommodate variants not covered by the built-in means, building your own DateTimeFormatter will probably be a good and not too complicated solution. You may use the DateTimeForamtter.ofPattern method or a DateTimeFormatterBuilder.
  2. The built-in means sometimes allow values that you may not want to allow. For example in strict ISO 8601 a year is between 1583 and 9999 inclusive. The classes of java.time allow years from -999 999 999 through +999 999 999. Your solution is a range check after parsing. The date-time classes have methods isBefore and isAfter for this.
  3. ISO 8601 includes time intervals for which java.time offers no class nor formatter, including repeating time intervals. If you want to allow these, you may look for a different date and time library (Time4J and ThreeTen Extra come to mind) or you will have to do some more work yourself to validate.

How to compare to a java.sql.Timestamp?

If you can avoid using java.sql.Timestamp, do. That class is poorly designed and long outdated. Since JDBC 4.2 we prefer fetching timestamps as OffsetDateTime or LocalDateTime from our SQL databases.

If you have got a Timestamp from a legacy API that you cannot afford to upgrade just now, convert each to an Instant and compare them using isBefore() or isAfter(). Here’s an example:

    String yourIso8601String = "2020-11-17T02:51:39.375109+07:00";
Timestamp ts = Timestamp.from(Instant.parse("2020-11-16T19:00:00Z"));

OffsetDateTime odt = OffsetDateTime.parse(yourIso8601String);
boolean isBeforeTimestamp = odt.toInstant().isBefore(ts.toInstant());
System.out.println("Is the ISO 8601 string before the Timestamp? " + isBeforeTimestamp);

Output from the example is:

Is the ISO 8601 string before the Timestamp? false

Links

  • Wikipedia article: ISO 8601
  • Oracle tutorial: Date Time explaining how to use java.time.
  • Time4J - Advanced Date, Time, Zone and Interval Library for Java
  • ThreeTen Extra

How to parse a Date with TimeZone with and without colon

Interesting question. You can use parseBest.

  String[] test =  {"2015-03-25T09:24:10.000+0530" , "2015-03-25T09:24:10.000+05:30" };
for (String s : test) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS[Z][XXX]");
TemporalAccessor result = formatter.parseBest(s, ZonedDateTime::from, ZonedDateTime::from);
System.out.println(result);
}

This outputs

2015-03-25T09:24:10+05:30
2015-03-25T09:24:10+05:30

Converting a String to Java Date in ISO 8601 date-time format

tl;dr

OffsetDateTime.parse( "2001-05-03T00:00:00+00:00" )

Avoid legacy date-time classes

Never use SimpleDateFormat, Date, Calendar, etc. These terrible classes were supplanted years ago by the java.time classes.

ISO 8601

trying to convert a string into an ISO 8601 date-time format

String s1 = "2001-05-03T00:00:00+00:00"

Your Question is quite confused. Your input string is in standard ISO 8601 format. The T in the middle separates the year-month-day from the hour-minute-second, and the +00:00 at the end indicates an offset-from-UTC of zero hours and zero minutes, that is, UTC itself. All standard, all proper.

Perhaps you are conflating strings representing date-time values and date-time objects containing date-time values. A date-time object has no “format”; it has its own internally-defined representation of a date-time value. Only text has a format. A date-time class can parse a formatted string as input, and a date-time class can generate a formatted sting as output, but within the date-time there is no format at all.

OffsetDateTime

The java.time classes use the ISO 8601 formats by default when parsing/generating strings representing date-time values. So no need to specify a formatting pattern for such inputs.

OffsetDateTime odt = OffsetDateTime.parse( "2001-05-03T00:00:00+00:00" ) ;  

See this code run live at IdeOne.com.

odt.toString(): 2001-05-03T00:00Z

Z

There is a common abbreviation for an offset of zero: a simple Z letter, meaning UTC, and pronounced “Zulu”. Example: 2019-02-26T00:44:28Z

The Z is quite commonly used. But if for some reason you prefer the numeric +00:00, use DateTimeFormatter options.

COLON character

By the way, you mentioned the COLON character omitted from the offset: +0000. That is actually tolerated by the ISO 8601 standard. But I do not advise it. I have seen libraries and systems break on such inputs. Best to use full-length, hours and minutes, padding zeros, and the colon character: +00:00 rather than +0000, and -07:00 rather than -7.


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.

Where to obtain the java.time classes?

  • Java SE 8, Java SE 9, Java SE 10, Java SE 11, and later - Part of the standard Java API with a bundled implementation.

    • Java 9 adds some minor features and fixes.
  • Java SE 6 and Java SE 7
    • Most of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
  • Android
    • Later versions of Android bundle implementations of the java.time classes.
    • For earlier Android (<26), the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….

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.

Which type of date format it is?? 2018-09-06T10:12:21-0300

What type of date format is it?

This format is one of the ISO 8601 standard, but obviously not for the java.time.format.DateTimeFormatter which considers it a custom format consisting of ISO-standard date and time of day plus an offset from UTC without a separator (colon) between hours and minutes.

And how can I format it to something like that "06 Sep" ???

You need to define two DateTimeFormatters, one for parsing the non-standard input and the other one for outputting day of month and abbreviated month name only. Here's an example:

fun main(args: Array<String>) {
// some non-ISO formatted String
val inputDateTime = "2018-09-06T10:12:21-0300"
// build up a DateTimeFormatter that can parse such a String
val inputParser = DateTimeFormatterBuilder()
// date part uuuu-MM-dd
.append(DateTimeFormatter.ISO_LOCAL_DATE)
.appendLiteral('T') // the T separating date from time
// the time of day part
.append(DateTimeFormatter.ISO_LOCAL_TIME)
// the offset part without a separator between hours and minutes
.appendPattern("X")
// (just for completeness) a locale
.toFormatter(Locale.ENGLISH)
// parse the String to an OffsetDateTime
val offsetDateTime = OffsetDateTime.parse(inputDateTime, inputParser)
// define another formatter for output, make it only use day of month and abbreviated month in English
val outputFormatter = DateTimeFormatter.ofPattern("dd MMM", Locale.ENGLISH)
// print the results
println("$offsetDateTime ---> ${offsetDateTime.format(outputFormatter)}")
}

Example output:

2018-09-06T10:12:21-03:00 ---> 06 Sep


Related Topics



Leave a reply



Submit