Parsing of ISO 8601 Date String in Java [duplicate] - java

This question already has answers here:
Cannot parse String in ISO 8601 format, lacking colon in offset, to Java 8 Date
(3 answers)
Closed 5 years ago.
We try to parse the following ISO 8601 DateTime String with timezone offset:
final String input = "2022-03-17T23:00:00.000+0000";
OffsetDateTime.parse(input);
LocalDateTime.parse(input, DateTimeFormatter.ISO_OFFSET_DATE_TIME);
Both approaches fail (which makes sense as OffsetDateTime also use the DateTimeFormatter.ISO_OFFSET_DATE_TIME) because of the colon in the timezone offset.
java.time.format.DateTimeParseException: Text '2022-03-17T23:00:00.000+0000' could not be parsed at index 23
But according to Wikipedia there are 4 valid formats for a timezone offset:
<time>Z
<time>±hh:mm
<time>±hhmm
<time>±hh
Other frameworks/languages can parse this string without any issues, e.g. the Javascript Date() or Jacksons ISO8601Utils (they discuss this issue here)
Now we could write our own DateTimeFormatter with a complex RegEx, but in my opinion the java.time library should be able to parse this valid ISO 8601 string by default as it is a valid one.
For now we use Jacksons ISO8601DateFormat, but we would prefer to use the official date.time library to work with. What would be your approach to tackle this issue?

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.

You don't need to write a complex regex - you can build a DateTimeFormatter that will work with that format easily:
DateTimeFormatter formatter =
DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSX", Locale.ROOT);
OffsetDateTime odt = OffsetDateTime.parse(input, formatter);
That will also accept "Z" instead of "0000". It will not accept "+00:00" (with the colon or similar. That's surprising given the documentation, but if your value always has the UTC offset without the colon, it should be okay.

I wouldn't call it a solution but a workaround. SimpleDateFormat's Z template supports the timezone-syntax you showed, so you can do something like this:
final String input = "2022-03-17T23:00:00.000+0000";
try {
OffsetDateTime.parse(input);
LocalDateTime.parse(input, DateTimeFormatter.ISO_OFFSET_DATE_TIME);
}
catch (DateTimeParseException e) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SZ", Locale.GERMANY);
sdf.parse(input);
}
You're still using official libraries shipped with the JVM. One isn't part of the date.time-library, but still ;-)

Since it is without colon, can you use your own format string :
final String input = "2022-03-17T23:00:00.000+0000";
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
Date parsed = df.parse(input);
System.out.println(parsed);

Related

Parse time format yyyy-MM-dd'T'HH:mm:ss.SSS'+' [duplicate]

This question already has answers here:
Why can't OffsetDateTime parse '2016-08-24T18:38:05.507+0000' in Java 8
(5 answers)
Closed 3 months ago.
I am working with jira api and in one of request I get the response with date field in format like this: 2022-10-26T09:34:00.000+0000. I need to convert this to LocalDate but I do not know how to to this with this strange format.
Here are some of formats I already tried:
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS")
DateTimeFormatter.ISO_LOCAL_DATE_TIME
but both cannot deserialize this + sign at the end of the date.
Text '2022-10-27T09:34:00.000+0000' could not be parsed, unparsed text found at index 24
You have to add the timezone:
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
Checkout the documentation
https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html
The error message is telling you that your DateTimeFormatter is not able to parse the zone offset of +0000 at the end of your String. That's because a DateTimeFormatter.ISO_LOCAL_DATE_TIME just does not expect an offset, it is supposed to only parse
year
month of year
day of month
hour of day
minute of hour
second of minute
fraction of second (and nanos)
That's it! No offset! No zone!.
But that also means it could perfectly parse everything until this offset in your example!
However, the String is nearly formatted as an OffsetDateTime, but unfortunately its ISO formatter expects an offset where hours and minutes are separated by a colon, e.g. +00:00, and your String does not have one.
java.time grants you the possibility of building a custom formatter based on an exisiting one. That's why I mentioned above that everything apart from the offset could be parsed with a DateTimeFormatter.ISO_LOCAL_DATE_TIME. You can take that one and attach an offset-x pattern which then parses your unseparated offset. Let's call it extending an existing formatter, here's an example:
public static void main(String[] args) {
// your example String
String someTimes = "2022-10-26T09:34:00.000+0000";
// build a suitable formatter by extending an ISO-formatter
DateTimeFormatter dtf = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
.appendPattern("xxxx")
.toFormatter(Locale.ENGLISH);
// then parse
LocalDate localDate = LocalDate.parse(someTimes, dtf);
// and print
System.out.println(localDate);
}
Output:
2022-10-26
It is always a good idea to create a formatter with a Locale, but in most cases where exclusively numerical values are to be parsed, a formatter without a Locale might be sufficient. That counts for DateTimeFormatter.ofPattern(String, Locale) as well.

