なぜこれらの2回の減算(1927年)が奇妙な結果をもたらすのですか?


6827

次のプログラムを実行すると、2つの日付文字列を1秒間隔で参照して解析し、それらを比較します。

public static void main(String[] args) throws ParseException {
    SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
    String str3 = "1927-12-31 23:54:07";  
    String str4 = "1927-12-31 23:54:08";  
    Date sDt3 = sf.parse(str3);  
    Date sDt4 = sf.parse(str4);  
    long ld3 = sDt3.getTime() /1000;  
    long ld4 = sDt4.getTime() /1000;
    System.out.println(ld4-ld3);
}

出力は次のとおりです。

353

(時間の1秒の差から予想されるように)なぜそうではld4-ld3ないのですか?1353

日付を1秒後に変更する場合:

String str3 = "1927-12-31 23:54:08";  
String str4 = "1927-12-31 23:54:09";  

その後にld4-ld3なります1


Javaバージョン:

java version "1.6.0_22"
Java(TM) SE Runtime Environment (build 1.6.0_22-b04)
Dynamic Code Evolution Client VM (build 0.2-b02-internal, 19.0-b04-internal, mixed mode)

Timezone(`TimeZone.getDefault()`):

sun.util.calendar.ZoneInfo[id="Asia/Shanghai",
offset=28800000,dstSavings=0,
useDaylight=false,
transitions=19,
lastRule=null]

Locale(Locale.getDefault()): zh_CN

23
これはロケールの問題である可能性があります。
–ThorbjørnRavn Andersen、2011

72
本当の答えは、常に、常にエポックからの秒数を使用することです。これは、Unixエポックのように、64ビット整数表現(エポックの前にスタンプを許可する場合は符号付き)を使用します。現実の時間システムには、うるう時や夏時間などの非線形で単調でない動作があります。
Phil H

22
笑。彼らは2011年にjdk6のためにそれを修正しました。それから2年後、jdk7でも修正されることを発見しました... 7u25の時点で修正されました。もちろん、リリースノートにヒントはありませんでした。時々私はOracleが修正するバグの数を知り、PRの理由でそれについて誰にも教えません。
user1050755

8
これらの種類についてのすばらしいビデオ:youtube.com/watch
v

4
@PhilHいいことは、うるう秒があることです。だからそれでもうまくいきません。
12431234123412341234123 2017年

回答:


10873

上海での12月31日のタイムゾーンの変更です。

上海での1927年の詳細については、このページを参照してください。基本的に1927年の終わりの真夜中に、時計は5分52秒戻りました。したがって、「1927-12-31 23:54:08」は実際には2回発生し、Javaがそのローカル日付/時刻の可能な限り遅い時刻として解析しているように見えます-したがって、違いがあります。

タイムゾーンのしばしば奇妙で素晴らしい世界のちょうど別のエピソード。

編集:プレスを止めて!歴史の変化...

TZDBのバージョン2013aで再構築した場合、元の質問はまったく同じ動作を示しません。2013aでは、結果は358秒で、遷移時間は23:54:08ではなく23:54:03になります。

野田タイムでユニットテストの形でこのような質問を収集しているため、これに気づきました。テストは変更されましたが、表示されるだけです。履歴データでさえ安全ではありません。

編集:歴史は再び変わりました...

TZDB 2014fでは、変更の時間は1900-12-31に移動し、現在は343秒の変更にすぎません(つまり、との間の時間はtt+1意味がわかると344秒です)。

編集: 1900年の移行に関する質問に答えるために... Javaタイムゾーン実装はすべてのタイムゾーンを1900 UTCの開始前の任意の瞬間の標準時間にあるものとして扱うように見えます:

import java.util.TimeZone;

public class Test {
    public static void main(String[] args) throws Exception {
        long startOf1900Utc = -2208988800000L;
        for (String id : TimeZone.getAvailableIDs()) {
            TimeZone zone = TimeZone.getTimeZone(id);
            if (zone.getRawOffset() != zone.getOffset(startOf1900Utc - 1)) {
                System.out.println(id);
            }
        }
    }
}

