peter.petrov
peter.petrov

Reputation: 39437

Parse non-UTC date string as UTC Instant, and print it out in another TimeZone

I am using Java 11.

I have a datetime string which looks like this

"2023-02-17T03:56:50.254"

But I know it represents a point in time in UTC time zone.

How do I parse this string taking into account it's actually in UTC (the parsing has to be independent of my default JVM time-zone)?

And then how do I convert it to another string which represents the same date/time in e.g. America/NewYork time zone?

There are many parsing examples here but none of them seems to represent exactly my case.

Upvotes: 1

Views: 1459

Answers (3)

Basil Bourque
Basil Bourque

Reputation: 338171

tl;dr

LocalDateTime                           // Represent a date with time-of-day, but lacking the context of a time zone or offset from UTC.
.parse( "2023-02-17T03:56:50.254" )     // Parse text in standard ISO 8601 format.
.atOffset(                              // Assign an offset to this date-with-time.
    ZoneOffset.UTC                      // Constant for an offset-from-UTC of zero hours-minutes-seconds.
)                                       // Returns an `OffsetDateTime` object.
.atZoneSameInstant(                     // Adjust from a moment as seen with an offset of zero to being seen with a time zone some number of hours-minutes-seconds ahead/behind UTC.
    ZoneId.of( "America/New_York" )     // Represent a time zone. Name is in format of Continent/Region. 
)                                       // Returns a `ZonedDateTime` object.
.toString()                             // Generate text in standard ISO 8601 format extended wisely to append the name of the zone in square brackets.

See this code run at Ideone.com. Notice how the date as well as the time-of-day differ from that of the input.

2023-02-16T22:56:50.254-05:00[America/New_York]

Offset versus zone

Per the Comment by Ole V.V., the appropriate class here is OffsetDateTime rather than ZonedDateTime.

  • An offset is merely a number hours, minutes, and second ahead of or behind the temporal meridian of UTC.
  • A time zone is a 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. Named in format of Continent/Region.

The java.time classes offer two classes to represent each concept:

  • ZoneOffset
  • ZoneId

And the java.time classes offer two classes to represent a moment as seen in each way.

  • OffsetDateTime
  • ZonedDateTime

By the way, the Instant class is a more basic building-block class in java.time, representing a moment as seen in UTC, always in UTC (an offset of zero).

LocalDateTime

So, yes parse your input as date with time-of-day, a LocalDateTime.

LocalDateTime ldt = LocalDateTime.parse( "2023-02-17T03:56:50.254" ) ;

The LocalDateTime does not represent a moment, as it lacks the context of an offset or zone.

ZoneOffet & OffsetDateTime

You said:

How do I parse this string taking into account it's actually in UTC

If you are certain that date and time was intended to represent a moment "in UTC", that is with an offset of zero hours-minutes-seconds, then apply a ZoneOffset rather than a ZoneId to produce an OffsetDateTime rather than a ZonedDateTime.

Java comes with a constant defined for an offset of zero, ZoneOffset.UTC.

OffsetDateTime odt = ldt.atOffset( ZoneOffset.UTC ) ;

ZoneId & ZonedDateTime

You asked:

And then how do I convert it to another string which represents the same date/time in e.g. America/NewYork time zone?

Define your desired time zone. Use ZoneId for a zone (ZoneOffset for a mere offset).

ZoneId zoneId = ZoneId.of( "America/New_York" ) ;

Apply that time zone to produce a ZonedDateTime.

ZonedDateTime zdt = odt.atZoneSameInstant( zoneId ) ;

In this example, both odt and zdt represent the same simultaneous moment, the same point on the timeline. That moment is what is meant by “Instant” in the method name atZoneSameInstant and in the class Instant. The odt and zdt variables differ in their wall-clock time & calendar. Like someone in Reykjavík Iceland calling someone in Los Angeles California — when each party simultaneously glances at the clock and calendar hanging on their respective wall, they see a different time and date.

In contrast, the LocalDateTime variable named ldt does not represent a moment, is not a point on the timeline. That class purposely lacks the context of a time zone or offset. So the LocalDateTime class cannot represent a moment.

Upvotes: 2

peter.petrov
peter.petrov

Reputation: 39437

Thanks for all the comments and answers. Putting them all together, I think this is what I needed.

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;

public class Main001 {

    public static void main(String[] args) {
        
        // Do first test with a winter date
        String text = "2023-02-17T03:56:50.254";
                
        LocalDateTime local = LocalDateTime.parse(text);
        ZonedDateTime zdt = local.atZone(ZoneId.of("UTC"));
        Instant instant = zdt.toInstant();
        
        System.out.println(instant);
        
        DateTimeFormatter formatter =
                DateTimeFormatter
                .ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS")
                .withZone(ZoneId.of("America/New_York"));
        
        System.out.println(formatter.format(instant));
        
        // Do second test with a summer date
        text = "2023-07-17T03:56:59.257";
        
        local = LocalDateTime.parse(text);
        zdt = local.atZone(ZoneId.of("UTC"));
        instant = zdt.toInstant();
        
        System.out.println(instant);
        
        formatter =
                DateTimeFormatter
                .ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS")
                .withZone(ZoneId.of("America/New_York"));
        System.out.println(formatter.format(instant));

    }

}

Output:

2023-02-17T03:56:50.254Z
2023-02-16T22:56:50.254
2023-07-17T03:56:59.257Z
2023-07-16T23:56:59.257

Upvotes: 1

Sweeper
Sweeper

Reputation: 270758

First, parse the string into a LocalDateTime, because that's all there is in the string - a date and a time.

Then, you can convert it to a ZonedDateTime or an Instant, depending on your interpretation - whether this is a date and time in the UTC zone, or a point in time.

var parsedResult =
    LocalDateTime.parse(string/*, DateTimeFormatter.ISO_DATE_TIME*/)
        .toInstant(ZoneOffset.UTC); // for an Instant
//      .atZone(ZoneOffset.UTC); // for a ZonedDateTime

After you have either of those, you can use withZoneSameInstant/atZone respectively to put it into another zone.

// if parsedResult is Instant
var atAnotherZone = parsedResult.atZone(anotherZone);
// if parsedResult is ZonedDateTime
var atAnotherZone = parsedResult.withZoneSameInstant(anotherZone);

Upvotes: 4

Related Questions