it-swarm.com.de

ZonedDateTime-Vergleich: erwartet: [Etc/UTC], war aber: [UTC]

Ich habe zwei Datumsangaben verglichen, die scheinbar gleich sind, aber sie enthalten einen anderen Namen für Zonen: Eines ist Etc/UTC, ein anderes ist UTC

Entsprechend dieser Frage: Gibt es einen Unterschied zwischen den Zeitzonen UTC und ETC/UTC? - diese zwei Zonen sind gleich. Aber meine Tests scheitern:

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));
    }
}

Das Ergebnis:

Java.lang.AssertionError: 
Expected :2018-01-26T13:55:57.087Z[Etc/UTC]
Actual   :2018-01-26T13:55:57.087Z[UTC]

Genauer gesagt würde ich erwarten, dass ZoneId.of("UTC") gleich ZoneId.of("Etc/UTC") wäre, aber das ist nicht der Fall!

Als @NicolasHenneaux vorgeschlagen sollte ich wahrscheinlich die compareTo(...)-Methode verwenden. Das ist eine gute Idee, aber zoneDateTimeEtcUtc.compareTo(zoneDateTimeUtc) liefert -16-Wert aufgrund dieser Implementierung in ZoneDateTime:

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

Assertionsergebnis:

Java.lang.AssertionError: 
Expected :0
Actual   :-16

Das Problem liegt also irgendwo in der ZoneId-Implementierung. Aber ich würde immer noch erwarten, dass, wenn beide Zonen-IDs gültig sind und beide dieselbe Zone bezeichnen, diese sollten gleich sind.

Meine Frage ist: Ist es ein Bibliotheksfehler oder mache ich etwas falsch?

UPDATE

Mehrere Personen versuchten, mich davon zu überzeugen, dass es sich um ein normales Verhalten handelt, und es ist normal, dass die Implementierung von Vergleichsmethoden die String id-Darstellung der ZoneId verwendet. In diesem Fall sollte ich fragen, warum der folgende Test in Ordnung ist. 

    @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);
    }

Wenn Etc/UTC das gleiche wie UTC ist, sehe ich zwei Optionen:

  • die compareTo/equals-Methode sollte ZoneId id nicht verwenden, sollte jedoch deren Regeln vergleichen
  • Zone.of(...) ist defekt und sollte Etc/UTC und UTC als gleiche Zeitzonen behandeln. 

Ansonsten sehe ich nicht, warum UTC+0 und UTC gut funktionieren.

UPDATE-2 Ich habe einen Fehler gemeldet, ID: 9052414. Mal sehen, was das Oracle-Team entscheiden wird.

UPDATE-3 Der Fehlerbericht wurde akzeptiert (weiß nicht, ob er ihn als "nicht beheben" schließen soll oder nicht): https://bugs.openjdk.Java.net/browse/JDK-8196398

9
Andremoniy

Sie können die ZonedDateTime-Objekte in Instant konvertieren, wie bereits in den anderen Antworten/Kommentaren angegeben.

ZonedDateTime::isEqual

Oder Sie können die isEqual-Methode verwenden, die vergleicht, ob beide ZonedDateTime-Instanzen der gleichen Instant entsprechen:

ZonedDateTime now = ZonedDateTime.now();
ZonedDateTime zoneDateTimeEtcUtc = now.withZoneSameInstant(ZoneId.of("Etc/UTC"));
ZonedDateTime zoneDateTimeUtc = now.withZoneSameInstant(ZoneId.of("UTC"));

Assert.assertTrue(zoneDateTimeEtcUtc.isEqual(zoneDateTimeUtc));
5
javaguest

Die Klasse ZonedDateTime verwendet in ihrer equals()- Methode den Vergleich innerer ZoneId- Member. Wir sehen also in dieser Klasse (Quellcode in 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;
}

Die lexikalischen Darstellungen "Etc/UTC" und "UTC" sind offensichtlich unterschiedliche Zeichenfolgen, sodass der zoneId-Vergleich trivial false ergibt. Dieses Verhalten ist im Javadoc beschrieben, daher haben wir keinen Fehler. Ich betone die Aussage des Dokumentation :

Der Vergleich basiert auf der ID.

Das heißt, ein ZoneId ist wie ein benannter Zeiger auf die Zonendaten und repräsentiert nicht die Daten selbst.

Aber ich gehe davon aus, dass Sie lieber die Regeln der beiden unterschiedlichen Zonen-IDs und nicht die lexikalischen Darstellungen vergleichen möchten. Dann fragen Sie nach den Regeln:

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