Does java.time bring default DateTimeFormatters for patterns such as "yyyy-MM-dd", "HH:mm:ss" or similar variants?

I have found myself repeating over and over again the same code pattern. I'm using some sort of date / time object (Java 8 API) and I end up having to roll out my own DateTimeFormatter:
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
DateTimeFormatter.ofPattern("yyyy-MM-dd")
DateTimeFormatter.ofPattern("HH:mm:ss")
which is fine but.. it gets me wondering whether there are not already pre-made ones done for me? I took a glance at the ones provided in the DateTimeFormatter singleton but they don't seem to be of much help.
I think the documentation of DateTimeFormatter is quite clear. The concrete patterns you have mentioned are supported by following predefined constants:
yyyy-MM-dd => ISO_LOCAL_DATE
HH:mm:ss => ISO_LOCAL_TIME
Only your first example "yyyy-MM-dd HH:mm:ss" is not matched by any predefined formatter so you can simply construct it by specifying the pattern (as you have already done). ISO_LOCAL_DATE_TIME is very similar but replaces the space by ISO-literal "T".
yyyy-MM-dd HH:mm:ss: assemble from built-in parts
As a supplement to the good answer by Meno Hochschild.
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
While not built in as it stands (even though occurring regularly), my preference is for assembling it from the two predefined formatters mentioned in that other answer rather than writing my own format pattern string:
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_LOCAL_DATE)
.appendLiteral(' ')
.append(DateTimeFormatter.ISO_LOCAL_TIME)
.toFormatter();
ISO_LOCAL_TIME accepts and prints other formats than HH:mm:ss, though. On one hand it accepts a format without any seconds (HH:mm), on the other hand seconds with a fraction of up to 9 decimals (for example HH:mm:ss.SSSSSSSSS). It is often an advantage; but if you want to do strict validation of the parsed string or you want string output in a consistent format, it is a drawback, of course.
Or use String.replace(char, char)
Others would format and parse LocalDateTime objects to and from yyyy-MM-dd HH:mm:ss format without any explicit formatter, relying on its resemblance to the ISO 8601 format also mentioned by Meno Hochschild.
String str = "2020-04-17 20:23:30";
LocalDateTime ldt = LocalDateTime.parse(str.replace(' ', 'T'));
System.out.println(ldt);
Output:
2020-04-17T20:23:30
And the other way:
LocalDateTime now = LocalDateTime.now(ZoneId.of("Asia/Tehran"));
String str = now.truncatedTo(ChronoUnit.SECONDS)
.toString()
.replace('T', ' ');
System.out.println(str);
2020-04-18 09:35:38

JAX-RS parse date in response [duplicate]

