webentwicklung-frage-antwort-db.com.de

Warum ergibt das Abziehen dieser beiden Male (1927) ein seltsames Ergebnis?

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 den Sekundenunterschieden der Zeiten erwarten würde), sondern 353?

Wenn ich die Datumsangaben später nach 1 Sekunde ändere:

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

Dann wird ld4-ld31 sein.


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
6294
Freewind

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 später 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.

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

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

Das ist mir nur aufgefallen, weil ich in Noda Time solche Fragen sammle, in Form von Unit-Tests ... 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).

EDIT: Um eine Frage zu einem Übergang um 1900 zu beantworten, sieht es so aus, als würde die Java-Zeitzonenimplementierung alle -Zonen für jeden Moment vor Beginn des Jahres 1900 einfach als Standardzeit gelten KOORDINIERTE WELTZEIT:

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 weist einige Daten auf, die früher zurückliegen, und verlässt sich nicht auf eine Vorstellung von einer "festen" Standardzeit (die getRawOffset als gültiges Konzept annimmt), sodass andere Bibliotheken diesen künstlichen Übergang nicht einführen müssen.

10176
Jon Skeet

Sie haben eine Ortszeitdiskontinuität festgestellt :

Als die örtliche Normalzeit kurz vor Sonntag, dem 1. Januar 1928, erreicht wurde, 00:00:00 uhren wurden 0:05:52 stunden auf samstag, 31 . Dezember 1927, 23:54:08 stattdessen Ortszeit

Das ist nicht sonderbar seltsam und hat sich zu einem oder anderen Zeitpunkt fast überall ereignet, da die Zeitzonen aufgrund von politischen oder administrativen Maßnahmen geändert oder geändert wurden.

1523

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.
611
Raedwald

Wenn Sie die Zeit inkrementieren, sollten Sie wieder in UTC konvertieren und dann hinzufügen oder subtrahieren. Verwenden Sie nur die Ortszeit für die Anzeige.

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

Wenn Sie in UTC konvertiert haben, fügen Sie jede Sekunde hinzu und konvertieren Sie die Uhrzeit in Ortszeit. Sie würden bis 11:54:08 Uhr weitergehen. LMT - 11:59:59 nachmittags LMT und dann 11:54:08 Uhr CST - 11:59:59 nachmittags CST.

338
PatrickO

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
284
Rajshri

Es tut mir leid zu sagen, aber die Diskontinuität hat sich etwas verschoben

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

Lektion zu lernen: Vermeiden Sie unter allen Umständen Nicht-UTC-Zeiten, außer für die Anzeige.

202
user1050755

Wie von anderen erklärt, gibt es dort eine zeitliche Diskontinuität. Es gibt zwei mögliche Zeitzonenversätze für 1927-12-31 23:54:08 bei Asia/Shanghai, aber nur einen Versatz für 1927-12-31 23:54:07. Abhängig davon, welcher Versatz verwendet wird, gibt es entweder einen Unterschied von einer Sekunde oder einen Unterschied von 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 dies klarer zu erkennen und stellt Tools für die Handhabung 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 und 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 sehen. In diesem Fall ist es jedoch der frühere Versatz, der die längere Divergenz erzeugt, und das frühere Datum hat zwei mögliche Versätze.

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. 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 Funktionen isOverlap() und isGap(). Methoden auf zot4.

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

180

IMHO ist die durchdringende implicit -Lokalisierung in Java ihr einziger größter Konstruktionsfehler. Es ist zwar für Benutzeroberflächen gedacht, aber offen gesagt, wer Java heute wirklich für Benutzeroberflächen verwendet, mit Ausnahme einiger IDEs, bei denen Sie die Lokalisierung grundsätzlich ignorieren können, weil Programmierer nicht genau die Zielgruppe dafür sind. Sie können das Problem (insbesondere auf Linux-Servern) beheben, indem Sie:

  • export LC_ALL = C TZ = UTC
  • stellen Sie Ihre Systemuhr auf UTC
  • verwenden Sie niemals lokalisierte Implementierungen, es sei denn, dies ist unbedingt erforderlich (dh nur zur Anzeige).

Zum Java Community Process Mitgliedern empfehle ich:

  • machen lokalisierte Methoden nicht zum Standard, sondern verlangen, dass der Benutzer die Lokalisierung explizit anfordert.
  • verwenden Sie stattdessen UTF-8/UTC als FIXED, da dies heute einfach der Standard ist. Es gibt keinen Grund, etwas anderes zu tun, es sei denn, Sie möchten solche Threads erstellen.

Ich meine, komm schon, sind globale statische Variablen kein Anti-OO-Muster? Nichts anderes sind diese durchdringenden Standardeinstellungen, die durch einige rudimentäre Umgebungsvariablen vorgegeben werden.

148
user1050755

Es ist eine Zeitumstellung im Jahr 1927 in Shanghai, wie von anderen erwähnt. 

Im Grunde ist das 23:54:07 in Shanghai die örtliche Normalzeit, nach kurzer Zeit wurde es auf 00:00:00 umgestellt. Dann wurde jedoch für die lokale Standardzeit wieder auf 23:54:08 gewechselt. Aus diesem Grund beträgt der Unterschied 343 Sekunden und nicht 1 Sekunde.

Dies kann auch andere Orte wie die USA durcheinander bringen. In den USA haben sie Sommerzeit. Wenn die Sommerzeit beginnt, wird die Uhrzeit um 1 Stunde vorgezogen. Nach einer Weile endet die Sommerzeit und es geht 1 Stunde zurück in die Standardzeitzone. Beim Vergleich von Zeiten in den USA beträgt der Unterschied manchmal 3600 Sekunden und nicht 1 Sekunde.

Es ist besser, UTC zu verwenden, wenn sich die Zeit nicht ändert, es sei denn, dies ist wie in der Anzeige erforderlich.

0
zixuan