idk
idk

Reputation: 49

Convert OffSetDateTime String to ZonedDateTime Java

I have string of the pattern "yyyy-MM-dd'T'HH:mm:ssZ" which I want to convert to ZonedDateTime format using Java.

Input String Example: "2019-11-23T10:32:15+12:24"
Output: ZonedDateTime

Edit: I have tried this but it does not work.

   ZonedDateTime convertToZonedDateTime(final String source) {
    final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    Date date = null;
    try {
        date = dateFormat.parse(source);
    } catch (ParseException e) {
        e.printStackTrace();
    }
    return ZonedDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());

}

I have this solution which works for string "2018-04-05 19:58:55" produces output 2018-04-05T19:58:55+05:30[Asia/Kolkata] but when I change the pattern in function to "yyyy-MM-dd'T'HH:mm:ssZ" and input string to 2019-11-23T10:32:15+12:24 it does not work due to ParseException: Unparsable data.

I need ZonedDateTime format for an API which expects the input time in that format.

Upvotes: 1

Views: 4994

Answers (3)

Anonymous
Anonymous

Reputation: 86280

It’s unclear why you think you want a ZonedDateTime, and if you do, in which time zone. A bit of the following has been said already, but I would like to give you three suggestions to choose from:

  1. You don’t need a ZonedDateTime. An OffsetDateTime fits your string better.
  2. If you want a ZonedDateTime in your default time zone, which makes sense, use OffsetDateTime.atZoneSameInstant() (as in the answer by Basil Bourque).
  3. If you just want a ZonedDateTime representation of your string, the one-arg ZonedDateTime.parse() parses it directly.

Use OffsetDateTime

Your string contains an offset, +12:34, and not a time zone, like Pacific/Galapagos. So OffsetDateTime is more correct to represent its content.

    String inputStringExample = "2019-11-23T10:32:15+12:24";
    OffsetDateTime dateTime = OffsetDateTime.parse(inputStringExample);
    System.out.println(dateTime);

Output from this snippet is:

2019-11-23T10:32:15+12:24

I agree with the comment by Basil Bourque, the offset of +12:24 doesn’t look like a real-world UTC offset, but it’s fine for a Stack Overflow example. In 2019 most offsets are on a whole hour and the rest generally on a whole quarter of an hour, so 24 minutes is not used. Historic offsets include many with both minutes and seconds.

I am exploiting the fact that your string is in ISO 8601 format. The classes of java.time parse the most common ISO 8601 variants as their default, that is, without any explicit formatter. Which is good because writing a format pattern string is always error-prone.

Use OffsetDateTime.atZoneSameInstant()

Your call to ZoneId.systemDefault() in the code in the question seems to suggest that you want a ZonedDateTime in your default time zone. On one hand this use of ZonedDateTime seems reasonable and sound. On the other hand relying on ZoneId.systemDefault() is shaky since the default time zone of your JVM can be changed at any time by another part of your program or any ther program running in the same JVM.

    ZonedDateTime dateTime = OffsetDateTime.parse(inputStringExample)
            .atZoneSameInstant(ZoneId.systemDefault());
    System.out.println(dateTime);

Output in my time zone:

2019-11-22T23:08:15+01:00[Europe/Copenhagen]

Parse directly

If you just need a ZonedDateTIme for an API that requires one (for most purposes a poor design), just parse your string into one:

    ZonedDateTime dateTime = ZonedDateTime.parse(inputStringExample);

2019-11-23T10:32:15+12:24

Output is indistinguishable from the one we got from OffsetDateTime, but you have got the required type now.

Stay far away from SimpleDateFormat and Date

In the code in your question you tried to use SimpleDateFormat for parsing your string. Since you can use java.time, the modern Java date and time API, stick to it and forget everything about the old date and time classes. The modern API gives you all the functionality you need. In case we had needed a formatter for parsing, the modern DateTimeFormatter would have been the class to use.

What went wrong in your code?

… it does not work due to ParseException: Unparsable data.

Z in your format pattern string is for RFC 822 time zone offset. This is without colon and would have parsed +1224, but not +12:24.

Link

Upvotes: 0

Basil Bourque
Basil Bourque

Reputation: 338574

tl;dr

OffsetDateTime                       // Represent a moment as a date with time-of-day in the context of an offset-from-UTC (a number of hours-minutes-seconds).
.parse(                              // Parse text into a date-time object.
    "2019-11-23T10:32:15+12:24"      // The offset of +12:24 looks suspicious, likely an error.
)                                    // Returns an `OffsetDateTime` object.

Semantically, we are done at this point with a OffsetDateTime object in hand.

But you claim to be using an API that demands a ZoneDateTime object. We have no known time zone to apply, so let’s apply UTC (an offset of zero hours-minutes-seconds).

