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 aDateTimeParseException
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 useOffsetDateTime.parse()
orLocalDate.parse()
. - For special or exotic needs use one of the
ISO_XXXX
constants of theDateTimeFormatter
class.
There are at least three ways that the above may not be enough for you:
- 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 notZ
), 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 ownDateTimeFormatter
will probably be a good and not too complicated solution. You may use theDateTimeForamtter.ofPattern
method or aDateTimeFormatterBuilder
. - 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
andisAfter
for this. - 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 DateTimeFormatter
s, 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
Java Inetaddress.Getlocalhost(); Returns 127.0.0.1 ... How to Get Real Ip
Jtable Model Listener Detects Inserted Rows Too Soon (Before They Are Drawn)
Is There a Fixed Sized Queue Which Removes Excessive Elements
Java Comparator Class to Sort Arrays
How to Avoid Constructor Code Redundancy in Java
Maven Is Not Working in Java 8 When Javadoc Tags Are Incomplete
What Is the Default Access Modifier in Java
Jersey Stopped Working with Injectionmanagerfactory Not Found
Spring Data JPA Update @Query Not Updating
Autocomplete with Java , Redis, Elastic Search , Mongo
@Aspectj Pointcut for All Methods of a Class with Specific Annotation
How to Remove a Cookie in a Java Servlet
How to Sort an Arraylist of Objects by a Property
Executors.Newcachedthreadpool() Versus Executors.Newfixedthreadpool()