Antarctica Time Zones in Kotlin on GraalVM

They say you can tell someone is a developer by whispering the word “timezone” in their ear and watching a shudder go down their spine. Here, from Wikipedia, is a picture of the time zones in Antarctica.

Time zones in Antarctica, from Wikipedia
Time zones in Antarctica, from Wikipedia

Amazing, right? But the funny part isn’t immediately obvious. See how the South Pole is inside that hashed area with alternating red and white stripes? That section is called the Ross Dependency and is claimed by New Zealand, and while most countries don’t recognize territorial claims in Antarctica, that area follows the time zone rules for New Zealand.

That means the South Pole, which experiences six months of daylight followed by six months of darkness, spends part of the year on Daylight Savings Time. Seriously.

Here’s a simple Kotlin script (for the JVM) to check whether or not that’s true at the moment:

import java.time.*

val instant =
val southPole = instant.atZone(ZoneId.of("Antarctica/South_Pole"))
val dst =
println("It is ${southPole.toLocalTime()} " + 
        "(UTC${southPole.offset}) at the South Pole")
println("The South Pole ${if (dst) "is" else "is not"} " +
        "on Daylight Savings Time")

This is really just Java code using classes from the java.time package, but written using Kotlin syntax.

Running this on my Mac (macOS Catalina, version 10.15.2) running Java 11 gives:

$ kotlinc -script southpole.kts
 It is 05:17:54.524607 (UTC+13:00) at the South Pole
 The South Pole is on Daylight Savings Time

Yeah, go figure. This can be fleshed out a bit to see all the time zones in Antarctica:

import java.time.Instant
import java.time.ZoneId
import java.time.ZonedDateTime

