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.
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.
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
How to parse offset with colon using DateTimeFormatter?
You should use the times X
(yyyy-MM-dd HH:mm:ss XXX
):
String timeStamp = "2020-01-31 12:13:14 +03:00";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss XXX");
ZonedDateTime tmpTimestamp = ZonedDateTime.parse(timeStamp, formatter);
From the docs:
Offset X and x: This formats the offset based on the number of pattern letters.
One letter outputs just the hour, such as '+01', unless the minute is non-zero in which case the minute is also output, such as '+0130'.
Two letters outputs the hour and minute, without a colon, such as '+0130'.
Three letters outputs the hour and minute, with a colon, such as '+01:30'.
Four letters outputs the hour and minute and optional second, without a colon, such as '+013015'.
Five letters outputs the hour and minute and optional second, with a colon, such as '+01:30:15'.
Six or more letters throws IllegalArgumentException.
Pattern letter 'X' (upper case) will output 'Z' when the offset to be output would be zero, whereas pattern letter 'x' (lower case) will output '+00', '+0000', or '+00:00'.
Alternative you can use five letters (XXXXX
) and you also can use ZZZ
or ZZZZZ
instead of XXX
or XXXXX
.
How to solve parse exception when timezone has colon?
The java.util
Date-Time API and their formatting API, SimpleDateFormat
are outdated and error-prone. It is recommended to stop using them completely and switch to the modern Date-Time API*.
Solution using java.time
, the modern API:
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.stream.Stream;
public class Main {
public static void main(String[] args) {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("u-M-d'T'H:m:s[XXX][XX][X]", Locale.ENGLISH);
//Test
Stream.of(
"2021-06-06T04:54:41-04:00",
"2021-06-06T04:54:41-0400",
"2021-06-06T04:54:41-04",
"2021-06-06T04:54:41Z"
).forEach(s -> System.out.println(OffsetDateTime.parse(s, dtf)));
}
}
Output:
2021-06-06T04:54:41-04:00
2021-06-06T04:54:41-04:00
2021-06-06T04:54:41-04:00
2021-06-06T04:54:41Z
ONLINE DEMO
Check How to use OffsetDateTime
in JDBC?.
Learn more about java.time
, the modern Date-Time API* from Trail: Date Time.
Solution using legacy API:
SimpleDateFormat
does not have a feature to specify optional patterns, the way we do, using the square bracket, with DateTimeFormatter
. In this case, you can create multiple instances of SimpleDateFormat
and try with each one.
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class Main {
public static void main(String[] args) {
SimpleDateFormat sfdArr[] = {
new SimpleDateFormat("y-M-d'T'H:m:sXXX", Locale.ENGLISH),
new SimpleDateFormat("y-M-d'T'H:m:sXX", Locale.ENGLISH),
new SimpleDateFormat("y-M-d'T'H:m:sX", Locale.ENGLISH)
};
String []strDateTimeArr = {
"2021-06-06T04:54:41-04:00",
"2021-06-06T04:54:41-0400",
"2021-06-06T04:54:41-04",
"2021-06-06T04:54:41Z"
};
for(String s : strDateTimeArr) {
Date date = null;
for(SimpleDateFormat sdf : sfdArr) {
try {
date = sdf.parse(s);
}catch(ParseException e) {
//...
}
}
System.out.println(date);
}
}
}
ONLINE DEMO
* For any reason, if you have to stick to Java 6 or Java 7, you can use ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7. If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and How to use ThreeTenABP in Android Project.
Formatting ISO 8601 date with a colon seperator
You can always use a StringBuilder:
new StringBuilder(
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
.format(date))
.insert(22,':')
.toString();
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).
Related Topics
Getting Rsa Private Key from Pem Base64 Encoded Private Key File
Adding Elements to a Collection During Iteration
Get Query from Java.Sql.Preparedstatement
Call "Java -Jar Myfile.Jar" with Additional Classpath Option
Sort a Two Dimensional Array Based on One Column
When to Use Abstract Class or Interface
Javafx "Location Is Required." Even Though It Is in the Same Package
@Autowired Bean Is Null When Referenced in the Constructor of Another Bean
What Does Swingutilities.Invokelater Do
How to Remove Duplicates from a List
What Is the Equivalent Lambda Expression for System.Out::Println
Parsing Iso-8601 Datetime with Offset with Colon in Java
Using Sleep() for a Single Thread
Main Thread VS. UI Thread in Java
Only One Component Shows Up in Jframe
Jackson with JSON: Unrecognized Field, Not Marked as Ignorable
Java: Syntax and Meaning Behind "[B@1Ef9157"? Binary/Address