This question already has answers here:
Cannot parse String in ISO 8601 format, lacking colon in offset, to Java 8 Date
(3 answers)
Closed 5 years ago.
We try to parse the following ISO 8601 DateTime String with timezone offset:
final String input = "2022-03-17T23:00:00.000+0000";
OffsetDateTime.parse(input);
LocalDateTime.parse(input, DateTimeFormatter.ISO_OFFSET_DATE_TIME);
Both approaches fail (which makes sense as OffsetDateTime also use the DateTimeFormatter.ISO_OFFSET_DATE_TIME) because of the colon in the timezone offset.
java.time.format.DateTimeParseException: Text '2022-03-17T23:00:00.000+0000' could not be parsed at index 23
But according to Wikipedia there are 4 valid formats for a timezone offset:
<time>Z
<time>±hh:mm
<time>±hhmm
<time>±hh
Other frameworks/languages can parse this string without any issues, e.g. the Javascript Date() or Jacksons ISO8601Utils (they discuss this issue here)
Now we could write our own DateTimeFormatter with a complex RegEx, but in my opinion the java.time library should be able to parse this valid ISO 8601 string by default as it is a valid one.
For now we use Jacksons ISO8601DateFormat, but we would prefer to use the official date.time library to work with. What would be your approach to tackle this issue?
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.
You don't need to write a complex regex - you can build a DateTimeFormatter that will work with that format easily:
DateTimeFormatter formatter =
DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSX", Locale.ROOT);
OffsetDateTime odt = OffsetDateTime.parse(input, formatter);
That will also accept "Z" instead of "0000". It will not accept "+00:00" (with the colon or similar. That's surprising given the documentation, but if your value always has the UTC offset without the colon, it should be okay.
I wouldn't call it a solution but a workaround. SimpleDateFormat's Z template supports the timezone-syntax you showed, so you can do something like this:
final String input = "2022-03-17T23:00:00.000+0000";
try {
OffsetDateTime.parse(input);
LocalDateTime.parse(input, DateTimeFormatter.ISO_OFFSET_DATE_TIME);
}
catch (DateTimeParseException e) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SZ", Locale.GERMANY);
sdf.parse(input);
}
You're still using official libraries shipped with the JVM. One isn't part of the date.time-library, but still ;-)
Since it is without colon, can you use your own format string :
final String input = "2022-03-17T23:00:00.000+0000";
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
Date parsed = df.parse(input);
System.out.println(parsed);

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

This question already has answers here:
Cannot parse String in ISO 8601 format, lacking colon in offset, to Java 8 Date
(3 answers)
Closed 5 years ago.
We try to parse the following ISO 8601 DateTime String with timezone offset:
final String input = "2022-03-17T23:00:00.000+0000";
OffsetDateTime.parse(input);
LocalDateTime.parse(input, DateTimeFormatter.ISO_OFFSET_DATE_TIME);
Both approaches fail (which makes sense as OffsetDateTime also use the DateTimeFormatter.ISO_OFFSET_DATE_TIME) because of the colon in the timezone offset.
java.time.format.DateTimeParseException: Text '2022-03-17T23:00:00.000+0000' could not be parsed at index 23
But according to Wikipedia there are 4 valid formats for a timezone offset:
<time>Z
<time>±hh:mm
<time>±hhmm
<time>±hh
Other frameworks/languages can parse this string without any issues, e.g. the Javascript Date() or Jacksons ISO8601Utils (they discuss this issue here)
Now we could write our own DateTimeFormatter with a complex RegEx, but in my opinion the java.time library should be able to parse this valid ISO 8601 string by default as it is a valid one.
For now we use Jacksons ISO8601DateFormat, but we would prefer to use the official date.time library to work with. What would be your approach to tackle this issue?
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.
You don't need to write a complex regex - you can build a DateTimeFormatter that will work with that format easily:
DateTimeFormatter formatter =
DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSX", Locale.ROOT);
OffsetDateTime odt = OffsetDateTime.parse(input, formatter);
That will also accept "Z" instead of "0000". It will not accept "+00:00" (with the colon or similar. That's surprising given the documentation, but if your value always has the UTC offset without the colon, it should be okay.
I wouldn't call it a solution but a workaround. SimpleDateFormat's Z template supports the timezone-syntax you showed, so you can do something like this:
final String input = "2022-03-17T23:00:00.000+0000";
try {
OffsetDateTime.parse(input);
LocalDateTime.parse(input, DateTimeFormatter.ISO_OFFSET_DATE_TIME);
}
catch (DateTimeParseException e) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SZ", Locale.GERMANY);
sdf.parse(input);
}
You're still using official libraries shipped with the JVM. One isn't part of the date.time-library, but still ;-)
Since it is without colon, can you use your own format string :
final String input = "2022-03-17T23:00:00.000+0000";
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
Date parsed = df.parse(input);
System.out.println(parsed);