val regex = """.*Antarctica.*""".toRegex()
val instant: Instant =

    .filter { regex.matches(it) }
    .map { instant.atZone(ZoneId.of(it)) }
    .sortedBy { it.offset.totalSeconds }
    .forEach { zdt ->
        println(String.format("%7s: %25s %7s",

This produces:

  -03:00:        Antarctica/Rothera   false
  -03:00:         Antarctica/Palmer   false
       Z:          Antarctica/Troll   false
  +03:00:          Antarctica/Syowa   false
  +05:00:         Antarctica/Mawson   false
  +06:00:         Antarctica/Vostok   false
  +07:00:          Antarctica/Davis   false
  +08:00:          Antarctica/Casey   false
  +10:00: Antarctica/DumontDUrville   false
  +11:00:      Antarctica/Macquarie   false
  +13:00:        Antarctica/McMurdo    true
  +13:00:     Antarctica/South_Pole    true

So at the moment, both the South Pole and the scientists at McMurdo Station are on daylight savings time. Keep that in mind next time you plan to set up a conference call with them. And in case you’re wondering, the temperature at McMurdo Station today ranges from a high of 29 F down to a low of 19 F. Not too bad. It is, after all, summer down there.

(Also, if you’re wondering about that “Troll” time zone, that apparently is for Troll Station in Queen Maud Land and it’s practically sitting on the Greenwich meridian. I was kind of hoping someone from the IANA was, well, trolling us.)

The code in the script above is hopefully reasonably clear, with a few explanations:

  • The getAvailableZoneIds method (from Java) returns a Set<String> representing the “region ids” for all the time zones around the world (approximately 600).
  • The filter passes only those in Antarctica.
  • The atZone method returns a ZonedDateTime, so the result of the map operation is a Stream<ZonedDateTime>.
  • These are then sortedBy their offsets in total seconds from GMT, and then printed as the time zone offset, the region ID, and whether or not the time zone is experiencing daylight savings time.

Easy peasy, penguin squeezy, at least until the rest of the Antarctic ice sheet melts or falls into the sea.

Native Image On GraalVM

As it says on the GraalVM web site, Graal is a universal virtual machine for running applications written in multiple languages. Please keep that in mind, because if you talk to one of the Graal developers and you refer only to the native image tool (which I’m about to do), you’ll annoy them unnecessarily. They’re rather sensitive about that, so be careful. I promise I understand that it’s a complete virtual machine, with cross-language capabilities.

Still, what I like about Graal is the native image generator, which allows you to “ahead of time” compile Java code into a stand-alone executable. There are various ways to install the VM and the compiler. For this particular demo, I’m going to use the Gradle plugin.

If you take a look at the source code for my book Kotlin Cookbook, located in this GitHub repository, you’ll find the following sections in the build file build.gradle.kts:

plugins {
    kotlin("jvm") version "1.3.61"
    id("com.palantir.graal") version "0.6.0-58-gce10e7e"

val scriptname: String by project  // from

graal {
    outputName(scriptname) // output is build/graal/${scriptname}

The gradle-graal plugin is a simple wrapper around the GraalVM tooling that downloads the necessary tools and caches them locally. It adds the graal task seen in the snippet, which asks for the class with the main method in it. There are actually many properties you can configure — see the plugin documentation for details.

In this case I’m arguably being a bit too clever, but the ideas are:

  • I have a series of Kotlin scripts in a package called scripts under src/main/kotlin.
  • I put the name of the script I want in my local file. The “by project” delegate is used by Gradle to read properties out of that file.
  • When Kotlin compiles a script (meaning code that isn’t wrapped inside a class), by default it generates a class called ScriptKt. I’m using that as the name of the class Graal needs.

The plugin requires me to have a main method in my script, so here’s a mild refactoring of the overall program:

package scripts

import java.time.Instant
import java.time.ZoneId

fun main() {
    val regex = """.*Antarctica.*""".toRegex()
    val instant =

    val zones = ZoneId.getAvailableZoneIds()
        .filter { regex.matches(it) }
        .map { instant.atZone(ZoneId.of(it)) }
        .sortedBy { it.offset.totalSeconds }

    zones.forEach { zdt ->
        println(String.format("%7s: %25s %7s", zdt.offset,,

    val southPole = instant.atZone(
    val dst =
    println("It is ${southPole.toLocalTime()} " +
            "(UTC${southPole.offset}) at the South Pole")
    println("The South Pole ${if (dst) "is" else "is not"} " +
            "currently on Daylight Savings Time")

The plugin adds a handful of tasks to Gradle. Running gradle tasks gives (among the rest):

Graal tasks
downloadGraalTooling - Downloads and caches GraalVM binaries.
extractGraalTooling - Extracts GraalVM tooling from downloaded archive using the system's tar command or Gradle's copy method.
nativeImage - Runs GraalVM's native-image command with configured options and parameters.
sharedLibrary - Runs GraalVM's native-image command configured to produce a shared library.

The one I want is the nativeImage task. Running this (the first time, so the download of the toolkit is included):

$ ./gradlew nativeImage

> Task :extractGraalTooling
Downloading: Component catalog from
Processing component archive: Native Image
Downloading: Component native-image: Native Image  from
[#####               ]                                                                                                      t: Installing new component: Native Image (org.graalvm.native-image, version 19.2.0)

> Task :nativeImage
Build on Server(pid: 36964, port: 55606)*
[antarctica:36964]    classlist:   2,658.08 ms
[antarctica:36964]        (cap):   2,267.25 ms
[antarctica:36964]        setup:   3,204.49 ms
[antarctica:36964]   (typeflow):   2,251.16 ms
[antarctica:36964]    (objects):   1,932.88 ms
[antarctica:36964]   (features):     248.91 ms
[antarctica:36964]     analysis:   4,527.18 ms
[antarctica:36964]     (clinit):      89.98 ms
[antarctica:36964]     universe:     264.94 ms
[antarctica:36964]      (parse):     319.07 ms
[antarctica:36964]     (inline):   1,054.46 ms
[antarctica:36964]    (compile):   2,818.36 ms
[antarctica:36964]      compile:   4,477.76 ms
[antarctica:36964]        image:     474.30 ms
[antarctica:36964]        write:     175.90 ms
[antarctica:36964]      [total]:  15,920.48 ms
native image available at build/graal/antarctica (5 MB)  

There are now three different ways to execute the original Kotlin script:

  • Compile with kotlinc-jvm; execute it with kotlin
  • Create a jar file by including the runtime; execute it with java
  • Create the native image; execute it directly

So here is using kotlinc-jvm and kotlin:

> kotlinc-jvm antarctica.kt // generates AntarcticaKt.class
> kotlin AntarcticaKt
... output from before ...
> time kotlin AntarcticaKt
kotlin A.. 0.23s user 0.04s system 146% cpu 0.188 total

Now creating the jar first and running java:

> kotlinc-jvm antarctica.kt -include-runtime -d antarctica.jar
> java -jar antarctica.jar
... output from before ...
> time java -jar antarctica.jar
java ...  0.21s user 0.03s system 151% cpu 0.159 total

And finally for the native image:

> sdk install java 19.3.0.r11-grl
> sdk use java 19.3.0.r11-grl
Using java version 19.3.0.r11-grl in this shell
> gu install native-image
Downloading: Component catalog from
 Processing Component: Native Image
 Downloading: Component native-image: Native Image  from
 Installing new component: Native Image (org.graalvm.native-image, version 19.3.0)
> native-image -jar antarctica.jar
... lots of output similar to before ...
> ./antarctica
... works ...
> time ./antarctica
./antarctica  0.00s user 0.00s system 68% cpu 0.008 total

Okay, I lied. I didn’t use the Gradle plugin for the terminal demo. I removed the package statement, moved the script to my root, and installed the tools locally. I use the awesome SDKMAN! tool to manage multiple JDKs, so with it I installed the latest GraalVM version of the JDK, and used its gu command to install native-image, and so on.

Summarizing the results from this admittedly trivial experiment:

kotlin: 0.23s user 0.04s system 146% cpu 0.188 total
  java: 0.21s user 0.03s system 151% cpu 0.159 total
native: 0.00s user 0.00s system  68% cpu 0.008 total

Wow, that’s quite a speed improvement (nearly 20x) with the native image tool, which presumably all comes from avoiding the JVM startup time and memory costs. So sure, I had no good reason to do this, but it’s fun to see a speed up like that.

I should note that the native image tool requires the local toolchain to work, so things like glibc-devel, zlib-devel, and gcc need to be available, as they are on a Mac.

I hope you enjoyed this demo. All the steps are included in my Kotlin Cookbook, now available from O’Reilly Media and in print form on Amazon and from other booksellers.

Now if we could just transport some of that Antarctic ice to Australia where they could really use it…

2 responses to “Antarctica Time Zones in Kotlin on GraalVM”

  1. Hi Ken, It was great to attend your speeches about Kotlin on Devnexus. I wonder if you can write a post about how to write good functional code that is easy to maintain and extend. In the past, I saw people apply functional programming with lambda expressions everywhere in a large spring boot application, causing it becomes super hard to maintain and change compared to normal OO approach with good design patterns. I would like to hear if you have any experience about it.

  2. I don’t know what you’ve seen, but I’ve found functional approaches in Spring Boot to be reasonably easy to maintain. Spring Boot does hide a lot of what it does, which can lead to maintenance issues, but that’s not related to apps using streams, lambdas, method references, or other functional constructs. I personally find functional programs easier to read and understand, but that could vary depending on how they’re written. Good luck either way. 🙂

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.