Instant — 来自java.time包的不可变类,以纳秒精度表示UTC时间线上的一个点。与LocalDateTime不同,Instant不包含人类可读格式的日期和时间——它是时刻的机器表示。根据Oracle Java 17 (2024)规范,Instant设计用于机器间的时间戳交换,是System.currentTimeMillis()的类似物,但具有纳秒精度。
要点
Instant — 是一个模拟时间线上单个点的类。其内部表示由两个字段组成:long seconds(从1970-01-01T00:00:00Z开始的秒数)和int nanos(当前秒内的纳秒数,从0到999999999)。
Instant的取值范围 — 从-31557014167219200到31556889864403199秒(从纪元算起),覆盖了大约2.92亿年(双向)。这足以满足任何实际任务,包括天文计算。
根据Baeldung (2024),Instant是人类可读类型(LocalDateTime、ZonedDateTime)和机器格式(毫秒时间戳)之间的桥梁。Instant用于日志记录、缓存、同步以及所有需要绝对时间时刻的任务。
该类实现了 Comparable(用于比较时刻)和 Temporal(用于在通用java.time API中使用)接口。Instant是不可变的——所有方法都返回新对象。
在Java 8之前,java.util.Date和System.currentTimeMillis()被用于处理时间时刻。两种方法都有缺点。Date是可变的,非线程安全,以毫秒为单位存储从纪元开始的时间,但方法名称已过时(getYear()对2016年返回116)。
Long(简单时间戳)快速且紧凑,但没有内置的纳秒支持,不以可读形式显示,并且在调试时需要手动解析。Long方法也不区分数据类型——开发人员可能传递错误的值。
Instant解决了所有这些问题。它是不可变的,包含关于精度的明确信息(秒+纳秒),序列化为ISO-8601格式"2026-07-21T15:00:00Z",并拥有丰富的转换API。根据SonarSource (2024),Instant是所有新项目中推荐的Date替代品。
当前时刻通过 Instant.now() 获取。与LocalDateTime.now()不同,Instant.now()始终返回UTC时间,忽略设备的时区。这使其成为服务器时间戳的理想选择。
从现有值:Instant.ofEpochSecond(long epochSecond) — 从纪元开始的秒数,Instant.ofEpochMilli(long epochMilli) — 从毫秒数,Instant.parse(CharSequence) — 从ISO-8601字符串("2026-07-21T15:00:00Z")。
读取使用getEpochSecond() — 从纪元开始的秒数,toEpochMilli() — 毫秒数,getNano() — 纳秒。toString()方法返回ISO-8601格式的字符串。
val now = Instant.now()
val fromSeconds = Instant.ofEpochSecond(1784700000)
val fromMillis = Instant.ofEpochMilli(1784700000000)
val parsed = Instant.parse("2026-07-21T15:00:00Z")
val epochSecond = now.getEpochSecond()
val epochMilli = now.toEpochMilli()
val nanos = now.getNano()
Instant通过atZone(ZoneId)转换为 ZonedDateTime。例如,Instant.now().atZone(ZoneId.of("Europe/Moscow"))将为莫斯科返回ZonedDateTime。没有时区,转换是不可能的——Instant不包含日历信息。
LocalDateTime通过atZone(ZoneId).toLocalDateTime()转换。这种方法明确且不丢失信息。反向转换 — LocalDateTime.atZone(ZoneId).toInstant()。
为了与java.util.Date兼容:Date.from(instant)和date.toInstant()。这是一个双向转换,保持毫秒精度(Date不支持纳秒)。要使用 java.sql.Timestamp,使用支持纳秒的Timestamp.from(instant)。
val instant = Instant.now()
val zoned = instant.atZone(ZoneId.of("Europe/Moscow"))
val localDateTime = instant
.atZone(ZoneId.systemDefault())
.toLocalDateTime()
val oldDate = Date.from(instant)
val backToInstant = oldDate.toInstant()
Instant 的关键特性——它完全独立于时区。Instant.now()在世界任何地方的任何设备上都返回相同的结果。这是通过将时间固定在UTC实现的。
时区仅用于向人类显示Instant。为此使用atZone(ZoneId)。ZoneId.systemDefault()返回操作系统中设置的设备时区。ZoneOffset.UTC — UTC的常量。
在分布式系统中,建议将所有时间戳存储和传输为Instant(或带ZoneOffset.UTC的OffsetDateTime)。转换为本地时间仅在客户端向用户显示之前执行。这可以防止时区混淆。
在分布式Android应用程序中,时间同步对于缓存、通知和协同编辑的正确运行至关重要。Instant — 由于与UTC的关联,是该任务的自然选择。
在比较来自不同设备的时间戳时,需要考虑到系统时钟可能存在差异。建议使用服务器时间作为参考。服务器返回UTC中的Instant,客户端仅用于相对计算与本地Instant进行比较。
计算两个时刻之间的差异使用 Duration.between(Instant start, Instant end)。此方法返回Duration — 可以转换为小时、分钟、秒的持续时间。isAfter()和isBefore()方法允许比较时刻。
fun isCacheExpired(
cachedAt: Instant,
ttlMinutes: Long
): Boolean {
val elapsed = Duration.between(cachedAt, Instant.now())
return elapsed.toMinutes() >= ttlMinutes
}
第一个示例 — 使用时间戳记录事件。Instant存储在Room数据库中并发送到服务器。时间戳在UTC中记录以进行唯一解释。
data class EventLog(
val id: Long = 0,
val eventName: String,
val timestamp: Instant
)
class Converters {
@TypeConverter
fun fromInstant(value: Instant?): Long? {
return value?.toEpochMilli()
}
@TypeConverter
fun toInstant(value: Long?): Instant? {
return value?.let { Instant.ofEpochMilli(it) }
}
}
第二个示例 — 确定自事件以来经过的时间。我们使用 Duration.between 显示"5分钟前","2小时前" — 这是即时通讯工具和社交媒体中常见的格式。
fun timeAgo(instant: Instant): String {
val duration = Duration.between(instant, Instant.now())
return when {
duration.toMinutes() < 1 -> "just now"
duration.toHours() < 1 -> "${duration.toMinutes()} min ago"
duration.toDays() < 1 -> "${duration.toHours()} h ago"
else -> "${duration.toDays()} d ago"
}
}
第三个示例 — 服务器和客户端之间的数据同步。我们使用 Instant 跟踪上次更新时间。
class SyncManager {
private var lastSyncAt: Instant? = null
fun sync() {
val syncStart = Instant.now()
// 带有lastSyncAt的服务器请求
lastSyncAt = syncStart
}
fun shouldSync(intervalMinutes: Long): Boolean {
val last = lastSyncAt ?: return true
return Duration.between(last, Instant.now())
.toMinutes() >= intervalMinutes
}
}
第一个错误 — 使用 Instant.now().toString() 向用户显示。Instant以UTC格式"2026-07-21T15:00:00Z"显示,这对人类来说不可读。在显示之前,始终通过atZone()将Instant转换为本地时区。
第二个错误 — 转换为java.util.Date时丢失纳秒。Date仅支持毫秒。如果Instant有纳秒,它们将在Date.from(instant)时丢失。使用 Instant.truncatedTo(ChronoUnit.MILLIS) 明确指定精度。
第三个错误 — 混淆toEpochMilli()和getEpochSecond()。toEpochMilli()返回从纪元开始的毫秒数(long),而getEpochSecond()返回秒数(long)。混淆这些方法可能导致1000倍的错误。
第四个错误 — 假设Instant.now()在所有设备上同步。系统时钟可能相差几分钟甚至几小时。对于时间敏感的操作(身份验证、支付),使用 服务器Instant 作为真相来源。
常见问题
System.currentTimeMillis()返回long — 从纪元开始的毫秒数,不关联时区。Instant提供相同的功能,但具有纳秒精度和丰富的API,用于转换、比较和与java.time的兼容性。
Room不直接支持Instant。使用 TypeConverter 将Instant转换为Long(toEpochMilli)和反向(Instant.ofEpochMilli)。对于纳秒精度,保存两个字段:纪元秒和纳秒。
是的,Instant是不可变的,并且正确实现了equals()和hashCode()。两个值相同的Instant将相等。与可变的java.util.Date不同,这使其成为 HashMap 和其他集合的可靠键。
使用 Duration.between(start, end) 获取Duration,或使用 ChronoUnit.SECONDS.between(start, end) 获取以秒为单位的差异(long)。Duration提供toMinutes()、toHours()、toDays()和toNanos()方法。
Instant被设计为时间线上的绝对点。如果不指定时区或UTC,解析是不可能的,因为 Instant 不包含日历信息。后缀"Z"表示零偏移(UTC),是ISO-8601格式所必需的。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。