1. 简介

在开发软件时,处理时间间隔(duration)是常见需求。Kotlin 提供了 Duration 类,封装了一套强大且易用的时间间隔操作 API。

本文将深入介绍 Kotlin 中的 Duration 类,帮助你高效、准确地处理各类时间计算场景,比如任务调度、超时控制、耗时统计等,避免踩坑 ❌。

2. 什么是 Duration?

Duration 表示一段具体的时间长度,可以是正数、负数、零,甚至是无限长(infinite)

这类时间间隔通过 DurationUnit 枚举来定义单位,支持:

  • DAYS
  • HOURS
  • MINUTES
  • SECONDS
  • MILLISECONDS
  • MICROSECONDS
  • NANOSECONDS

⚠️ 注意:超过一天的时间统一以“天”为单位表示,即使实际是周或月。Kotlin 并不直接支持 YEARSMONTHS 单位(尽管 ISO8601 支持),这是为了规避闰年、月份天数不一致等复杂问题。

与用于标记某个具体时刻的日期时间类型(如 LocalDateTime)不同,Duration 只关心“经过了多久”,而不关联任何具体时间点。例如,10.days 表示整整 10 天(即 240 小时),它描述的是一个时间段。

典型应用场景

3. 创建 Duration 实例

Kotlin 提供了多种方式创建 Duration,以下是常用方法。

3.1 使用伴生对象扩展函数(推荐)

Kotlin 在 Duration 的伴生对象中提供了简洁的扩展函数,可直接对数字调用:

val tenMinutes = 10.minutes 
val tenSeconds = 10.seconds
val tenHours = 10.hours
val tenDays = 10.days
val tenMillis = 10.milliseconds
val tenMicros = 10.microseconds
val tenNanos = 10.nanoseconds

此外,还可以使用预定义常量:

val zero = Duration.ZERO        // 零时长
val infinite = Duration.INFINITE // 无限时长

Duration.INFINITE 特别适合配置永不超时的场景,比如某些后台任务或长轮询接口。

3.2 使用 toDuration() 方法

另一种方式是通过 toDuration() 扩展函数,显式传入单位:

val tenMinutes = 10.toDuration(DurationUnit.MINUTES)
val tenSeconds = 10.toDuration(DurationUnit.SECONDS)

该方法适用于动态单位场景,代码更清晰,尤其在单位来自变量时推荐使用。

3.3 从 ISO8601 字符串解析

✅ 支持从 ISO8601 格式的字符串创建 Duration

格式说明:

  • P 表示 Period 开始
  • T 表示 Time 部分开始
  • 示例:
    • P10D → 10 天
    • PT10M → 10 分钟(无天数部分)
    • P10DT1H → 10 天 1 小时
    • P10DT1H5M7S → 10 天 1 小时 5 分 7 秒

转换代码如下:

val tenMinDuration = Duration.parseIsoString("PT10M")
val tenDays = Duration.parseIsoString("P10D")
val tenDaysAndOneHour = Duration.parseIsoString("P10DT1H")
val tenDaysWithAllUnits = Duration.parseIsoString("P10DT1H5M7S")

⚠️ 注意:Kotlin 的 Duration.parseIsoString() 仅支持到 D(天),不支持 Y(年)和 M(月),这点与完整 ISO8601 标准有差异,使用时需注意。

4. Duration 操作

Duration 提供了丰富的操作方法,涵盖转换、运算、比较等。

4.1 单位转换与格式化

可以将 Duration 转换为指定单位的整数值:

val tenMinutes = 10.minutes
assertEquals(10L, tenMinutes.inWholeMinutes)
assertEquals(600L, tenMinutes.inWholeSeconds)

也可以转换为 ISO8601 字符串:

val tenSeconds = 10.seconds 
assertEquals("PT10S", tenSeconds.toIsoString())

val tenDaysAndOneHour = Duration.parseIsoString("P10DT1H")
assertEquals("PT241H", tenDaysAndOneHour.toIsoString())

⚠️ 注意:toIsoString() 会将所有时间归一化为小时表示(即 D 被转为 H),所以 P10D 会变成 PT240H。这是设计行为,不是 bug。

若需按天、小时、分钟等分别提取,可用 toComponents()

val seventyMinutes = 70.minutes
val asStr = seventyMinutes.toComponents { hrs, min, sec, nanos -> "${hrs}:${min}" }
assertEquals("1:10", asStr)

该方法支持 lambda 回调,便于格式化输出或业务处理。

4.2 算术运算

Duration 支持常见的算术操作,单位会自动转换:

val tenMinutes = 10.minutes
val fiveHours = 5.hours

val sum = tenMinutes + fiveHours
assertEquals(310L, sum.inWholeMinutes)

val diff = fiveHours - tenMinutes
assertEquals(290L, diff.inWholeMinutes)

val triple = tenMinutes.times(3)
assertEquals(30L, triple.inWholeMinutes)

val sixth = tenMinutes.div(100)
assertEquals(6, sixth.inWholeSeconds)

✅ 除了 +- 等操作符,也可使用 plus()minus() 方法,效果相同。

这种统一的运算模型极大简化了混合单位的处理逻辑。

4.3 比较操作

支持直接使用比较操作符:

val tenMinutes = 10.minutes
val fiveHours = 5.hours

assertTrue { fiveHours > tenMinutes }
assertFalse { fiveHours < tenMinutes }
assertTrue { fiveHours == 300.minutes }

同时也提供语义化判断方法:

  • isInfinite():是否无限长
  • isNegative():是否为负数
  • isFinite():是否有限(非无限)
  • isZero():是否为零

这些方法在条件判断中非常实用。

4.4 计算两个 DateTime 之间的时间差

可以通过 Java 的 java.time.Duration.between() 计算两个时间点之间的间隔,并转换为 Kotlin 的 Duration

val datetime1 = LocalDateTime.now()
val datetime2 = LocalDateTime.now().minusDays(1).minusHours(1)

val duration = java.time.Duration.between(datetime2, datetime1).toKotlinDuration()
val expectedDuration = 25.hours

assertEquals(expectedDuration, duration)

关键点:

  • 使用 java.time.Duration.between(startTime, endTime)
  • 调用 .toKotlinDuration() 扩展函数转换为 Kotlin 类型

4.5 实际应用示例

测量代码执行耗时

@ExperimentalTime
fun main() {
    val elapsedTime = kotlin.time.measureTime {
        Thread.sleep(510)
    }
    println(elapsedTime) // 输出类似 "510ms"
}

⚠️ 需启用 @ExperimentalTime 注解,生产环境建议封装成工具类。

Coroutine 中的延迟执行

@OptIn(ExperimentalTime::class)
fun main() = runBlocking {
    val delayDuration = 1000.milliseconds
    println("Task will execute after a delay of $delayDuration")
    delay(delayDuration)
    println("Task executed")
}

delay() 函数接受 Duration 类型参数,比传 long 更直观、安全。

5. 总结

Kotlin 的 Duration 类为时间间隔管理提供了现代化、类型安全的解决方案。相比原始的 Long + 单位注释方式,它具备以下优势:

  • ✅ 语法简洁:10.minutes 直观易读
  • ✅ 类型安全:避免单位混淆
  • ✅ 运算友好:支持加减乘除、比较
  • ✅ 标准兼容:支持 ISO8601 解析与格式化
  • ✅ 场景丰富:适用于耗时统计、超时控制、协程调度等

在实际项目中,建议全面替换 Long 表示时间的做法,统一使用 Duration,提升代码可维护性与健壮性 ✅。


原始标题:Guide to Duration in Kotlin