Warum führt die Subtraktion dieser beiden Epochenmillionen (im Jahr 1927) zu einem seltsamen Ergebnis?

Lesezeit: 8 Minuten

Freewinds Benutzeravatar
Freier Wind

Wenn ich das folgende Programm ausführe, analysiert es zwei Datumszeichenfolgen, die auf Zeiten im Abstand von 1 Sekunde verweisen, und vergleicht sie:

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-ld3nicht 1 (wie ich es aufgrund des Zeitunterschieds von einer Sekunde erwarten würde), aber 353?

Wenn ich die Daten auf Zeiten 1 Sekunde später ändere:

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

Dann ld4-ld3 wird sein 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 eigentliche Antwort ist, immer, immer Sekunden seit einer Epoche für die Protokollierung zu verwenden, wie die Unix-Epoche, mit 64-Bit-Ganzzahldarstellung (mit Vorzeichen, wenn Sie Stempel vor der Epoche zulassen möchten). Jedes reale Zeitsystem weist ein nichtlineares, nicht monotones Verhalten wie Schaltstunden oder Sommerzeit auf.

    – Phil H

    12. Juli 2012 um 8:34

  • Ein tolles Video über solche Dinge: youtube.com/watch?v=-5wpm-gesOY

    – Thorbjørn Ravn Andersen

    14. Okt. 2014 um 10:39

  • Und noch einer vom selben Typ, @ThorbjørnRavnAndersen: youtube.com/watch?v=Uqjg8Kk1HXo (Schaltsekunden). (Dieser stammt von Tom Scotts eigenem YouTube-Kanal, nicht von Computerphile.)

    – TRiG

    3. Juli 2015 um 16:23

  • @Phil H „Sekunden seit der Epoche“ (dh Unix-Zeit) ist ebenfalls nichtlinear, in dem Sinne, dass POSIX-Sekunden keine SI-Sekunden sind und in der Länge variieren

    – Erinnere dich an Monica

    13. September 2020 um 23:57 Uhr

  • POSIX-Sekunden können in der Länge variieren, aber auch das Stepping ist eine zulässige und nicht ungewöhnliche Implementierungsoption. Das heißt, die Differenz zwischen Zeitstempeln im Abstand von einer Sekunde kann Null sein, wenn eine Schaltsekunde hinzugefügt wird, oder negativ, wenn die Zeitstempel weniger als eine Sekunde voneinander entfernt sind. Die Unix-Zeit verhält sich also nicht monoton wie andere reale Zeitsysteme und ist kein Allheilmittel.

    – Erickson

    4. Mai 2022 um 20:22 Uhr

Jon Skeets Benutzeravatar
Jon Skeet

Am 31. Dezember findet in Shanghai eine Zeitzonenumstellung statt.

Sehen diese Seite für Einzelheiten von 1927 in Shanghai. Grundsätzlich wurden die Uhren Ende 1927 um Mitternacht um 5 Minuten und 52 Sekunden zurückgestellt. „1927-12-31 23:54:08“ ist also tatsächlich zweimal passiert, und es sieht so aus, als würde Java es als analysieren später Möglicher Zeitpunkt für dieses lokale Datum/diese lokale Uhrzeit – daher der Unterschied.

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

Bei Neuaufbau mit Version 2013a von TZDB, Die ursprüngliche Frage würde nicht mehr ganz dasselbe Verhalten zeigen. Im Jahr 2013a läge das Ergebnis bei 358 Sekunden, bei einer Übergangszeit von 23:54:03 statt 23:54:08.

Das ist mir nur aufgefallen, weil ich solche Fragen in Noda Time sammle, und zwar in Form von Unit-Tests… Der Test wurde jetzt geändert, aber es zeigt sich: Nicht einmal historische Daten sind sicher.

In TZDB 2014fder Zeitpunkt der Änderung wurde auf den 31.12.1900 verschoben, und die Änderung dauert jetzt nur noch 343 Sekunden (also die Zeit dazwischen). t Und t+1 beträgt 344 Sekunden, wenn Sie verstehen, was ich meine).


Um eine Frage zu einem Übergang um 1900 zu beantworten: Es sieht so aus, als ob die Java-Zeitzonenimplementierung behandelt wird alle Zeitzonen einfach so, dass sie für jeden Zeitpunkt vor Beginn von 1900 UTC in ihrer Standardzeit liegen:

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 auf meinem Windows-Rechner keine Ausgabe. Daher gilt jede Zeitzone, die zu Beginn des Jahres 1900 einen anderen als den Standardversatz aufweist, als Übergang. TZDB selbst verfügt über einige Daten, die früher zurückreichen, und verlässt sich nicht auf die Vorstellung einer „festen“ Standardzeit (was ja auch der Fall ist). getRawOffset davon ausgeht, dass es sich um ein gültiges Konzept handelt), sodass andere Bibliotheken diesen künstlichen Übergang nicht einführen müssen.

Sie sind auf ein gestoßen lokale Zeitdiskontinuität:

Als die lokale Standardzeit Sonntag, 1. Januar 1928, 00:00:00 Uhr erreichen sollte, wurden die Uhren um 0:05:52 Stunden auf Samstag, 31. Dezember 1927, 23:54:08 Uhr lokaler Standardzeit zurückgestellt