How do I convert an ISO 8601 date time String to java.time.LocalDateTime?

I am reading data from Wikidata. They represent their point in time property, P585 using ISO 8601 spec. However, the same beings with a +.
If I were using Joda then converting the String to joda dateTime would have been very simple.
new DateTime(dateTime, DateTimeZone.UTC);
However, when I do LocalDateTime.parse("+2017-02-26T00:00:00Z") I get an error saying can't parse the character at index 0. Is there a reason for this in Java 8. Joda does it pretty easily without any errors.
I also tried LocalDateTime.parse("+2017-02-26T00:00:00Z", DateTimeFormatter.ISO_DATE_TIME) but in vain.
How do we get around the Plus sign without having to remove it by string manipulation?
Java's DateTimeFormatter class may be of help. Here is some sample code that I believe addressses your problem (or at least gives you something to think about):
class DateTimeTester {
---
public static void main(String[] args) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("+yyyy-MM-dd'T'HH:mm:ss'Z'")
.withZone(ZoneId.of("UTC"));
LocalDateTime date = LocalDateTime.parse("+2017-02-26T01:02:03Z", formatter);
System.out.println(date);
}
}
Refer to the section called Patterns for Formatting and Parsing in the javadoc for DateTimeFormatter for details about the format-string passed to DateTimeFormatter.ofPattern().
Something to note:
Wikidata's list of available data types says about the time data type:
"explicit value for point in time, represented as a timestamp resembling ISO 8601, e.g. +2013-01-01T00:00:00Z. The year is always signed and padded to have between 4 and 16 digits.".
So Wikidata's time data type only resembles ISO 8601.
If your code needs to handle negative years (before year 0), you will need to adjust my suggested solution accordingly.
Unfortunately, the accepted answer is wrong for three reasons:
The given date-time string +2017-02-26T00:00:00Z does not represent a LocalDateTime. It represents an OffsetDateTime.
It uses a + sign with the pattern which means the pattern will fail to parse a date-time with a negative year.
'Z' is not the same as Z.
You can create the required DateTimeFormatter using the DateTimeFormatterBuilder as shown below:
class Main {
public static void main(String[] args) {
DateTimeFormatter parser = new DateTimeFormatterBuilder()
.appendValue(ChronoField.YEAR, 4, 4, SignStyle.ALWAYS)
.appendPattern("-MM-dd'T'HH:mm:ssXXX")
.toFormatter(Locale.ENGLISH);
OffsetDateTime odt = OffsetDateTime.parse("+2017-02-26T00:00:00Z", parser);
System.out.println(odt);
// If you want a LocalDateTime, you can get it from `odt`
LocalDateTime ldt = odt.toLocalDateTime();
System.out.println(ldt);
}
}
Output:
2017-02-26T00:00Z
2017-02-26T00:00
ONLINE DEMO
Learn more about the modern Date-Time API from Trail: Date Time.
A quick solution can be :
LocalDateTime.parse(yourDate, DateTimeFormatter.ISO_DATE_TIME);
Don't forget to manage the DateTimeParseException in the case of a non supported date format 😉

Categories