首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >ZonedDateTime比较:预期:[Etc/UTC],但为:[UTC]

ZonedDateTime比较:预期:[Etc/UTC],但为:[UTC]
EN

Stack Overflow用户
提问于 2018-01-26 13:57:27
回答 3查看 12K关注 0票数 19

我比较了两个看似相等的日期,但它们包含不同的区域名称:一个是Etc/UTC,另一个是UTC

根据这个问题:UTC和Etc/UTC时区之间有什么区别吗? -这两个区域是相同的。但我的测试失败了:

代码语言:javascript
复制
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));
    }
}

结果:

代码语言:javascript
复制
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内部的这个实现

代码语言:javascript
复制
cmp = getZone().getId().compareTo(other.getZone().getId());

断言结果:

代码语言:javascript
复制
java.lang.AssertionError: 
Expected :0
Actual   :-16

因此,问题就在ZoneId实现的某个地方。但是,如果这两个区域I都是有效的,并且都指定了相同的区域,那么它们应该是相等的。

我的问题是:是图书馆的错误,还是我做错了什么?

更新

有几个人试图让我相信这是一种正常的行为,比较方法的实现使用ZoneIdString id表示是正常的。在这种情况下,我应该问,为什么下面的测试运行良好?

代码语言:javascript
复制
    @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相同,那么我看到两个选项:

  • compareTo/equals方法不应该使用ZoneId id,而是应该比较它们的规则
  • Zone.of(...)坏了,应该将Etc/UTCUTC视为相同的时区。

否则,我不明白为什么UTC+0UTC工作得很好。

更新-2我报告了一个错误,ID : 9052414。看看甲骨文团队会做出什么决定。

UPDATE-3 bug报告已被接受(不知道他们会将其关闭为“不会修复”或“不会修复”):https://bugs.openjdk.java.net/browse/JDK-8196398

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2018-01-29 17:32:38

您可以将ZonedDateTime对象转换为Instant,就像其他答案/注释已经告诉的那样。

ZonedDateTime::isEqual

也可以使用方法,如果两个ZonedDateTime实例对应于同一个Instant,则进行比较。

代码语言:javascript
复制
ZonedDateTime now = ZonedDateTime.now();
ZonedDateTime zoneDateTimeEtcUtc = now.withZoneSameInstant(ZoneId.of("Etc/UTC"));
ZonedDateTime zoneDateTimeUtc = now.withZoneSameInstant(ZoneId.of("UTC"));

Assert.assertTrue(zoneDateTimeEtcUtc.isEqual(zoneDateTimeUtc));
票数 13
EN

Stack Overflow用户

发布于 2018-01-26 14:50:23

ZonedDateTime在其equals()-method中使用内部ZoneId-members的比较。因此,我们在该类中看到(Java-8中的源代码):

代码语言:javascript
复制
/**
 * 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的规则,而不是词汇表示。那就问问规则:

代码语言:javascript
复制
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**.比较的另一个重要评论看这里:

代码语言:javascript
复制
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-发行已经被甲骨文关闭,因为它“不是一个问题”。

票数 3
EN

Stack Overflow用户

发布于 2018-01-26 14:24:12

如果要比较时间,应该将ZoneDateTime转换为OffsetDateTime,然后将其与compareTo(..)进行比较。

ZoneDateTime.equalsZoneDateTime.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的原因。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/48462758

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档