This question already has answers here:
Why can't OffsetDateTime parse '2016-08-24T18:38:05.507+0000' in Java 8
(5 answers)
Closed 8 months ago.
I am trying to parse the date which is in the format 2021-01-15T19:00:00+0000. I am not sure which format it is but based on my research I tried with methods below
1. val odt = OffsetDateTime.parse("2021-01-15T19:00:00+0000")
2. val zdt = ZonedDateTime.parse("2021-01-15T19:00:00+0000")
It is logging the exception:
java.time.format.DateTimeParseException: Text '2021-01-15T19:00:00+0000' could not be parsed at index 19
at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1949)
at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1851)
at java.time.ZonedDateTime.parse(ZonedDateTime.java:591)
at java.time.ZonedDateTime.parse(ZonedDateTime.java:576)
tl;dr
OffsetDateTime
.parse(
"2021-01-15T19:00:00+0000" ,
DateTimeFormatter.ofPattern( "uuuu-MM-dd'T'HH:mm:ssxxxx" )
)
.toString()
2021-01-15T19:00Z
OffsetDateTime
Your input has an offset of zero hours-minutes-seconds from UTC, the +0000 portion.
So you should be parsing with OffsetDateTime rather than ZonedDateTime.
ZonedDateTime
The ZonedDateTime class is for a time zone rather than an offset. A time zone is the named history of the past, present, and future changes to the offset used by the people of a particular region, as decided by their politicians. Time zones have a name in format of Continent/Region such as Europe/Paris or America/Edmonton.
Optional COLON in offset
Unfortunately, your input omitted the COLON character from between the hours and minutes of the offset. While optional in the ISO 8601 standard, the parse method expects to find that character.
If you know all the inputs have the same +0000 I would simply perform a string manipulation.
OffsetDateTime odt = OffsetDateTime.parse( "2021-01-15T19:00:00+0000".replace( "+0000" , "+00:00" ) ) ;
If some other offsets might appear, you must specify a formatting pattern.
DateTimeFormatter f = DateTimeFormatter.ofPattern( "uuuu-MM-dd'T'HH:mm:ssxxxx" ) ;
OffsetDateTime odt = OffsetDateTime.parse( input , f ) ;
See this code run live at Ideone.com.
2021-01-15T19:00Z
I would recommend to use the OffsetDateTime with the right offset - that should do the trick!
Related
What type of date format is it?
"2018-09-06T10:12:21-0300"
And how can I format it to something like that "06 Sep" ???
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
This format respects the ISO 8601 standard for representing a date/time with timezone information.
There is enough information to parse this string into an OffsetDateTime, but apparently Java's formatter is a bit strict with respect to the missing separator between hours and minutes in the offset representation (which is technically allowed by the standard). This means a plain OffsetDateTime.parse(text) will throw an exception.
Therefore, you'll to define a custom DateTimeFormatter as explained by #deHaar.
I need to convert a dateTime String to millis and I am using ThreeTenABP for this, but the OffSetDateTime.parse is unable to parse the dateTime String which is for ex. "2020-08-14T20:05:00" and giving the following exception.
Caused by: org.threeten.bp.format.DateTimeParseException:
Text '2020-09-22T20:35:00' could not be parsed:
Unable to obtain OffsetDateTime from TemporalAccessor:
DateTimeBuilder[, ISO, null, 2020-09-22, 20:35], type org.threeten.bp.format.DateTimeBuilder
I have already searched through similar questions but could not find the exact solution.
Below is the code that I am using in Kotlin.
val formatter: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss",
Locale.ROOT)
val givenDateString = event?.eventDateTime
val timeInMillis = OffsetDateTime.parse(givenDateString, formatter)
.toInstant()
.toEpochMilli()
The problem is the missing offset in the String that you are trying to parse to an OffsetDateTime. An OffsetDateTime cannot be created without a ZoneOffset but no ZoneOffset can be derived from this String (one could just guess it's UTC, but guessing is not suitable in such a situation).
You can parse the String to a LocalDateTime (a representation of a date and a time of day without a zone or an offset) and then add / attach the desired offset. You don't even need a custom DateTimeFormatter because your String is of ISO format and can be parsed using the default built-in formatter:
fun main() {
// example String
val givenDateString = "2020-09-22T20:35:00"
// determine the zone id of the device (you can alternatively set a fix one here)
val localZoneId: ZoneId = ZoneId.systemDefault()
// parse the String to a LocalDateTime
val localDateTime = LocalDateTime.parse(givenDateString)
// then create a ZonedDateTime by adding the zone id and convert it to an OffsetDateTime
val odt: OffsetDateTime = localDateTime.atZone(zoneId).toOffsetDateTime()
// get the time in epoch milliseconds
val timeInMillis = odt.toInstant().toEpochMilli()
// and print it
println("$odt ==> $timeInMillis")
}
this example code produces the following output (pay attention to the trailing Z in the datetime representation, that's an offset of +00:00 hours, the UTC time zone, I wrote this code in the Kotlin Playground and it seems to have UTC time zone ;-) ):
2020-09-22T20:35Z ==> 1600806900000
Please note that I tried this with java.time and not with the ThreeTen ABP, which is obsolete to use for many (lower) Android versions now, since there's Android API Desugaring. However, this shouldn't make a difference because your example code threw exactly the same exception when I tried it first, which means ThreeTen is not to blame for this.
I am trying to check if the date has passed more than one day or not.
And I got these errosr while it parses the string.
java.lang.IllegalArgumentException: Pattern ends with an incomplete string literal: uuuu-MM-dd'T'HH:mm:ss'Z
org.threeten.bp.format.DateTimeParseException: Text '2020-04-04T07:05:57+00:00' could not be parsed, unparsed text found at index 19
My data example is here:
val lastDate = "2020-04-04T07:05:57+00:00"
val serverFormat = "uuuu-MM-dd'T'HH:mm:ss'Z"
val serverFormatter =
DateTimeFormatter
.ofPattern(serverFormat)
val serverDateTime =
LocalDateTime
.parse(
lastDate,
serverFormatter
)
.atZone(ZoneId.of("GMT"))
val clientDateTime =
serverDateTime
.withZoneSameInstant(ZoneId.systemDefault())
val timeDiff =
ChronoUnit.DAYS.between(
serverDateTime,
clientDateTime
I've tried with these:
uuuu-MM-dd\'T\'HH:mm:ss\'Z
yyyy-MM-dd\'T\'HH:mm:ss\'Z
uuuu-MM-dd\'T\'HH:mm:ss
uuuu-MM-dd'T'HH:mm:ss'Z
yyyy-MM-dd'T'HH:mm:ss'Z
uuuu-MM-dd'T'hh:mm:ss'Z
yyyy-MM-dd'T'hh:mm:ss'Z
yyyy-MM-dd HH:mm:ss
yyyy-MM-dd HH:mm:ssZ
yyyy-MM-dd'T'HH:mm:ss
yyyy-MM-dd'T'HH:mm:ssZ
yyyy-MM-dd'T'HH:mm:ss
And none of them worked... What is the correct way?
You don’t need any explicit formatter. In Java (because it’s what I can write):
String lastDate = "2020-04-04T07:05:57+00:00";
OffsetDateTime serverDateTime = OffsetDateTime.parse(lastDate);
ZonedDateTime clientDateTime
= serverDateTime.atZoneSameInstant(ZoneId.systemDefault());
System.out.println("serverDateTime: " + serverDateTime);
System.out.println("clientDateTime: " + clientDateTime);
Output in my time zone:
serverDateTime: 2020-04-04T07:05:57Z
clientDateTime: 2020-04-04T09:05:57+02:00[Europe/Copenhagen]
The format of the string from your server is ISO 8601. The classes of java.time parse the most common ISO 8601 variants as their default, that is, without any formatter being specified.
Since the string from your server has a UTC offset, +00:00, and no time zone, like Asia/Seoul, OffsetDateTime is the best and most correct time to use for it. On the other hand, the client time has got time zone, so ZonedDateTime is fine here.
Since server and client time denote the same time, the difference will always be zero:
Duration difference = Duration.between(serverDateTime, clientDateTime);
System.out.println(difference);
PT0S
Read as a period of time of 0 seconds (this too is ISO 8601 format).
If you want to know the difference between the current time and the server time, use now():
Duration difference = Duration.between(serverDateTime, OffsetDateTime.now());
System.out.println(difference);
What went wrong in your code?
First, the UTC offset in your string is +00:00. Neither one format pattern letter Z nor a literal Z will match this. So don’t try that. Second, never give Z as a literal enclosed in single quotes in your format pattern string. When a Z appears as an offset, which is common, you need to parse it as an offset, not as a literal. Third, literal text in a format pattern string needs to have a single quote before and a single quote after it. You are doing it correctly for the T in the middle. If you didn’t mean Z to be a literal, don’t put a single quote before it. If you did mean it to be a literal — as I said, just don’t.
Links
Wikipedia article: ISO 8601
Documentation of the one-arg OffsetDateTime.parse()
From Server I get following values:
epochMillis=1556532279322
iso8601=2019-04-29T10:04:39.322Z
When I do serverTimeDateFormat.parse(iso8601), I get as a result Mon Apr 29 10:04:39 GMT+02:00 2019
and for serverTimeDateFormat.parse(iso8601).time, the result is 1556525079322, which is different from what I get from the server (2 hours behind from UNIX time), while I am in timeZone + 2 hours.
When I format it back with serverTimeDatFormat.format(1556525079322), the result is 2019-04-29T10:04:39.322Z
I understand that SimpleDateFormat is using local timezone, but why is the result 2 hours behind and how can I parse the Date without taking into account timezone? I don't understand the logic of all this.
My code:
private val serverTimeDateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",Locale.ENGLISH)
val iso8601 = "2019-04-29T10:04:39.322Z"
val epochMillis = 1556532279322
serverTimeDateFormat.parse(iso8601).time
The problem lies with the pattern for your SimpleDateFormat. At the end, you have 'Z', which indicates there should be a literal "Z" in the date string to be parsed. However, the "Z" at the end of the date has a special meaning, namely it signifies the UTC timezone. Hence, you should parse it as a timezone designator so that the correct date value will be obtained. You can do this with the pattern XXX (See JavaDocs).
private val serverTimeDateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX",Locale.ENGLISH)
val iso8601 = "2019-04-29T10:04:39.322Z"
print( serverTimeDateFormat.parse(iso8601).time ) // 1556532279322
Runnable example on pl.kotl.in
Addendum: While the above code should work for you, if at all possible, you should consider adding ThreeTen Android Backport to your project. This will give you access to the newer time classes added by JSR310 to Java/Kotlin (Also available by default in Android API >=26). The classes have generally easier API, and use ISO8601 by default, so you wouldn't need any formatter at all:
print( ZonedDateTime.parse(iso8601).toInstant().toEpochMilli() )
Avoid legacy date-time classes
You are using terrible date-time classes that were supplanted years ago by the modern java.time classes defined in JSR 310.
Date::toString lies
why is the result 2 hours behind
It is not actually two hours behind.
The problem is that while a java.util.Date represents a moment in UTC, its toString method dynamically applies the JVM’s current default time zone while generating the text representing the value of the date-time object. While well-intentioned, this anti-feature confusingly creates the illusion of the Date object having that time zone.
In other words, Date::toString lies. One of many poor design decisions found in these legacy classes. And one of many reasons to never use these legacy classes.
java.time
Instant
Parse your count of milliseconds since the epoch reference of first moment of 1970 in UTC as a Instant.
Instant instant = Instant.ofEpochMilli( 1556532279322 );
Your other input, a standard ISO 8601 string, can also be parsed as an instant.
Instant instant = Instant.parse( "2019-04-29T10:04:39.322Z" ) ;
ZonedDateTime
To see the same moment through the wall-clock time used by the people of a particular region (a time zone), apply a ZoneId to get a ZonedDateTime object.
ZoneId z = ZoneId.of( "America/Montreal" ) ;
ZonedDateTime zdt = instant.atZone( z ) ) ;
Both the instant and zdt objects represent the same simultaneous moment. Two ways of reading the same moment, as two people conversing on the phone in Iceland and Québec would each see a different time on the clock on the wall while glancing simultaneously.
I am working on ThreeTenABP library to parse date and time. However, it is crashing. API I consume sends DateTime like;
2018-10-20T14:27:47.3949709+03:00
This is the way I try to parse;
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_OFFSET_DATE_TIME)
.append(DateTimeFormatter.ofPattern("yyyy-MM-dd"))
.toFormatter();
Timber.d(LocalDateTime.parse("2018-10-20T14:27:47.3949709+03:00", formatter).toString());
I am getting below error:
Text '2018-10-20T14:27:47.3949709+03:00' could not be parsed at index
33
Thanks in advance.
Explanation of the error message:
2018-10-20T14:27:47.3949709+03:00 is 33 chars long, therefore
Text '2018-10-20T14:27:47.3949709+03:00' could not be parsed at index 33
means it expected a 34th char which is not there (it's 0 indexed).
Problem
The way you defined the Formatter it would accept 2018-10-20T14:27:47.3949709+03:002018-10-20`
Solution:
To overcome this you can either drop the .append(DateTimeFormatter.ofPattern("yyyy-MM-dd")
Or define a Formatter that accepts both formats with startOptional and endOptional
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.optionalStart()
.append(DateTimeFormatter.ISO_OFFSET_DATE_TIME)
.optionalEnd()
.optionalStart()
.append(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
.optionalEnd().toFormatter();
You can see the example working at https://ideone.com/RDVHYG
Side note: "yyyy-MM-dd" does not yield enough information for a LocalDateTime therefore I added "HH:mm"