上記のコードでは、Windowsマシンで出力が生成されません。したがって、1900年の初めに標準以外のオフセットを持つタイムゾーンは、それを遷移としてカウントします。TZDB自体にはそれより前に遡るデータがあり、「固定された」標準時間(これはgetRawOffset有効な概念であると想定されている)の概念に依存していないため、他のライブラリはこの人為的な移行を導入する必要がありません。


25
@ジョン:好奇心から、なぜ彼らはそのような「奇妙な」間隔で時計を戻したのですか?1時間のようなものは理にかなっているように思われましたが、どうして5:52分だったのでしょうか。
ヨハネスルドルフ

63
@ヨハネス:よりグローバルな通常のタイムゾーンにするために、私は信じています- 結果のオフセットはUTC + 8です。パリは1911年に同じ種類のことをしました。たとえば、timeanddate.com
Jon Skeet

34
@ジョン1752年のJava / .NETが9月に対応しているかどうかを知っていますか?私はいつも、UNIXシステムでcal 9 1752を人々に見せることが好き
Mr Moose、

30
そもそもなぜ、そもそも上海が5分足らずだったのでしょうか。
Igby Largeman、2007

25
@Charles:多くの場所では、当時は従来型のオフセットが少なかった。一部の国では、異なる町がそれぞれ独自のオフセットを持ち、地理的に可能な限り正確に近くなっています。
Jon Skeet

1602

現地時間の不連続が発生しました:

1928年1月1日、日曜日に現地標準時が近づいているとき、00:00:00時計は31:土曜日に0:05:52時間逆になりました。代わりに1927年12月23:54:08現地標準時

これは特に奇妙なことではなく、政治的または行政的措置のためにタイムゾーンが切り替えられたり変更されたりしたため、いつでもかなりどこでも起こっていました。


661

この奇妙さの教訓は次のとおりです。

  • 可能な限り、UTCの日付と時刻を使用してください。
  • 日付または時刻をUTCで表示できない場合は、常にタイムゾーンを指定してください。
  • UTCで入力日時を要求できない場合は、明示的に示されたタイムゾーンが必要です。

75
UTCへの変換で不連続性が発生するため、UTCへの変換/保存は、説明されている問題には実際には役立ちません。
unpythonic '30

23
@Mark Mann:プログラムが内部的にどこでもUTCを使用し、UIでのみローカルタイムゾーンとの間で変換を行う場合、そのような不連続性は気になりません。
Raedwald、2011

66
@Raedwald:もちろんです-1927-12-31 23:54:08のUTC時間は何ですか?(現時点では、UTCは1927年にさえ存在しなかったことを無視しています)。で、いくつかの点、この時間と日付は、あなたのシステムに来ている、とあなたはそれをどうするかを決定する必要があります。UTCで時間を入力する必要があることをユーザーに伝えても、問題はユーザーに移るだけで、解消されるわけではありません。
ニックバスティン

72
私はこのスレッドでのアクティビティの量に自信を持っています。大規模アプリの日時リファクタリングにほぼ1年取り組んでいます。カレンダーのようなことをしている場合、UTCを「単純に」格納することはできません。UTCがレンダリングされる可能性のあるタイムゾーンの定義は時間とともに変化するためです。「ユーザーインテントタイム」-ユーザーのローカルタイムとそのタイムゾーン-検索と並べ替え用のUTCを保存し、IANAデータベースが更新されるたびに、すべてのUTC時間を再計算します。
taiganaut

366

時間をインクリメントするときは、UTCに変換してから、加算または減算する必要があります。表示には現地時間のみを使用してください。

この方法で、数時間または数分が2回発生する期間を歩くことができます。

UTCに変換した場合は、毎秒追加し、現地時間に変換して表示します。あなたは11時54分08秒午後通過するでしょうLMT -午後11:59:59 LMT、その後、11時54分08秒午後CST -午後11:59:59 CST。


309

各日付を変換する代わりに、次のコードを使用できます。

long difference = (sDt4.getTime() - sDt3.getTime()) / 1000;
System.out.println(difference);

そして、結果が次のようになることを確認します。

1

72
そうではありません。あなたはあなたのシステムで私のコードを試すことができます、それは出力します1、なぜなら私達は異なるロケールを持っているからです。
Freewind、2012年