Dies ist nicht besonders seltsam und ist fast überall zu der einen oder anderen Zeit passiert, da aufgrund politischer oder administrativer Maßnahmen die Zeitzonen umgestellt oder geändert wurden.

  • Es passiert zweimal im Jahr überall dort, wo Sommerzeit gilt.

    – Uckelman

    27. November 2020 um 22:57 Uhr

  • Das hier ist keine Sommerzeit, denke ich. Es ist nur 10 Minuten zurück und nur einmal. Gleichzeitig kann es zweimal im Jahr oder viermal im Jahr (aufgrund des Ramadan) zu Änderungen im Zusammenhang mit der Sommerzeit kommen. oder in einigen Setups sogar einmal im Jahr. Da gibt es keine Regel 🙂

    – iwat0qs

    27. März 2022 um 10:29

  • Heutzutage kommt es bei der Sommerzeit im Allgemeinen nicht mehr so ​​vor, da die Zeitumstellung um 1 oder 2 Uhr erfolgt, um sicherzustellen, dass es zu keiner Änderung des Datums kommt und sich die Daten nicht wiederholen.

    – Henry Brice

    10. April um 17:18 Uhr

Die Moral dieser Seltsamkeit ist:

  • Verwenden Sie Datums- und Uhrzeitangaben nach Möglichkeit in UTC.
  • Wenn Sie kein Datum oder keine Uhrzeit in UTC anzeigen können, geben Sie immer die Zeitzone an.
  • Wenn Sie kein Eingabedatum/Uhrzeit in UTC benötigen, fordern Sie eine explizit angegebene Zeitzone an.

  • Keiner dieser Punkte würde dieses Ergebnis beeinflussen – es fällt direkt unter den dritten Aufzählungspunkt – und darüber hinaus handelt es sich um einen Zeitpunkt, der mehrere Jahrzehnte vor der Definition von UTC liegt, und kann daher nicht wirklich sinnvoll in UTC ausgedrückt werden.

    – Dag Ågren

    2. November 2020 um 16:11 Uhr

Benutzeravatar von PatrickO
PatrickO

Wenn Sie die Zeit erhöhen, sollten Sie sie zurück in UTC umrechnen und dann addieren oder subtrahieren. Verwenden Sie ausschließlich die Ortszeit zur Anzeige.

Auf diese Weise können Sie alle Zeiträume durchlaufen, in denen Stunden oder Minuten zweimal vorkommen.

Wenn Sie in UTC konvertiert haben, addieren Sie jede Sekunde und konvertieren Sie sie zur Anzeige in die Ortszeit. Sie würden bis 23:54:08 Uhr gehen LMT – 23:59:59 Uhr LMT und dann 23:54:08 Uhr CST – 23:59:59 Uhr CST.

Rajshris Benutzeravatar
Rajshri

Anstatt jedes Datum umzuwandeln, können Sie den folgenden Code verwenden:

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

Und dann sehen Sie, dass das Ergebnis ist:

1

davnicwils Benutzeravatar
davnicwil

Es tut mir leid, das sagen zu müssen, aber die Zeitdiskontinuität ist etwas in die Länge gezogen

JDK 6 vor zwei Jahren und in JDK 7 erst kürzlich in Update 25.

Lektion zum Lernen: Vermeiden Sie unbedingt Nicht-UTC-Zeiten, außer vielleicht zur Anzeige.

Benutzeravatar des neuen QOpenGLWidget
neues QOpenGLWidget

Wie andere erklärt haben, gibt es dort eine Zeitdiskontinuität. Es gibt zwei mögliche Zeitzonen-Offsets für 1927-12-31 23:54:08 bei Asia/Shanghaiaber nur ein Offset für 1927-12-31 23:54:07. Je nachdem, welcher Offset verwendet wird, gibt es also entweder einen Unterschied von einer Sekunde oder einen Unterschied von 5 Minuten und 53 Sekunden.

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

Beachten Sie, dass die Aktualisierung der Zeitzonendatenbank im Jahr 2013 diese Diskontinuität einige Sekunden früher verschoben hat, der Effekt jedoch weiterhin beobachtbar wäre.

Das neue java.time Mit dem Paket auf Java 8 können wir dies klarer erkennen und Tools zur Verfügung stellen, um damit umzugehen. 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 durationAtEarlierOffset wird eine Sekunde dauern, während durationAtLaterOffset beträgt fünf Minuten und 53 Sekunden.

Außerdem sind diese beiden Offsets gleich:

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

Aber diese beiden sind unterschiedlich:

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

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

Beim Vergleich sieht man das gleiche Problem 1927-12-31 23:59:59 mit 1928-01-01 00:00:00allerdings ist es in diesem Fall der frühere Versatz, der die längere Divergenz erzeugt, und es ist das frühere Datum, das zwei mögliche Versätze hat.

Eine andere Möglichkeit, dies zu erreichen, besteht darin, zu prüfen, ob 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, bei der es mehr als einen gültigen Offset für dieses Datum/diese Uhrzeit gibt, oder um eine Lücke, bei der dieses Datum/diese Uhrzeit für diese Zonen-ID nicht gültig ist – indem Sie Folgendes verwenden isOverlap() Und isGap() Methoden auf zot4.

Ich hoffe, dass dies den Leuten hilft, diese Art von Problemen zu bewältigen, sobald Java 8 allgemein verfügbar wird, oder denjenigen, die Java 7 verwenden und den JSR 310-Backport übernehmen.

1451310cookie-checkWarum führt die Subtraktion dieser beiden Epochenmillionen (im Jahr 1927) zu einem seltsamen Ergebnis?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy