Java 8 Timezones: Examples in Scala

Java 8 Timezones: Examples in Scala

Last updated:
Java 8 Timezones: Examples in Scala
Source
Table of Contents

All Scala code can be viewed on this notebook

The new java.time API, available in Java version 8 upwards, fixes many inconsistencies and imperfections of previous versions and adds several new classes and methods, roughly based upon Joda-Time, which had become the de facto date and time library for JVM projects.

Users are advised to migrate to the new API if at all possible.

This post deals specifically with the intricacies of working with time zones, which are required for most if not all applications used nowadays.

This post deals specifically with the intricacies of working with time zones in the new API

First off, why exactly do we need time zones?

Time zones are needed to enable telling apart two distinct points in time which happen to be defined the same way, for example 6 PM in Tokyo and 6 PM in New York.

Without them, there would be no way to tell exactly when an event happened if all the information you had was its local time. This is even more important when we look at how people from all over the world interact over the Internet.

An assignment in an online course is due at 6 PM tomorrow. But whose 6 PM? My local time? Your local time? It is impossible to tell without time zones.

It's sometimes confusing to do time arithmetic in your head, telling local datetimes from zoned datetimes, etc.

One thing I found useful when thinking about timezones is to realize two things:

  • Local datetimes are worthless because they're ambiguous;

  • Add timezone information to local times as soon as you can; preferably before they enter your system/database.

Class ZonedDateTime

The bread and butter of the new Java Time API; as its name implies, it represents a specific instant, at a specific date at a specific time, and a specific time zone.

It can be accurate down to nanoseconds, as per the following example:

import java.time._

ZonedDateTime.now()
// ZonedDateTime = 2015-12-24T02:31:15.104-02:00[America/Sao_Paulo]
// not that it has used my O.S. settings to infer my timezone.

LocalDateTime in specific time zone

view notebook

You have a local time and you want to make it clear to what time zone you refer.

In other words, you want to convert a LocalDateTime into a ZonedDateTime

Example: turn "2015-10-15T04:12:30" into "2015-10-10T04:12:30-03:00":

val ldt = LocalDateTime.of(2015,10,15,4,12,30)
// 2015-10-15T04:12:30

// create a time zone using its offset from UTC
val zone = ZoneId.of("-03:00")

ldt.atZone(zone)
// ZonedDateTime = 2015-10-15T04:12:30-03:00
// note that the time was not shifted in any direction

Change time zone for ZonedDateTime object

view notebook

If you want to enable two-way interaction among people in different time zones, you need to be able to convert between one and the other.

What is the local time in Paris, France that corresponds to 05:30 AM in Sydney, Australia, on the 2nd of May, 2015?

// you can define time zones using IANA time zone codes
val frTZ = ZoneId.of("Europe/Paris")

val ausTZ = ZoneId.of("Australia/Sydney")

val time = LocalDateTime.of(2015,5,2,5,30,0)

// tell java that your local datetime is actually
// australian datetime
val ausTime = time.atZone(ausTZ)

// convert it to french local time
val frTime = ausTime.withZoneSameInstant(frTZ).toLocalDateTime
// LocalDateTime = 2015-05-01T21:30
// in Paris, it is still 9:30 PM on the day before!

Represent LocalTime in given time zone

view notebook

In other words, convert a LocalTime into an OffsetTime

Note that we have no date information and we do not know what offset represents Zurich time. In addition, class LocalTime has no method (like a LocalDateTime) to represent the same instant in another time zone.

So we need to, firstly, find out what offset (with respect to UTC) represents our time zone, and then apply that offset to the local time.

Example: turn "04:12:20" into Zurich local time.

val tz = ZoneId.of("Europe/Zurich")

val lt = LocalTime.of(4,12,20)

// this is where we discover that the offset for
// Zurich is +01:00, at this moment (at another
// moment it might be different, for example
// during daylight saving time)
val offset = tz.getRules.getOffset(Instant.now())

// note that this is just the time, there's no date information!
lt.atOffset(offset)
// OffsetTime = 04:12:20+01:00

If you want to add date information to an OffsetTime like you got in this example, you can call method atDate().

Current date and time in UTC

vew notebook

This is a very common use case; you want to unambiguously record what time it is when something has happened, e.g. when logging an event.

val now = Instant.now

val utcTz = ZoneId.of("UTC")

now.atZone(utcTz)
// ZonedDateTime = 2015-12-24T04:17:20.449Z
// Z is shorthand for +00:00, or UTC

More information