Time Zones in Kotlin: How Not to Lose Your Mind with Time

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Why Time is a Pain (and How Kotlin Treats It)

\n\n

If you've ever written an application that serves users from different time zones, you know this pain. A request comes with a timestamp, the database stores UTC, a user in New York complains that a notification arrived at 3 AM, while in the database it's 8 AM. Sound familiar?

\n\n

Working with time zones is that area of programming where apparent simplicity turns into dozens of bugs. But Kotlin, having inherited the best practices from Java, gives us a powerful arsenal to fight the chaos. In this article, we'll break down how to properly work with time zones in Kotlin, avoid common mistakes, and write code that won't break during daylight saving time transitions.

\n\n

A Brief Overview: What is a Time Zone?

\n\n

Before diving into code, let's agree on terms. A time zone is not just an offset from UTC. It also includes a set of rules for daylight saving time (DST) transitions. For example, Europe/Moscow is not just +3 hours, it also includes historical changes (in 2014, Russia stopped changing clocks).

\n\n

In the Java/Kotlin world, two key concepts handle this:

\n\n
    \n
  • ZoneId — a time zone identifier (e.g., "America/New_York"). Contains the full DST history.
  • \n
  • ZoneOffset — just an offset (e.g., +05:00). No transition rules.
  • \n
\n\n

The difference is critical: if you use ZoneOffset instead of ZoneId, your code will break on the day clocks are changed. Remember this.

\n\n

Basic java.time Classes in Kotlin

\n\n

Kotlin fully supports the java.time package (Java 8+). If you're writing for Android, don't forget to add desugaring or use ThreeTenABP. For server-side Kotlin, there are no issues.

\n\n

Instant — A Moment in Time

\n\n

Instant is an absolute point on the timeline. It is not tied to any time zone. This is what you should store in the database.

\n\n
import java.time.Instant\n\nfun main() {\n    val now = Instant.now()\n    println(now) // 2025-03-15T10:30:00.123Z\n}
\n\n

Notice the letter Z at the end — this is Zulu time, i.e., UTC. Store everything in UTC — this is the golden rule.

\n\n

ZonedDateTime — Time with Zone Awareness

\n\n

When you need to display time to a user, use ZonedDateTime. It stores date, time, offset, and zone identifier.

\n\n
import java.time.ZonedDateTime\nimport java.time.ZoneId\n\nfun main() {\n    val moscowTime = ZonedDateTime.now(ZoneId.of("Europe/Moscow"))\n    println(moscowTime) // 2025-03-15T13:30:00.123+03:00[Europe/Moscow]\n}
\n\n

See the square brackets? That's the ZoneId, which allows the system to correctly handle DST.

\n\n

OffsetDateTime — Time with Offset

\n\n

If you don't need zone information, only the offset, use OffsetDateTime. It is lighter and faster.

\n\n
import java.time.OffsetDateTime\nimport java.time.ZoneOffset\n\nfun main() {\n    val offsetTime = OffsetDateTime.now(ZoneOffset.ofHours(5))\n    println(offsetTime) // 2025-03-15T15:30:00.123+05:00\n}
\n\n

But remember: if you save OffsetDateTime for New York, and then the US switches to daylight saving time, your offset will become outdated.

\n\n

Converting Between Time Zones in Kotlin

\n\n

The most common task is to take time from the database (UTC) and display it in the user's time zone. Let's break down how to do this correctly.

\n\n

From UTC to Local Zone

\n\n
import java.time.Instant\nimport java.time.ZoneId\nimport java.time.ZonedDateTime\n\nfun convertUtcToLocal(utcInstant: Instant, targetZone: String): ZonedDateTime {\n    return utcInstant.atZone(ZoneId.of(targetZone))\n}\n\nfun main() {\n    val utcNow =                

Blogs

Book Recommendations