OffsetDateTime                       // Represent a moment as a date with time-of-day in the context of an offset-from-UTC (a number of hours-minutes-seconds).
.parse(                              // Parse text into a date-time object.
    "2019-11-23T10:32:15+12:24"      // The offset of +12:24 looks suspicious, likely an error.
)                                    // Returns an `OffsetDateTime` object.
.atZoneSameInstant(                  // Convert from `OffsetDateTime` to `ZonedDateTime` by applying a time zone.
    ZoneOffset.UTC                   // This constant is a `ZoneOffset` object, whose class extends from `ZoneId`. So we can use it as a time zone, though semantically we are making a mess. 
)                                    // Returns a `ZonedDateTime` object.
.toString()                          // Generate text in standard ISO 8601 format.

See this code run live at IdeOne.com.

2019-11-22T22:08:15Z

Caveat: The offset on your example input string looks wrong to me.

Details

You need to understand some concepts for date-time handling.

Offset

A offset-from-UTC is merely a number of hours-minutes-seconds ahead of, or behind, the meridian line drawn at the Greenwich Royal Observatory.

In Java, we represent an offset with the ZoneOffset class. A date and time-of-day in the context of an offset is represented with the OffsetDateTime class. Such an object represents a moment, a specific point on the timeline.

Time zone

A time zone is much more. A time zone is a history of the past, present, and future changes to the offset used by the people of a particular region. These changes are determined by politicians. So these changes can be arbitrary and capricious, and happen surprisingly often, often with little or no warning. In North America, for example, most regions have adopted Daylight Saving Time (DST) nonsense, resulting in the offset changing twice a year. Currently there is a fad amongst politicians to quit DST changes while staying permanently year-round on “summer time”, one hour ahead of standard time.

There is a database cataloging these changes. The tZ data is a file maintained by IANA listing changes worldwide. You’ll likely find copies of this data in your host OS, in enterprise-quality database management systems such as Postgres, and in your Java Virtual Machine. Be sure to keep these up-to-date with changes in zones you care about.

Time zones have names in the format of Continent/Region. For example, Africa/Tunis, Europe/Paris, and Asia/Kolkata.

OffsetDateTime

So an input string like "2019-11-23T10:32:15+12:24" has no indicator of time zone, only an offset. So we must parse it an a OffsetDateTime.

OffsetDateTime odt = OffsetDateTime.parse( "2019-11-23T10:32:15+12:24" ) ;

Asking for that as a ZonedDateTime makes no sense. We cannot reliably determine a time zone merely from an offset. Many time zones may share an offset for some pints in time.

Also, that particular input string 2019-11-23T10:32:15+12:24 is suspect. That offset of twelve hours and twenty-four minutes does not map to any current time zone. Are you sure it is correct?

You can convert your OffsetDateTime to a ZonedDateTime by specifying a time zone to use in adjustment. I suggest using UTC. While this works technically, semantically it is confusing. Moments in UTC are best represented by OffsetDateTime rather than ZonedDateTime. But apparently you are interoperating with code that demands a ZonedDateTime specifically, so c’est la vie.

ZonedDateTime zdt = odt.atZoneSameInstant( ZoneOffset.UTC ) ;

Instant

Tip: Generally, APIs should be written to hand off moments as an Instant object, which is always in UTC by definition.

LocalDateTime

You present another string input, "2018-04-05 19:58:55". This input lacks any indicator of time zone or offset-from-UTC. So we cannot know if this means almost-8PM in Tokyo Japan, or almost-8PM in Toulouse France, or almost-8PM in Toledo Ohio US — which are all events happening several hours apart, different points on the time zone.

Such a value must be parsed as a LocalDateTime. Replace the SPACE in the middle with a T to comply with ISO 8601 standard formatting.

LocalDateTime ldt = LocalDateTime.parse( "2018-04-05 19:58:55".replace( " " , "T" ) ) ;

The resulting object does not represent a moment, is not a point in the timeline. Such an object represents potential moments along a spectrum of about 26-27 hours, the range of time zones around the globe.

ZonedDateTime

If you are certain that input string was intended for a particular time zone, apply a ZoneId to get a ZonedDateTime. Then you have determined a moment, a specific point on the timeline.

ZoneId z = ZonedId.of( "Asia/Kolkata" ) ;
ZonedDateTime zdt = ldt.atZone( z ) ;

Table of date-time types in Java, both modern and legacy


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?

Table of which java.time library to use with which version of Java or Android

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: 4

Ryuzaki L
Ryuzaki L

Reputation: 40048

You can parse the input date time string into OffsetDateTime and then convert it into ZonedDateTime

String inputDate = "2019-11-23T10:32:15+12:24";

OffsetDateTime offset = OffsetDateTime.parse(inputDate);

ZonedDateTime dateTime = offset.toZonedDateTime();

If you just need ZonedDateTime at same local time with ZoneId then use atZoneSimilarLocal

ZonedDateTime dateTime = offset.atZoneSimilarLocal(ZoneId.systemDefault());

Upvotes: 2

Related Questions