java timezone date - Warum ergibt das Abziehen dieser beiden Male (1927) ein seltsames Ergebnis?




4 Answers

Es ist eine Zeitzonenänderung am 31. Dezember in Shanghai.

Siehe diese Seite für Details von 1927 in Shanghai. Im Grunde um Mitternacht Ende 1927 gingen die Uhren um 5 Minuten und 52 Sekunden zurück. "1927-12-31 23:54:08" passierte tatsächlich zweimal und es sieht so aus, als würde Java es als den späteren möglichen Zeitpunkt für das lokale Datum / die lokale Uhrzeit analysieren - daher der Unterschied.

Nur eine weitere Episode in der oft seltsamen und wundervollen Welt der Zeitzonen.

BEARBEITEN: Stop drücken! Geschichte ändert sich ...

Die ursprüngliche Frage würde nicht mehr dasselbe Verhalten zeigen, wenn sie mit Version TZDB von TZDB . In 2013a wäre das Ergebnis 358 Sekunden mit einer Übergangszeit von 23:54:03 anstelle von 23:54:08.

Ich habe das nur bemerkt, weil ich solche Fragen in Noda Time sammle, in Form von TZDB ... Der Test wurde nun geändert, aber es zeigt nur, dass nicht einmal historische Daten sicher sind.

EDIT: Die Geschichte hat sich wieder geändert ...

In TZDB 2014f hat sich der Zeitpunkt der Änderung auf 1900-12-31 verschoben, und jetzt sind es nur noch 343 Sekunden (also beträgt die Zeit zwischen t und t+1 344 Sekunden, wenn Sie sehen, was ich meine).

BEARBEITEN: Um eine Frage zu einem Übergang um 1900 zu beantworten, sieht es so aus, als würde die Java-Zeitzonenimplementierung alle Zeitzonen so behandeln, als würde sie sich zu jedem Zeitpunkt vor dem Beginn von 1900 UTC in ihrer Standardzeit befinden:

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

Der obige Code erzeugt keine Ausgabe auf meinem Windows-Computer. Jede Zeitzone, die zu Beginn des Jahres 1900 einen anderen Offset als den Standardzeitpunkt hat, wird dies als Übergang betrachten. TZDB selbst hat einige Daten, die früher zurückliegen, und verlässt sich nicht auf eine "feste" Standardzeit (was getRawOffset für ein gültiges Konzept hält), sodass andere Bibliotheken diesen künstlichen Übergang nicht einführen müssen.

convert to

Wenn ich das folgende Programm ausführen, werden zwei Datumsfolgen im Abstand von 1 Sekunde analysiert und miteinander verglichen:

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

Die Ausgabe ist:

353

Warum ist ld4-ld3 nicht 1 (wie ich es von dem Zeitunterschied von einer Sekunde erwarten würde), sondern 353 ?

Wenn ich die Datumsangaben in Sekunden später umstelle:

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

Dann wird ld4-ld3 1 .

Java-Version:

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



Die Moral dieser Fremdheit ist:

  • Verwenden Sie möglichst Datums- und Uhrzeitangaben in UTC.
  • Wenn Sie in UTC kein Datum oder keine Uhrzeit anzeigen können, geben Sie immer die Zeitzone an.
  • Wenn Sie in UTC kein Datum und keine Uhrzeit eingeben können, benötigen Sie eine explizit angegebene Zeitzone.



Anstatt jedes Datum zu konvertieren, verwenden Sie den folgenden Code

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

Und sehen Sie das Ergebnis ist:

1



Wie von anderen erklärt, gibt es dort eine Diskontinuität. Es gibt zwei mögliche Zeitzonenversätze für 1927-12-31 23:54:08 in Asia/Shanghai , aber nur einen Versatz für 1927-12-31 23:54:07 . Abhängig davon, welcher Versatz verwendet wird, gibt es entweder eine Differenz von 1 Sekunde oder 5 Minuten und 53 Sekunden.

Diese geringfügige Verschiebung der Offsets anstelle der üblichen einstündigen Sommerzeit (Sommerzeit), die wir gewohnt sind, verdeckt das Problem ein wenig.

Beachten Sie, dass das 2013a-Update der Zeitzonen-Datenbank diese Diskontinuität einige Sekunden früher verschoben hat, der Effekt jedoch noch zu beobachten ist.

Das neue java.time Paket auf Java 8 macht es klarer zu erkennen und stellt Tools für den Umgang damit bereit. Gegeben:

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

Dann ist durationAtEarlierOffset eine Sekunde, durationAtLaterOffset fünf Minuten und 53 Sekunden.

Auch diese beiden Offsets sind gleich:

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

Diese beiden unterscheiden sich jedoch:

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

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

Sie können dasselbe Problem beim Vergleich von 1927-12-31 23:59:59 mit 1928-01-01 00:00:00 . In diesem Fall ist es jedoch der frühere Offset, der die längere Divergenz erzeugt früheres Datum mit zwei möglichen Offsets.

Eine andere Möglichkeit, dies zu erreichen, besteht darin, zu prüfen, ob gerade ein Übergang stattfindet. Wir können das so machen:

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

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

Sie können überprüfen, ob es sich bei dem Übergang um eine Überlappung handelt. In diesem Fall gibt es mehr als einen gültigen Versatz für dieses Datum / diese Uhrzeit oder eine Lücke. In diesem Fall ist das Datum / die Uhrzeit für diese Zonen-ID nicht gültig. Verwenden Sie dazu die isOverlap() und isGap() -Methoden auf zot4 .

Ich hoffe, das hilft den Leuten, mit dieser Art von Problem umzugehen, sobald Java 8 verfügbar ist oder für diejenigen, die Java 7 verwenden, die den JSR 310-Backport verwenden.




Related