What is the difference between UTC (ZoneId) and (ZoneOffSet)

280 views Asked by At

Out of curiosity are there any one who knows what the difference is between UTC as ZoneId and ZoneOffset in Java.

I was a little bit surprised that it was not true when comparing utc in zoneid and zoneoffset

I tried to write this test in spock and the only thing I could find was that the zone is different.

And does anyone have any recommendation on which one to use when?

It is not clear from the example but I actually used spock/groovy where == corresponds to the equals method in java, so apoligize for that

But no matter if I would have written lhs.equals(rhs) it would still return false

LocalDateTime now = LocalDateTime.now() // 2023-11-03T19:42:41.772234517
ZonedDateTime lhs = now.atZone(ZoneOffset.UTC) 
String lhsString = lhs.toString() // 2023-11-03T19:42:41.772234517Z
ZonedDateTime rhs = now.atZone(ZoneId.of('UTC'))
String rhsString = rhs.toString()  // 2023-11-03T19:42:41.772234517Z[UTC]
Duration duration = Duration.between(lhs, rhs)  // PT0S
boolean equal = lhs == rhs   // this is false
3

There are 3 answers

5
Robert On

From the ZoneId documentation:

A ZoneId is used to identify the rules used to convert between an Instant and a LocalDateTime.

From the ZoneOffset documentation:

Different parts of the world have different time-zone offsets. The rules for how offsets vary by place and time of year are captured in the ZoneId class.

For example, Paris is one hour ahead of Greenwich/UTC in winter and two hours ahead in summer. The ZoneId instance for Paris will reference two ZoneOffset instances - a +01:00 instance for winter, and a +02:00 instance for summer.

So the difference is summer vs winter time (daylight savings time), if the region uses them. There could be different states or countries in the same time zone, for example, Mexico City and Chicago are in the same zone offset (GMT-5), but Mexico stopped switching between summer and winter time in spring of 2023, so during summer they were an hour off, and in winter they'll match again.

So if you want to display or convert to local time, always use the ZoneId which considers the location (state) and the local rules on daylight savings time.

2
kriegaex On

Quoting my own comment:

Z is just a military equivalent to UTC, and the equals method should be a bit more precise there. When debugging into the JDK, in the end it boils down to a comparison of two zone IDs, and the fact that "UTC".equals("Z") evaluates to false. The JDK itself uses both in different contexts, so it also should know that they are equivalent.

Having said that, your workaround is to use ZoneId.normalized():

package de.scrum_master.stackoverflow.q77419050

import spock.lang.Specification

import java.time.Duration
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.ZoneOffset
import java.time.temporal.ChronoUnit

class TimeZoneTest extends Specification {
  def test() {
    given:
    def now = LocalDateTime.now()
    
    when:
    def lhs = now.atZone(ZoneOffset.UTC.normalized())
    def rhs = now.atZone(ZoneId.of('UTC').normalized())
    def timeDifference = Duration.between(lhs, rhs).get(ChronoUnit.SECONDS)

    then:
    lhs == rhs
    timeDifference == 0
  }
}

Try it in the Groovy Web Console,

More details can be found in this answer.

2
Reilas On

The double-equal equality operator will simply compare, in this case, the instance of the object.

Since lhs, and rhs, are assigned different instances, the values will return false.

Typically, the Object#equals method will return whether the content is equal.

boolean equal = lhs.equals(rhs)

If not, implement your own comparison method.