14
パーサーの入力でロケールを指定していないため、これは真実です。これは悪いコーディングスタイルであり、Javaの設計上の大きな欠陥です。個人的には、Javaを使用するすべての場所に「TZ = UTC LC_ALL = C」を配置して、それを回避しています。さらに、ユーザーと直接対話して明示的に必要としない限り、ローカライズ版の実装はすべて避けてください。ローカリゼーションを含むいかなる計算もしないでください。どうしても必要な場合を除き、常にLocale.ROOTおよびUTCタイムゾーンを使用してください。
user1050755 14年

226

申し訳ありませんが、時間の不連続が少し進んでいます

2年前のJDK 6、そして最近のアップデート25のJDK 7

学ぶべき教訓:表示のためを除いて、UTC以外の時間は絶対に避けてください。


27
これは誤りです。不連続性はバグではありません-それはTZDBのより新しいバージョンがわずかに異なるデータを持っているということだけです。たとえば、Java 8を搭載した私のマシンで、コードを少し変更して「1927-12-31 23:54:02」と「1927-12-31 23:54:03」を使用する場合でも、不連続性-しかし今では353秒ではなく358秒です。TZDBのさらに最近のバージョンにはさらに別の違いがあります-詳細は私の回答を参照してください。ここには実際のバグはなく、あいまいな日付/時刻のテキスト値がどのように解析されるかに関する設計上の決定があります。
Jon Skeet、2014年

6
本当の問題は、現地時間と世界時(どちらの方向でも)間の変換が100%信頼できるわけではなく、できないことをプログラマーが理解していないことです。古いタイムスタンプの場合、現地時間でのデータはせいぜい不安定です。将来のタイムスタンプについては、政治的行動により、特定の現地時間が対応する世界時が変わる可能性があります。現在および最近の過去のタイムスタンプの場合、tzデータベースを更新して変更をロールアウトするプロセスが、法律の実装スケジュールよりも遅くなる可能性があるという問題が発生する可能性があります。
プラグウォッシュ2017年

200

他の人が説明したように、そこには時間の不連続があります。1927-12-31 23:54:08at Asia/Shanghaiには2つの可能なタイムゾーンオフセットがありますが、のオフセットは1つだけです1927-12-31 23:54:07。したがって、使用されるオフセットに応じて、1秒の差または5分53秒の差があります。

通常の1時間の夏時間(夏時間)の代わりに、このオフセットのわずかなシフトにより、問題が少しわかりにくくなります。

タイムゾーンデータベースの2013aの更新により、この不連続性が数秒早く移動しましたが、その影響はまだ観察可能です。

java.timeJava 8 の新しいパッケージは、これをより明確に見て、それを処理するためのツールを提供します。与えられた:

DateTimeFormatterBuilder dtfb = new DateTimeFormatterBuilder();
dtfb.append(DateTimeFormatter.ISO_LOCAL_DATE);
dtfb.appendLiteral(' ');
dtfb.append(DateTimeFormatter.ISO_LOCAL_TIME);
DateTimeFormatter dtf = dtfb.toFormatter();
ZoneId shanghai = ZoneId.of("Asia/Shanghai");

String str3 = "1927-12-31 23:54:07";  
String str4 = "1927-12-31 23:54:08";  

ZonedDateTime zdt3 = LocalDateTime.parse(str3, dtf).atZone(shanghai);
ZonedDateTime zdt4 = LocalDateTime.parse(str4, dtf).atZone(shanghai);

Duration durationAtEarlierOffset = Duration.between(zdt3.withEarlierOffsetAtOverlap(), zdt4.withEarlierOffsetAtOverlap());

Duration durationAtLaterOffset = Duration.between(zdt3.withLaterOffsetAtOverlap(), zdt4.withLaterOffsetAtOverlap());

その後durationAtEarlierOffsetは1秒、durationAtLaterOffset5分53秒になります。

また、これらの2つのオフセットは同じです。

// Both have offsets +08:05:52
ZoneOffset zo3Earlier = zdt3.withEarlierOffsetAtOverlap().getOffset();
ZoneOffset zo3Later = zdt3.withLaterOffsetAtOverlap().getOffset();

しかし、これら2つは異なります。

