web-dev-qa-db-ja.com

ZonedDateTime比較:予想:[Etc / UTC]でした:[UTC]

等しいと思われる2つの日付を比較しましたが、ゾーンの名前が異なります。1つは_Etc/UTC_、もう1つはUTCです。

この質問によると、 TCとEtc/UTCのタイムゾーンに違いはありますか? -この2つのゾーンは同じです。しかし、私のテストは失敗します:

_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(...)メソッドを使用する必要があります。それはいい考えですが、ZoneDateTime内のこの実装のため、zoneDateTimeEtcUtc.compareTo(zoneDateTimeUtc)は_-16_値を返します。

_cmp = getZone().getId().compareTo(other.getZone().getId());
_

アサーション結果:

_Java.lang.AssertionError: 
Expected :0
Actual   :-16
_

したがって、問題はZoneId実装のどこかにあります。しかし、両方のゾーンIDが有効であり、両方が同じゾーンを指定している場合、それらはshouldであるとまだ期待しています。

私の質問は、それはライブラリのバグですか、それとも何か間違っているのですか?

[〜#〜] update [〜#〜]

normalの振る舞いであり、比較メソッドの実装がStringZoneId id表現を使用することをnormalであると納得させようとする人々がいました。この場合、次のテストが正常に実行されるのはなぜですか?

_    @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_isUTCと同じ場合、2つのオプションが表示されます。

  • compareTo/equalsメソッドはZoneId idを使用するべきではありませんが、ルールを比較する必要があります
  • Zone.of(...)は壊れており、_Etc/UTC_とUTCを同じタイムゾーンとして扱う必要があります。

そうでなければ、なぜ_UTC+0_とUTCがうまく機能するのかわかりません。

UPDATE-2バグID 9090514を報告しました。Oracleチームが何を決定するかがわかります。

UPDATE-3バグレポートが受け入れられました(「修正しない」かどうかで閉じるとはわかりません): https ://bugs.openjdk.Java.net/browse/JDK-8196398

13
Andremoniy

他の回答/コメントで既に述べたように、ZonedDateTimeオブジェクトをInstantに変換できます。

ZonedDateTime::isEqual

または、両方のisEqualインスタンスが同じZonedDateTimeに対応するかどうかを比較する Instant method を使用できます。

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

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

クラス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で説明されているため、バグはありません。 ドキュメント のステートメントを強調します。

比較は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」を除く)、オフセット記号は逆向きです通常予想されるものより。

別のZonedDateTime- instancesの比較に関する重要なコメント。ここを見て:

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

同じインスタントおよびローカルタイムスタンプを持つZonedDateTime- instancesの比較は、ゾーンIDの字句比較に基づいていることがわかります。通常、ほとんどのユーザーが期待するものではありません。ただし、この動作は APIで説明 であるため、バグでもありません。これが、タイプZonedDateTimeを使用したくない別の理由です。本質的に複雑すぎます。 Instantとローカル型IMHOの間の中間型変換にのみ使用する必要があります。具体的には:ZonedDateTime- instancesを比較する前に、それらを変換(通常はInstantに)してから比較してください。

2018-02-01からの更新:

この質問に対して開かれた JDK-issue は、「問題ではない」としてOracleによって閉じられました。

4
Meno Hochschild

ZoneDateTimeOffsetDateTimeに変換し、時間を比較する場合はcompareTo(..)と比較する必要があります。

_ZoneDateTime.equals_と_ZoneDateTime.compareTo_は、インスタントとゾーン識別子、つまりタイムゾーンを識別するt番目の文字列が比較されます。

ZoneDateTimeはインスタントとゾーン(オフセットだけでなくIDを含む)であり、OffsetDateTimeはインスタントとゾーンオフセットです。 2つのZoneDateTimeオブジェクト間の時間を比較する場合は、OffsetDateTimeを使用する必要があります。

メソッドZoneId.of(..)は、指定した文字列を解析し、必要に応じて変換します。 ZoneIdは、オフセットではなくタイムゾーンを表します。つまり、GMTタイムゾーンでのタイムシフトです。 ZoneOffsetはオフセットを表します。 https://docs.Oracle.com/javase/8/docs/api/Java/time/ZoneId.html#of-Java.lang.String-

ゾーンIDが「GMT」、「UTC」、または「UT」の場合、結果は同じIDとZoneOffset.UTCと同等のルールを持つZoneIdになります。

ゾーンIDが「UTC +」、「UTC-」、「GMT +」、「GMT-」、「UT +」、または「UT-」で始まる場合、IDはプレフィックスベースのオフセットIDです。 IDは2つに分割され、2文字または3文字の接頭辞と記号で始まる接尾辞が付きます。接尾辞はZoneOffsetとして解析されます。結果は、指定されたUTC/GMT/UTプレフィックスとZoneOffset.getId()に従って正規化されたオフセットIDを持つZoneIdになります。返されるZoneIdのルールは、解析されたZoneOffsetと同等です。

他のすべてのIDは、リージョンベースのゾーンIDとして解析されます。リージョンIDは、正規表現[A-Za-z] [A-Za-z0-9〜/._+-]+と一致する必要があります。一致しない場合、DateTimeExceptionがスローされます。ゾーンIDが構成済みのIDセットにない場合、ZoneRulesExceptionがスローされます。地域IDの詳細な形式は、データを提供するグループによって異なります。デフォルトのデータセットは、IANAタイムゾーンデータベース(TZDB)によって提供されます。これには、「Europe/Paris」や「America/New_York」など、「{area}/{city}」という形式のリージョンIDがあります。これは、TimeZoneのほとんどのIDと互換性があります。

etC/UTCを変更せずに保持しながら、_UTC+0_はUTCに変換されます

_ZoneDateTime.compareTo_は、IDの文字列を比較します("Etc/UTC".compareTo("UTC") == 16)。これが、OffsetDateTimeを使用する必要がある理由です。

2