Search code examples
javatimezonejava-timedate-conversionzoneddatetime

Instant vs ZoneDateTime. Converting to another timezone


I'm having a hard time understanding java.time between ZoneDateTime - Instant - LocalDateTime , so far, the only thing I know of is:

  • Instant works in-between the two
  • Instant (in my understanding), is a Stamp of time from the moment of time (UTC), a stamp of time that is relevant to the flow of human time, but without a time zone
  • Zone Date time has TimeZone
  • Instant does not have Time Zone but can deal with it given that a Zone information is supplied
  • LocalDate time does not have time zone and cannot deal with zones, it's a Date Time without any relevance on the continuation of entire flow of time (global).

So I have this conversion below

val seoul = "Asia/Seoul"

val zoneId = ZoneId.of(seoul)
val now = ZonedDateTime.now()

val convertedZoneDateTIme = ZonedDateTime.of(now.toLocalDateTime(), zoneId).withZoneSameInstant(ZoneOffset.UTC)
val convertedInstant = now.toInstant().atZone(zoneId)

// expected output
println(convertedInstant.format(DateTimeFormatter.ofPattern(format)))

// not expected output
println(converted.format(DateTimeFormatter.ofPattern(format)))

Output

2021-05-02 03:15:13
2021-05-02 09:15:13

I'm trying to convert a given time to another Time Zone, a use-case where a user moved to a different timezone and I need to update any information about a stored date.

Why am I getting an incorrect value on the second one..? Why do I have to convert it to Instant first and proceed with conversion?

Thank you in advance


Solution

  • Most of your bullets are fully correct. Only you should not use Instant for working between LocalDateTime and ZonedDateTime as you said in your first bullet. Converting between Instant and LocalDateTime requires a time zone (or at least an offset from UTC), so should go through a ZonedDateTime. So ZonedDateTime is the one to use between the two others. As I said, the rest is correct.

    You are not being perfectly clear about what you had expected from your code nor how more specifically observed result differs. Assuming you wanted to use the same point in time throughout, this line is where your surprise arises:

    val convertedZoneDateTIme = ZonedDateTime.of(now.toLocalDateTime(), zoneId).withZoneSameInstant(ZoneOffset.UTC)
    

    now is a ZonedDateTime in your own time zone (the default time zone of your JVM to be precise). By taking only the date and time of day from it and combining them with a different time zone you are keeping the time of day but in that way (probably) changing the point in the flow of time. Next you are converting to UTC keeping the point in time (the instant), thereby (probably) changing the time of day and possibly the date. You have got nothing left from the ZonedDateTime that was your starting point, and I can’t see that the operation makes sense. To convert now to UTC keeping the point on the timeline use the simpler:

    val convertedZoneDateTIme = now.withZoneSameInstant(ZoneOffset.UTC)
    

    With this change your two outputs agree about the point in time. Example output:

    2021-05-07 02:30:16 +09:00 Korean Standard Time
    2021-05-06 17:30:16 +00:00 Z
    

    I used a format of uuuu-MM-dd HH:mm:ss xxx zzzz.

    Also for you other conversion I would prefer to use withZoneSameInstant(). Then we don’t need to go through an Instant.

    val convertedInstant = now.withZoneSameInstant(zoneId)
    

    It gives the same result as your code.

    A short overview of what is in each of the classes discussed:

    Class Date and time of day Point in time Time zone
    ZonedDateTime Yes Yes Yes
    Instant - Yes -
    LocalDateTime Yes - -

    Basically you don’t have any use for LocalDateTime for your purpose, and also Instant, while useable, isn’t necessary. ZonedDateTime alone fulfils your needs.