日期时间 (kotlinx-datetime)
在 Kotlin 多平台 (KMP) 生态中,kotlinx-datetime 是处理时间数据的标准方案。它不仅抹平了平台差异,还通过严谨的类型系统区分了“绝对时刻”与“本地挂钟时间”,有效规避了传统时间 API 中常见的逻辑错误。
依赖配置与版本
kotlin
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.0")groovy
implementation 'org.jetbrains.kotlinx:kotlinx-datetime:0.6.0'核心组件架构
该库的设计核心在于区分物理时间与日历时间。
- 物理时间 (Physical Time):对应
Instant。它是时间轴上一个与时区无关的绝对点。 - 本地时间 (Local Time):对应
LocalDateTime、LocalDate。它是特定时区下人们看到的挂钟时间。 - 时区 (TimeZone):连接上述两者的桥梁。
时钟与当前时间获取
为了保证代码的可测试性,严禁直接在业务逻辑中使用硬编码的系统时间。
kotlin
// 获取当前的绝对时刻 (UTC)
val now: Instant = Clock.System.now()
// 将当前时刻转换为特定时区的挂钟时间
val shanghaiZone = TimeZone.of("Asia/Shanghai")
val localDateTime = now.toLocalDateTime(shanghaiZone)
println("当前时刻: $now")
println("上海时间: $localDateTime")kotlin
// 快速获取当前日期
val today: LocalDate = Clock.System.todayIn(TimeZone.currentSystemDefault())时间算术运算深度实战
时间计算分为“基于持续时间”和“基于日历周期”两种模型。
1. 基于 Duration 的物理计算
Duration 代表精确的物理时间跨度(纳秒精度),不受日历规则影响。
kotlin
val start = Instant.parse("2024-01-01T00:00:00Z")
// 增加 24 小时 (物理上的 86400 秒)
val nextDay = start + 24.hours
// 计算两个时刻之间的物理间隔
val duration = now - start
println("已过去 ${duration.inWholeDays} 天")2. 基于 Period 的日历计算
DateTimePeriod 或 DatePeriod 用于执行符合人类直觉的日历计算(如“加一个月”)。这种计算必须在特定时区的语境下进行。
kotlin
val jan31 = LocalDate(2024, 1, 31)
// 增加一个月:库会自动处理 1月31日 -> 2月29日 (闰年) 的逻辑
val feb29 = jan31 + DatePeriod(months = 1)kotlin
val zone = TimeZone.of("Europe/Berlin")
val dateTime = LocalDateTime(2024, 3, 30, 10, 0)
val instant = dateTime.toInstant(zone)
// 增加一天的日历周期 (会自动处理夏令时切换导致的 23 或 25 小时问题)
val nextDayInstant = instant.plus(1, DateTimeUnit.DAY, zone)比较、区间与范围判定
kotlin
val deadline = Instant.parse("2024-12-31T23:59:59Z")
val now = Clock.System.now()
// 1. 基础比较
if (now > deadline) {
println("任务已过期")
}
// 2. 差值计算
val daysRemaining = now.until(deadline, DateTimeUnit.DAY, TimeZone.UTC)
println("剩余 $daysRemaining 天")解析与格式化细节
kotlinx-datetime 严格遵循 ISO-8601 标准。
kotlin
// 包含时区的完整时刻
val instant = Instant.parse("2024-05-20T10:00:00Z")
// 纯日期解析
val date = LocalDate.parse("2024-05-20")kotlin
// 目前 KMP 库本身不内置 DateTimeFormatter,需桥接平台 API
fun LocalDateTime.format(pattern: String): String {
return this.toJavaLocalDateTime()
.format(java.time.format.DateTimeFormatter.ofPattern(pattern))
}单元测试中的时间模拟
通过自定义 Clock 接口,可以轻松模拟过去或未来的时间,而无需修改系统时钟。
kotlin
class FakeClock(var fixedInstant: Instant) : Clock {
override fun now(): Instant = fixedInstant
}
@Test
fun testFeature() {
val fakeClock = FakeClock(Instant.parse("2020-01-01T00:00:00Z"))
val service = MyService(fakeClock)
// 此时 service 内部获取到的“当前时间”永远是 2020年元旦
assertEquals(2020, service.getCurrentYear())
}核心工程准则
- 绝对时刻存 Instant:数据库存储和网络 API 交互应统一使用 UTC 的
Instant(ISO 字符串或毫秒戳)。 - 显示时转 LocalDateTime:仅在最外层 UI 展示时,根据用户当前设备的时区将
Instant转换为LocalDateTime。 - 区分运算语义:如果是计算“30天后”,使用
plus(30.days);如果是计算“下个月同一天”,使用plus(DatePeriod(months = 1))。 - 注入 Clock 依赖:业务逻辑类应通过构造函数接收
Clock实例,以确保逻辑的可测试性和确定性。