等しいと思われる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の振る舞いであり、比較メソッドの実装がString
のZoneId
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
_isがUTC
と同じ場合、2つのオプションが表示されます。
Zone.of(...)
は壊れており、_Etc/UTC
_とUTC
を同じタイムゾーンとして扱う必要があります。そうでなければ、なぜ_UTC+0
_とUTC
がうまく機能するのかわかりません。
UPDATE-2バグID 9090514を報告しました。Oracleチームが何を決定するかがわかります。
UPDATE-3バグレポートが受け入れられました(「修正しない」かどうかで閉じるとはわかりません): https ://bugs.openjdk.Java.net/browse/JDK-8196398
他の回答/コメントで既に述べたように、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));
クラス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
に)してから比較してください。
この質問に対して開かれた JDK-issue は、「問題ではない」としてOracleによって閉じられました。
ZoneDateTime
はOffsetDateTime
に変換し、時間を比較する場合は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
を使用する必要がある理由です。