Sie können also den Vergleich von Zonenregeln und den anderen nicht zonenbezogenen Mitgliedern von ZonedDateTime verwenden (etwas umständlich).

Ich empfehle übrigens dringend, keine "Etc /..."- Bezeichner" zu verwenden, da (mit Ausnahme von "Etc/GMT" oder "Etc/UTC") deren Versatzzeichen sind umgekehrt als normalerweise erwartet.

Noch eine wichtige Bemerkung zum Vergleich von ZonedDateTime- Instanzen . Schau hier:

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

Wir sehen, dass der Vergleich von ZonedDateTime -Instanzen mit demselben augenblicklichen und lokalen Zeitstempel auf dem lexikalischen Vergleich von Zonen-IDs basiert. Normalerweise nicht das, was die meisten Benutzer erwarten würden. Es ist aber auch kein Fehler, da dieses Verhalten in der API beschrieben ist. Dies ist ein weiterer Grund, warum ich nicht gerne mit dem Typ ZonedDateTime arbeite. Es ist von Natur aus zu komplex. Sie sollten es nur für Zwischentypkonvertierungen zwischen Instant und den lokalen Typen IMHO verwenden. Dies bedeutet konkret: Bevor Sie ZonedDateTime- Instanzen vergleichen, konvertieren Sie diese (normalerweise in Instant) und vergleichen Sie sie dann.

Update vom 01.02.2018:

Das für diese Frage geöffnete JDK-Problem wurde von Oracle als "Not an issue" geschlossen.

4
Meno Hochschild

ZoneDateTime sollte in OffsetDateTime konvertiert und dann mit compareTo(..) verglichen werden, wenn Sie die Zeit vergleichen möchten. 

ZoneDateTime.equals und ZoneDateTime.compareTo vergleichen, ob der Zeitpunkt und die Zonenkennung, d. h. die die Zeitzone identifizierende Zeichenfolge.

ZoneDateTime ist ein Moment und eine Zone (mit ihrer ID und nicht nur einem Versatz), während OffsetDateTime ein Moment und ein Zonenversatz ist. Wenn Sie die Zeit zwischen den beiden ZoneDateTime-Objekten vergleichen möchten, sollten Sie OffsetDateTime verwenden. 

Die Methode ZoneId.of(..) analysiert den von Ihnen angegebenen String und transformiert ihn bei Bedarf. ZoneId repräsentiert die Zeitzone und nicht den Versatz, d. H. Die Zeitverschiebung mit der GMT-Zeitzone. Während ZoneOffset den Versatz darstellt, . https://docs.Oracle.com/javase/8/docs/api/Java/time/ZoneId.html#of-Java.lang.String-

Wenn die Zonen-ID gleich 'GMT', 'UTC' oder 'UT' ist, ist das Ergebnis eine ZoneId mit derselben ID und denselben Regeln, die ZoneOffset.UTC entsprechen.

Wenn die Zonen-ID mit 'UTC +', 'UTC-', 'GMT +', 'GMT-', 'UT +' oder 'UT-' beginnt, ist die ID eine vorangestellte Offset-basierte ID. Die ID besteht aus zwei, drei oder drei Buchstaben und einem Suffix, das mit dem Zeichen beginnt. Das Suffix wird als ZoneOffset analysiert. Das Ergebnis ist eine ZoneId mit dem angegebenen UTC/GMT/UT-Präfix und der normalisierten Offset-ID gemäß ZoneOffset.getId (). Die Regeln der zurückgegebenen ZoneId entsprechen dem geparsten ZoneOffset.

Alle anderen IDs werden als bereichsbezogene Zonen-IDs analysiert. Regions-IDs müssen mit dem regulären Ausdruck [A-Za-z] [A-Za-z0-9 ~ /._+-+++ übereinstimmen, andernfalls wird eine DateTimeException ausgelöst. Wenn die Zonen-ID nicht in der konfigurierten ID-Gruppe enthalten ist, wird ZoneRulesException ausgelöst. Das detaillierte Format der Regions-ID hängt von der Gruppe ab, die die Daten liefert. Der Standarddatensatz wird von der IANA-Zeitzonendatenbank (TZDB) bereitgestellt. Diese enthält Regions-IDs der Form '{area}/{city}', wie 'Europe/Paris' oder 'America/New_York'. Dies ist mit den meisten IDs von TimeZone kompatibel. 

so wird UTC+0 in UTC umgewandelt, während Etc/UTC ohne Änderung beibehalten wird

ZoneDateTime.compareTo vergleicht die Zeichenfolge der ID ("Etc/UTC".compareTo("UTC") == 16). Aus diesem Grund sollten Sie OffsetDateTime verwenden.

0