ZonedDateTime比较:预期:[Etc / UTC]但是:[UTC]

时间:2018-01-26 13:57:28

标签: java date datetime zoneddatetime datetime-comparison

我正在比较似乎相同的两个日期,但它们包含不同的区域名称:一个是closeDialog('dialog_gui'); function closeDialog(elementId){ let dialog = new mdc.dialog.MDCDialog(document.getElementById(elementId)); dialog.close(); } ,另一个是Etc/UTC

根据这个问题:Is there a difference between the UTC and Etc/UTC time zones? - 这两个区域是相同的。但我的测试失败了:

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"),但它们不是!{/ p>

作为@NicolasHenneaux suggested,我应该使用ZoneId.of("Etc/UTC")方法。这是个好主意,但是compareTo(...)会返回zoneDateTimeEtcUtc.compareTo(zoneDateTimeUtc)值,因为-16中的这个实现:

ZoneDateTime

断言结果:

cmp = getZone().getId().compareTo(other.getZone().getId());

所以问题出在java.lang.AssertionError: Expected :0 Actual :-16 实施的某个地方。但我仍然希望如果两个区域ID都有效且两者都指定相同的区域,那么它们应该相等。

我的问题是:它是库错误,还是我做错了什么?

更新

有些人试图说服我这是正常的行为,并且正常比较方法的实现使用了ZoneId id表示String。在这种情况下,我应该问,为什么以下测试运行正常?

ZoneId

如果 @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相同,那么我会看到两个选项:

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

否则我不明白为什么UTCUTC+0正常工作。

UPDATE-2 我已经报告了一个错误,ID:9052414。将会看到Oracle团队将会做出决定。

UPDATE-3 错误报告已被接受(不知道是否将其关闭为“无法修复”):https://bugs.openjdk.java.net/browse/JDK-8196398

3 个答案:

答案 0 :(得分:7)

您可以将ZonedDateTime个对象转换为Instant,因为已经告知了其他答案/评论。

ZonedDateTime::isEqual

或者您可以使用isEqual method,比较两个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));

答案 1 :(得分:4)

班级ZonedDateTime在其equals() - 方法中使用内部ZoneId - 成员的比较。所以我们在那个类中看到了(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-comparison通常会产生false。这种行为在javadoc中有描述,所以我们没有错误。我强调documentation

的陈述
  

比较基于ID。

也就是说,ZoneId就像是指向区域数据的命名指针,并不代表数据本身。

但我认为您更愿意比较两个不同区域ID的规则而不是词法表示。然后问规则:

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 /..."-标识符,因为(除了”Etc / GMT“或”Etc / UTC“)他们的偏移标志是反向的< / strong>比通常预期的要好。

关于ZonedDateTime - 实例的比较的另一个重要评论。看这里:

System.out.println(zoneDateTimeEtcUtc.compareTo(zoneDateTimeUtc)); // -16
System.out.println(z1.getId().compareTo(z2.getId())); // -16

我们看到ZonedDateTime - 具有相同即时和本地时间戳的实例的比较是基于zone-id的词汇比较。通常不是大多数用户所期望的。但这也不是错误,因为这种行为是described in the API。这是我不喜欢使用ZonedDateTime类型的另一个原因。它本质上太复杂了。您应该只将它用于Instant和本地类型恕我直言之间的中间类型转换。具体意味着:在比较ZonedDateTime - 个实例之前,请将它们转换(通常转换为Instant),然后进行比较。

从2018-02-01更新:

为此问题打开的JDK-issue已被Oracle关闭为“不是问题”。

答案 2 :(得分:2)

type :: my_type1 real(KIND=DP), dimension(:), allocatable :: elem1 ! interest rate contains procedure, nopass :: getMean => getMean_my_type1 end type my_type1 interface assignment (=) module procedure assignEachThatInThis_my_type1, assignThatInThis_my_type1 end interface 应转换为ZoneDateTime,然后与OffsetDateTime进行比较,以便比较时间。

compareTo(..)ZoneDateTime.equals比较即时和区域标识符,即标识时区的第t个字符串。

ZoneDateTime.compareTo是一个瞬间和一个区域(带有id而不仅仅是一个偏移量),而ZoneDateTime是一个瞬间和一个区域偏移。如果要比较两个OffsetDateTime对象之间的时间,则应使用ZoneDateTime

方法OffsetDateTime正在解析您提供的字符串,并在需要时对其进行转换。 ZoneId.of(..)表示时区而不是偏移量,即GMT时区的时移。虽然ZoneId表示抵消。 https://docs.oracle.com/javase/8/docs/api/java/time/ZoneId.html#of-java.lang.String-

  

如果区域ID等于'GMT','UTC'或'UT',则结果是具有相同ID的ZoneId,并且规则等同于ZoneOffset.UTC。

     

如果区域ID以“UTC +”,“UTC-”,“GMT +”,“GMT-”,“UT +”或“UT-”开头,则ID是带前缀的基于偏移量的ID。 ID分为两部分,带有两个或三个字母前缀和以符号开头的后缀。后缀被解析为ZoneOffset。结果将是具有指定UTC / GMT / UT前缀的ZoneId和根据ZoneOffset.getId()的标准化偏移ID。返回的ZoneId的规则将等同于已解析的ZoneOffset。

     

所有其他ID都被解析为基于区域的区域ID。区域ID必须与正则表达式[A-Za-z] [A-Za-z0-9~ /._+-] +匹配,否则抛出DateTimeException。如果区域ID不在配置的ID集中,则抛出ZoneRulesException。区域ID的详细格式取决于提供数据的组。默认数据集由IANA时区数据库(TZDB)提供。这具有“{area} / {city}”形式的区域ID,例如“Europe / Paris”或“America / New_York”。这与TimeZone的大多数ID兼容。

所以ZoneOffset转换为UTC+0,而Etc / UTC不加修改

UTC比较id(ZoneDateTime.compareTo)的字符串。这就是你应该使用"Etc/UTC".compareTo("UTC") == 16的原因。