如何规范化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
相关产品推荐
相关产品推荐

