You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何规范化ZonedDateTime使.equals()方法生效?

问题:ZonedDateTime的.equals()与.isEqual()结果不一致,如何让.equals()返回true?

先看你的代码:

import java.time._
object app {
 def main (args :Array[String]) = {
 println("app started")
 // create two ZonedDateTime objects for 1st Jan 2018, 10am UTC
 // using separate methods
 val zdt1 = ZonedDateTime.of(2018, 1, 1, 10, 0, 0, 0, ZoneId.of("UTC"))
 val zdt2 = ZonedDateTime.parse("2018-01-01T10:00:00Z")
 println(s"COMPARING: $zdt1 and $zdt2")
 println("== check: " + (zdt1 == zdt2))
 println(".equals check: " + (zdt1.equals(zdt2)))
 println(".isEqual check " + (zdt1.isEqual(zdt2)))
 println("app finished")
 }
}

为什么会出现这个差异?

核心原因在于ZonedDateTime的两个比较方法逻辑完全不同:

  • .isEqual():只比较两个对象代表的瞬时时间(timestamp),不管时区是否一致。只要它们对应的Unix时间戳相同,就返回true。
  • .equals():会同时比较瞬时时间和时区信息(ZoneId/ZoneOffset)。只有当两者的时间戳和时区都完全匹配时,才会返回true。

在你的代码里,zdt1使用ZoneId.of("UTC")创建,而zdt2通过解析带Z的字符串得到,它的时区是ZoneOffset.UTC——虽然两者都是UTC时区,但ZoneId和ZoneOffset是java.time中不同的实现类,导致.equals()判定为不相等。

解决方案:让.equals()返回true的几种方法

1. 规范化所有ZonedDateTime的时区

确保所有日期时间对象都使用完全相同的时区实例,比如统一用ZoneOffset.UTC:

// 创建时直接使用ZoneOffset.UTC
val zdt1 = ZonedDateTime.of(2018, 1, 1, 10, 0, 0, 0, ZoneOffset.UTC)
val zdt2 = ZonedDateTime.parse("2018-01-01T10:00:00Z") // 解析出来的就是ZoneOffset.UTC

// 对已有的ZonedDateTime进行规范化转换
val normalizedZdt1 = zdt1.withZoneSameInstant(ZoneOffset.UTC)
val normalizedZdt2 = zdt2.withZoneSameInstant(ZoneOffset.UTC)

这样处理后,两个对象的时区完全一致,.equals()就会返回true。

2. 改用Instant存储时间(如果不需要时区信息)

如果你不需要保留时区上下文,直接使用Instant类(它只表示瞬时时间)是更简单的选择。Instant.equals()的逻辑和ZonedDateTime.isEqual()完全一致:

val instant1 = zdt1.toInstant()
val instant2 = zdt2.toInstant()
println(instant1.equals(instant2)) // 输出true

3. 重写自定义类的equals方法(如果ZonedDateTime是类字段)

如果这些日期时间是你自定义类的字段,你可以重写类的equals方法,在比较日期时使用isEqual()而非默认的equals()。记得同时重写hashCode保证一致性:

class MyEntity(val eventTime: ZonedDateTime) {
  override def equals(obj: Any): Boolean = obj match {
    case other: MyEntity => eventTime.isEqual(other.eventTime)
    case _ => false
  }

  override def hashCode(): Int = eventTime.toInstant().hashCode()
}

4. 在测试中使用自定义匹配器(针对ScalaTest的beEquals)

如果你的测试套件用的是ScalaTest,不想修改业务代码的话,可以自定义一个匹配器,让beEquals用isEqual()来比较ZonedDateTime:

import org.scalatest.matchers.{Matcher, MatchResult}

def beTimeEqual(expected: ZonedDateTime): Matcher[ZonedDateTime] = 
  new Matcher[ZonedDateTime] {
    def apply(actual: ZonedDateTime): MatchResult = MatchResult(
      actual.isEqual(expected),
      s"Actual time $actual is not equal to expected time $expected",
      s"Actual time $actual matches expected time $expected"
    )
  }

// 测试中使用
myEntity.eventTime should beTimeEqual(expectedZdt)

内容的提问来源于stack exchange,提问作者Philluminati

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:03:16