我比较了两个看似相等的日期,但它们包含不同的区域名称:一个是Etc/UTC,另一个是UTC。
根据这个问题:UTC和Etc/UTC时区之间有什么区别吗? -这两个区域是相同的。但我的测试失败了:
import org.junit.Test;
import java.sql.Timestamp;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import static org.junit.Assert.assertEquals;
public class TestZoneDateTime {
@Test
public void compareEtcUtcWithUtc() {
ZonedDateTime now = ZonedDateTime.now();
ZonedDateTime zoneDateTimeEtcUtc = now.withZoneSameInstant(ZoneId.of("Etc/UTC"));
ZonedDateTime zoneDateTimeUtc = now.withZoneSameInstant(ZoneId.of("UTC"));
// This is okay
assertEquals(Timestamp.from(zoneDateTimeEtcUtc.toInstant()), Timestamp.from(zoneDateTimeUtc.toInstant()));
// This one fails
assertEquals(zoneDateTimeEtcUtc,zoneDateTimeUtc);
// This fails as well (of course previous line should be commented!)
assertEquals(0, zoneDateTimeEtcUtc.compareTo(zoneDateTimeUtc));
}
}结果:
java.lang.AssertionError:
Expected :2018-01-26T13:55:57.087Z[Etc/UTC]
Actual :2018-01-26T13:55:57.087Z[UTC]更具体地说,我预计ZoneId.of("UTC")将等同于ZoneId.of("Etc/UTC"),但它们不是!
作为@NicolasHenneaux 建议,我可能应该使用compareTo(...)方法。这是个好主意,但是zoneDateTimeEtcUtc.compareTo(zoneDateTimeUtc)返回-16值,因为ZoneDateTime内部的这个实现
cmp = getZone().getId().compareTo(other.getZone().getId());断言结果:
java.lang.AssertionError:
Expected :0
Actual :-16因此,问题就在ZoneId实现的某个地方。但是,如果这两个区域I都是有效的,并且都指定了相同的区域,那么它们应该是相等的。
我的问题是:是图书馆的错误,还是我做错了什么?
更新
有几个人试图让我相信这是一种正常的行为,比较方法的实现使用ZoneId的String id表示是正常的。在这种情况下,我应该问,为什么下面的测试运行良好?
@Test
public void compareUtc0WithUtc() {
ZonedDateTime now = ZonedDateTime.now();
ZoneId utcZone = ZoneId.of("UTC");
ZonedDateTime zonedDateTimeUtc = now.withZoneSameInstant(utcZone);
ZoneId utc0Zone = ZoneId.of("UTC+0");
ZonedDateTime zonedDateTimeUtc0 = now.withZoneSameInstant(utc0Zone);
// This is okay
assertEquals(Timestamp.from(zonedDateTimeUtc.toInstant()), Timestamp.from(zonedDateTimeUtc0.toInstant()));
assertEquals(0, zonedDateTimeUtc.compareTo(zonedDateTimeUtc0));
assertEquals(zonedDateTimeUtc,zonedDateTimeUtc0);
}如果Etc/UTC 与UTC相同,那么我看到两个选项:
Zone.of(...)坏了,应该将Etc/UTC和UTC视为相同的时区。否则,我不明白为什么UTC+0和UTC工作得很好。
更新-2我报告了一个错误,ID : 9052414。看看甲骨文团队会做出什么决定。
UPDATE-3 bug报告已被接受(不知道他们会将其关闭为“不会修复”或“不会修复”):https://bugs.openjdk.java.net/browse/JDK-8196398
发布于 2018-01-29 17:32:38
您可以将ZonedDateTime对象转换为Instant,就像其他答案/注释已经告诉的那样。
ZonedDateTime::isEqual
也可以使用方法,如果两个ZonedDateTime实例对应于同一个Instant,则进行比较。
ZonedDateTime now = ZonedDateTime.now();
ZonedDateTime zoneDateTimeEtcUtc = now.withZoneSameInstant(ZoneId.of("Etc/UTC"));
ZonedDateTime zoneDateTimeUtc = now.withZoneSameInstant(ZoneId.of("UTC"));
Assert.assertTrue(zoneDateTimeEtcUtc.isEqual(zoneDateTimeUtc));发布于 2018-01-26 14:50:23
类ZonedDateTime在其equals()-method中使用内部ZoneId-members的比较。因此,我们在该类中看到(Java-8中的源代码):
/**
* Checks if this time-zone ID is equal to another time-zone ID.
* <p>
* The comparison is based on the ID.
*
* @param obj the object to check, null returns false
* @return true if this is equal to the other time-zone ID
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof ZoneId) {
ZoneId other = (ZoneId) obj;
return getId().equals(other.getId());
}
return false;
}词法表示"Etc/UTC“和"UTC”显然是不同的字符串,因此zoneId比较会产生false。这种行为是在javadoc中描述的,所以我们没有bug。我强调文档的声明
比较是基于ID的。
也就是说,ZoneId就像一个指向区域数据的命名指针,并不表示数据本身。
但我假设您更愿意比较不同区域I的规则,而不是词汇表示。那就问问规则:
ZoneId z1 = ZoneId.of("Etc/UTC");
ZoneId z2 = ZoneId.of("UTC");
System.out.println(z1.equals(z2)); // false
System.out.println(z1.getRules().equals(z2.getRules())); // true所以您可以使用区域规则与ZonedDateTime的其他非区域相关成员的比较(有点尴尬)。
顺便说一句,我强烈建议不要使用“Etc/.”-identifiers,因为(除了"Etc/GMT“或"Etc/UTC")它们的偏移符号是相反的,而不是通常预期的。
关于ZonedDateTime**-instances**.比较的另一个重要评论看这里:
System.out.println(zoneDateTimeEtcUtc.compareTo(zoneDateTimeUtc)); // -16
System.out.println(z1.getId().compareTo(z2.getId())); // -16我们发现,在相同时间和局部时间戳下,ZonedDateTime-instances的比较是基于区域ids的词法比较。通常不像大多数用户所期望的那样。但它也不是错误,因为这种行为是在API中描述。这是我不喜欢使用ZonedDateTime类型的另一个原因。它本身就太复杂了。您应该只将它用于Instant和本地类型IMHO之间的中间类型转换。具体意思是:在比较ZonedDateTime-instances之前,请将它们转换为Instant,然后进行比较。
2018-02-01更新:
为这个问题而打开的JDK-发行已经被甲骨文关闭,因为它“不是一个问题”。
发布于 2018-01-26 14:24:12
如果要比较时间,应该将ZoneDateTime转换为OffsetDateTime,然后将其与compareTo(..)进行比较。
ZoneDateTime.equals和ZoneDateTime.compareTo比较是否即时标识符和区域标识符,即标识时区的tth字符串。
ZoneDateTime是一个瞬间和一个区域(它的id而不仅仅是一个偏移),而OffsetDateTime是一个瞬间和一个区域偏移。如果要比较两个ZoneDateTime对象之间的时间,应该使用OffsetDateTime。
ZoneId.of(..)方法解析给出的字符串,并在需要时对其进行转换。ZoneId表示时区,而不是偏移量,即与格林尼治时间区的时间偏移。而ZoneOffset负责补偿偏移量。https://docs.oracle.com/javase/8/docs/api/java/time/ZoneId.html#of-java.lang.String-
如果区域ID等于“GMT”、“UTC”或“UT”,则结果是一个ZoneId,其ID和规则与ZoneOffset.UTC相同。 如果区域ID以“UTC+”、“UTC-”、“GMT+”、“GMT-”、“UT+”或“UT-”开头,则该ID是一个前缀基于偏移量的ID。该ID被分割为两个或三个字母前缀和以符号开头的后缀。后缀被解析为ZoneOffset。结果将是一个ZoneId,它具有指定的UTC/GMT/UT前缀和ZoneOffset.getId()的规范化偏移ID。返回的ZoneId的规则将与解析的ZoneOffset等效。 所有其他ID都被解析为基于区域的区域ID。区域is必须与正则表达式a+匹配,否则将引发DateTimeException。如果区域ID不在已配置的ID集合中,则抛出ZoneRulesException。区域ID的详细格式取决于提供数据的组。默认数据集由IANA时区数据库(TZDB)提供。它具有“{area}/{city}”形式的区域ID,例如“Europe/Paris”或“America/New_York”。这与来自TimeZone的大多数ID兼容。
因此,UTC+0被转换为UTC,而Etc/UTC则保持不变而不作修改。
ZoneDateTime.compareTo比较id ("Etc/UTC".compareTo("UTC") == 16)的字符串。这就是你应该使用OffsetDateTime的原因。
https://stackoverflow.com/questions/48462758
复制相似问题