Kotlin中的时区:如何不被时间逼疯

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

为什么时间是个麻烦(以及Kotlin如何解决它)

\n\n

如果你曾经编写过服务不同时区用户的应用程序,你一定知道这种痛苦。请求带着时间戳到来,数据库中存储的是UTC,纽约的用户抱怨通知在凌晨3点到达,而数据库中显示的是早上8点。熟悉吗?

\n\n

时区处理是编程中一个看似简单却容易引发数十个bug的领域。但Kotlin继承了Java的最佳实践,为我们提供了强大的工具来应对混乱。在本文中,我们将探讨如何正确处理Kotlin中的时区,避免常见错误,并编写在夏令时切换时不会崩溃的代码。

\n\n

简要概述:什么是时区?

\n\n

在深入代码之前,让我们先统一术语。时区(time zone)不仅仅是相对于UTC的偏移量。它还包括夏令时/冬令时(DST)的切换规则。例如,Europe/Moscow不仅仅是+3小时,它还包含历史变化(俄罗斯自2014年起停止调整时钟)。

\n\n

在Java/Kotlin世界中,有两个关键概念负责处理时区:

\n\n
    \n
  • ZoneId — 时区标识符(例如,"America/New_York")。包含所有DST历史记录。
  • \n
  • ZoneOffset — 简单的偏移量(例如,+05:00)。不包含切换规则。
  • \n
\n\n

区别至关重要:如果你使用ZoneOffset而不是ZoneId,你的代码将在时钟调整的那一天崩溃。请记住这一点。

\n\n

Kotlin中的java.time基础类

\n\n

Kotlin完全支持java.time包(Java 8+)。如果你在Android上开发,别忘了添加desugaring或使用ThreeTenABP。对于服务器端Kotlin,没有问题。

\n\n

Instant — 时间点

\n\n

Instant是时间轴上的绝对时刻。它不依赖于时区。这正是你应该存储在数据库中的内容。

\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

注意末尾的字母Z — 这是祖鲁时间,即UTC。将所有内容存储在UTC中 — 这是黄金法则。

\n\n

ZonedDateTime — 带时区的时间

\n\n

当你需要向用户显示时间时,请使用ZonedDateTime。它存储日期、时间、偏移量和时区标识符。

\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

看到方括号了吗?这正是ZoneId,它使系统能够正确处理DST。

\n\n

OffsetDateTime — 带偏移量的时间

\n\n

如果你不需要时区信息,只需要偏移量,请使用OffsetDateTime。它更轻量、更快。

\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

但请记住:如果你为纽约保存了OffsetDateTime,然后美国切换到夏令时,你的偏移量就会过时。

\n\n

Kotlin中的时区转换

\n\n

最常见的任务是从数据库(UTC)获取时间,并将其显示在用户的时区中。让我们看看如何正确操作。

\n\n

从UTC到本地时区

\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 =                

博客

书籍推荐