Search code examples
javakotlintimetimezonedst

Get time difference in milliseconds between JVM and UTC


I am having an exceedingly hard time trying to figure out what seems to be a simple problem.

I would like to calculate the difference (offset?) in milliseconds between the UTC instant and the JVM instant (i.e. specific to the machine running the code).

I have tried the following initializations (sorry, this is in Kotlin, not Java):

// 1
val difference = TimeZone.getDefault().getOffset(System.currentTimeMillis())

// 2
val difference = ZoneId.systemDefault().rules.getOffset(Instant.EPOCH).totalSeconds * 1000

// 3
val difference = ZonedDateTime.now(ZoneId.systemDefault()).offset.totalSeconds * 1000

To be more specific: I am running this on my Mac on the US west coast today (Feb 22, 2024 -- i.e. during Daylight Savings). All 3 initializations produce -28800000 (i.e. timezone-based) when I am looking for an initialization that will produce -25200000 (i.e. DST-sensitive / instant-based).

What am I doing wrong here?

I would like the solution to be timezone/machine agnostic (i.e. I could run this code on any machine around the world without any specific input like a string timezone).

EDIT:

As @Louis Wasserman pointed out, my expectation that the initializations would produce -25200000 is incorrect as the current time difference is in fact -28800000.

My incorrect expectation is due to the fact I had written the following test:

@Test
fun timeStrToEpochMillis_invariantValidation_happyPaths() {
    // Arrange
    val testTimeStr = "2023-06-13 20:21:46"

    // Act
    val result = timeStrToEpochMillis(testTimeStr)

    // Assert
    assertEquals(1686687706000, result)
}

For the below function:

fun timeStrToEpochMillis(timeStr: String): Long {
    val df = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
    return df.parse(timeStr).time
}

When I ran the above test in a container, whose default time zone is UTC, the test would pass. However, when running the above test on my local machine, whose default time zone is Pacific, the test would fail.

I overlooked that the difference ought to be based on the test string, like so:

@Test
fun timeStrToEpochMillis_invariantValidation_happyPaths() {
    // Arrange
    val testTimeStr = "2023-06-13 20:21:46"

    // Act
    val result = timeStrToEpochMillis(testTimeStr)
    val diff = TimeZone.getDefault().getOffset(result)

    // Assert
    assertEquals(1686687706000 + diff, result)
}

Thanks again to @Louis Wasserman for pointing out the obvious.


Solution

  • It is not currently Daylight Savings Time on the US West Coast. The time zone America/Los_Angeles, used by the US West Coast, is currently 28800000 milliseconds from UTC.

    The JVM is correct here.

    You later commented:

    My question is missing some context: I am trying to apply this logic to a specific date time string ("2023-06-13 20:21:46"), which is in Daylight Savings

    You have found ZonedDateTime.now(ZoneId.systemDefault()).offset): use instead ZonedDateTime.of(LocalDateTime, ZoneId), with that LocalDateTime.