// +08:05:52
ZoneOffset zo4Earlier = zdt4.withEarlierOffsetAtOverlap().getOffset();

// +08:00
ZoneOffset zo4Later = zdt4.withLaterOffsetAtOverlap().getOffset();

同じ問題がと比較1927-12-31 23:59:59してわかりますが1928-01-01 00:00:00、この場合、より長い発散を生じるのはより早いオフセットであり、2つの可能なオフセットがあるのはより早い日付です。

これにアプローチする別の方法は、進行中の移行があるかどうかを確認することです。これは次のようにして行うことができます。

// Null
ZoneOffsetTransition zot3 = shanghai.getRules().getTransition(ld3.toLocalDateTime);

// An overlap transition
ZoneOffsetTransition zot4 = shanghai.getRules().getTransition(ld3.toLocalDateTime);

isOverlap()isGap()メソッドを使用することにより、その日付/時刻に有効なオフセットが複数あるオーバーラップ、またはそのゾーンIDにその日付/時刻が有効でないギャップがある遷移が遷移であるかどうかを確認できzot4ます。

これが、Java 8が広く利用可能になったとき、またはJSR 310バックポートを採用するJava 7を使用する人々がこの種の問題を処理するのに役立つことを願っています。


1
こんにちはダニエル、私はあなたのコードを実行しましたが、期待通りの出力をしていません。同様に、durationAtEarlierOffsetとdurationAtLaterOffsetはどちらも1秒のみで、zot3とzot4はどちらもnullです。私は自分のマシンでこのコードをコピーして実行するように設定しました。ここで行う必要があるものはありますか。コードを確認したい場合はお知らせください。これがコードtutorialspoint.com/…です。ここで何が行われているのか教えてください。
vineeshchauhan 2017年

2
@vineeshchauhanこれはtzdataで変更されたため、Javaのバージョンに依存し、異なるバージョンのJDKには異なるバージョンのtzdataがバンドルされています。私自身のインストールされたJavaで、時間がある1900-12-31 23:54:161900-12-31 23:54:17、彼らはI.とは異なるJavaバージョンを使用しているので、それは、あなたが共有サイト上で作業をしません
ダニエルC.ソブラル

167

私見Javaでの広く暗黙的なローカリゼーションは、その単一の最大の設計上の欠陥です。それはユーザーインターフェースを対象としている可能性がありますが、率直に言って、プログラマーは対象としないため、ローカライズを基本的に無視できる一部のIDEを除いて、ユーザーインターフェースにJavaを実際に使用しています。あなたはそれを(特にLinuxサーバーで)修正することができます:

  • エクスポートLC_ALL = C TZ = UTC
  • システムクロックをUTCに設定する
  • 絶対に必要でない限り、ローカライズされた実装を使用しないでください(つまり、表示のみ)

Javaコミュニティプロセスのメンバー私がお勧めします。

  • ローカライズされたメソッドをデフォルトではありませんが、ユーザーがローカライズを明示的に要求する必要があります。
  • 代わりに、UTF-8 / UTCをFIXEDのデフォルトとして使用してください。このようなスレッドを作成したい場合を除いて、他に何かをする理由はありません。

つまり、グローバル静的変数は反OOパターンではないのですか?いくつかの基本的な環境変数によって与えられるこれらの広範なデフォルトは他にありません.......


21

他の人が言ったように、それは1927年の上海での時間変化です。

それがだったときに23:54:07、上海、現地標準時間ではなく、5分52秒後に、それは、次の日になって00:00:00、その後、現地標準時間がに戻します23:54:08。そのため、2つの時間の差は、予想どおり1秒ではなく343秒です。

時間は、米国などの他の場所でも混乱する可能性があります。米国には夏時間があります。夏時間が始まると、時刻は1時間進みます。しかし、しばらくすると夏時間が終了し、標準時間帯に1時間戻ります。したがって、米国の時間を比較すると、違いは36001秒ではなく数秒になることがあります。

しかし、これらの2つの時間の変化には異なる点があります。後者は継続的に変化し、前者は単なる変化でした。同じ量だけ元に戻ることも、再度変わることもありませんでした。

表示のように非UTC時間を使用する必要がない限り、時間が変更されないUTCを使用することをお勧めします。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.