Reputation: 7543
I am having a hard time converting an ISO 8601 formatted String to a java.time.LocalDateTime which is in UTC.
More specific, I am trying to write an XMLAdapter for which you can enter the various ISO 8601 dataformats as a String (i.e. 2002-09-24
, 2011-03-22T13:30
, 2015-05-24T12:25:15Z
, 2015-07-28T11:11:15.321+05:30
) and which outputs a LocalDateTime in UTC and visa versa.
The system stores all it's Date and Time information internal in UTC times. When a user requests a Date or Time it is represented to the user based on their own ZoneId.
Upvotes: 6
Views: 5100
Reputation: 6280
There are various formatters defined on DateTimeFormatter which do the job.
For example:
TemporalAccessor accessor= DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse("2020-07-14T21:56:00Z");
ZonedDateTime from = ZonedDateTime.from(accessor);
OR with a different offset format still in iso 8601 spec:
TemporalAccessor accessor= DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse("2020-07-14T21:56:00+00:00");
ZonedDateTime from = ZonedDateTime.from(accessor);
Note: This only works for the 'extended' iso 8601 formats. I didn't find support for the simplified one (without -'s for example 20200714T215600Z) but I don't think that's a big deal. I just wont support the simplified format in my API. If anyone does find a way to support the iso 8601 simplified date format (not just the extended) that is not a lot of work and is happy to leave a solution that would be great :)
Upvotes: 1
Reputation: 340230
Instant.parse( "2015-05-24T12:25:15Z" )
.atZone(
ZoneId.of( "America/Montreal" )
)
.toString()
2015-05-24T08:25:15-04:00[America/Montreal]
ZonedDateTime
vs LocalDateTime
Your Question contradicts itself, with the title asking for a ZonedDateTime
and the body asking for a LocalDateTime
. Those are two very different beasts. One (ZonedDateTime
) is a specific moment on the timeline, the other (LocalDateTime
) is only a vague idea about possible moments but is not a specific moment.
For example, the LocalDateTime
of Christmas starting this year is 2017-12-25T00:00:00
but that has no meaning until you apply a time zone as Santa delivers first to the islands of Kiribati at their midnight (the first midnight on Earth), then on to New Zealand at their later midnight, then on to Australia at their later midnight, and so on moving westward toward successive midnights.
Also, you Question seems confused about exactly what is the input and what is the output.
a java.time.LocalDateTime which is in UTC.
This is a contradiction in terms. The LocalDateTime
class lacks any concept of time zone or offset-from-UTC. So this class cannot be used to represent a moment such as a UTC value. For a moment in UTC, use either Instant
or OffsetDateTime
classes.
Instant
For an input like 2015-05-24T12:25:15Z
, that represents a moment on the timeline in UTC (Z
is short for Zulu
and means UTC). To represent that, use the Instant
class. The Instant
class represents a moment on the timeline in UTC with a resolution of nanoseconds (up to nine (9) digits of a decimal fraction).
Instant instant = Instant.parse( "2015-05-24T12:25:15Z" );
Generally you should be working in UTC for most business logic, data storage, data exchange, and database. So for that Instant
is all you need.
ZonedDateTime
If you need to adjust into a time zone, such as for presentation to a user, apply a ZoneId
to get a ZonedDateTime
.
Specify a proper time zone name in the format of continent/region
, such as America/Montreal
, Africa/Casablanca
, or Pacific/Auckland
. Never use the 3-4 letter abbreviation such as EST
or IST
as they are not true time zones, not standardized, and not even unique(!).
ZoneId z = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = instant.atZone( z );
LocalDate
If you have a date-only value, parse as a LocalDate
object.
LocalDate localDate = LocalDate.parse( "2002-09-24" );
To get the first moment of the day for that date, specify a time zone. For any given moment, the date varies around the world by zone.
Also, do not assume the day starts at the time of 00:00:00
. Anomalies such as Daylight Saving Time (DST) may cause the first moment to be something like 01:00:00
. Let java.time determine the first moment’s time-of-day.
ZonedDateTime zdt = localDate.atStartOfDay( z );
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?
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.
Upvotes: 6
Reputation: 5496
Edit: Basils answer below should be marked correct. https://stackoverflow.com/a/43083698/348956
As the name suggests LocalDateTime
holds both Date and Time. The first example of a date string you have in your question for example only holds information about the date, therefore you cannot parse this directly into a LocalDateTime
. What you could do there is to first parse it into a LocalDate
and by setting the time on that object get a LocalDateTime
.
LocalDateTime localDateTime = LocalDate.parse("2002-09-24").atStartOfDay();
All Date and Time objects have a parse method like LocalDate
which can take a certain string format. These formats are different ISO standard formats specified in DateTimeFormatter
For formatting custom datetime strings into Temporal
objects use DateTimeFormatter and specify a custom pattern.
Upvotes: 1
Reputation: 7543
public class LocalDateTimeXmlAdapter extends XmlAdapter<String, LocalDateTime> {
private static final Pattern ZONE_PATTERN = Pattern.compile("T.*(\\+|\\-|Z)");
@Override
public LocalDateTime unmarshal(String isoDateTime) throws Exception {
LocalDateTime utcDateTime;
if (ZONE_PATTERN.matcher(isoDateTime).matches()) {
OffsetDateTime offsetDateTime = OffsetDateTime.parse(isoDateTime, DateTimeFormatter.ISO_DATE_TIME);
ZoneOffset offset = offsetDateTime.getOffset();
utcDateTime = offsetDateTime.toLocalDateTime().plusSeconds(offset.getTotalSeconds());
} else {
LocalDateTime localDateTime = LocalDateTime.parse(isoDateTime, DateTimeFormatter.ISO_DATE_TIME);
ZoneId zoneId = ZoneId.systemDefault(); // TODO: Get ZoneId from userProfile
ZoneOffset offset = ZonedDateTime.now(zoneId).getOffset();
utcDateTime = localDateTime.minusSeconds(offset.getTotalSeconds());
}
return utcDateTime;
}
@Override
public String marshal(LocalDateTime utcDateTime) throws Exception {
ZoneId zoneId = ZoneId.systemDefault(); // TODO: Get ZoneId from userProfile
ZoneOffset offset = ZonedDateTime.now(zoneId).getOffset();
return utcDateTime.plusSeconds(offset.getTotalSeconds()).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
}
}
Upvotes: -1