Frage Warum werden diese beiden Zeiten (1927) subtrahiert, was zu einem seltsamen Ergebnis führt?


Wenn ich das folgende Programm ausführe, das zwei Datumszeichenfolgen referenziert, die 1 Sekunde auseinander 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-ld3 nicht 1 (wie ich von dem Unterschied von einer Sekunde zu der Zeit erwarten würde), aber 353?

Wenn ich die Daten um 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

6015
2017-07-27 08:15


Ursprung


Antworten:


Es ist ein Zeitzonenwechsel am 31. Dezember in Shanghai.

Sehen diese Seite für Details von 1927 in Shanghai. Im Grunde gegen Mitternacht Ende 1927 gingen die Uhren 5 Minuten und 52 Sekunden zurück. So "1927-12-31 23:54:08" ist tatsächlich zweimal passiert, und es sieht so aus, als würde Java es analysieren später möglicher Zeitpunkt für dieses lokale Datum / Zeit - daher der Unterschied.

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

BEARBEITEN: Hör auf zu drücken! Geschichte ändert sich ...

Die ursprüngliche Frage würde nicht mehr das gleiche Verhalten zeigen, wenn sie mit Version 2013a von neu erstellt wird TZDB. In 2013a würde das Ergebnis 358 Sekunden betragen, mit einer Übergangszeit von 23:54:03 statt 23:54:08.

Ich habe das nur bemerkt, weil ich Fragen wie diese in Noda Time in Form von Komponententests... Der Test wurde jetzt geändert, aber es zeigt nur, dass nicht einmal historische Daten sicher sind.

BEARBEITEN: Die Geschichte hat sich wieder verändert ...

In TZDB 2014f wurde die Zeit der Änderung auf 1900-12-31 verschoben, und es ist jetzt nur 343 Sekunden Änderung (also die Zeit zwischen t und t+1 ist 344 Sekunden, wenn Sie sehen, was ich meine).

BEARBEITEN: Um eine Frage um einen Übergang bei 1900 zu beantworten ... sieht es so aus, als würde die Java-Zeitzonenimplementierung behandeln alle Zeitzonen als einfach in ihrer Standardzeit für jeden Augenblick vor dem Beginn von 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);
            }
        }
    }
}

Der obige Code erzeugt auf meinem Windows-Rechner keine Ausgabe. Jede Zeitzone, die zu Beginn des Jahres 1900 einen anderen Offset als den Standard hat, zählt dies als Übergang. TZDB selbst hat einige Daten, die früher zurückliefen, und beruht nicht auf irgendeiner Idee einer "festen" Standardzeit (was das ist getRawOffset nimmt an, ein gültiger Begriff zu sein), so dass andere Bibliotheken diesen künstlichen Übergang nicht einführen müssen.


9719
2017-07-27 08:31



Sie haben eine gefunden lokale Zeitdiskontinuität:

Als die lokale Standardzeit am Sonntag, dem 1. Januar 1928, erreicht war,   00:00:00 Uhren wurden um 0:05:52 Uhr bis Samstag, 31.   Dezember 1927, 23:54:08 stattdessen Ortszeit

Dies ist nicht besonders merkwürdig und ist zu jeder Zeit überall passiert, da Zeitzonen aufgrund von politischen oder administrativen Maßnahmen gewechselt oder geändert wurden.


1462
2017-07-27 08:38



Die Moral dieser Fremdheit ist:

  • Verwenden Sie wann immer möglich Daten in UTC.
  • Wenn Sie ein Datum oder eine Uhrzeit nicht in UTC anzeigen können, geben Sie immer die Zeitzone an.
  • Wenn Sie kein Eingabe-Datum / Uhrzeit in UTC benötigen, benötigen Sie eine explizit angegebene Zeitzone.

579
2017-07-28 11:50



Wenn Sie die Zeit erhöhen, sollten Sie zurück in UTC konvertieren und dann addieren oder subtrahieren. Verwenden Sie die lokale Zeit nur 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 sie in die lokale Zeit für die Anzeige. Du würdest um 11:54:08 Uhr durchgehen. LMT - 23:59:59 LMT und dann 11:54:08 Uhr CST - 23:59:59 CST.


319
2017-07-30 16:55



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

264
2018-05-16 05:31



Es tut mir leid, das zu sagen, aber die Zeitverschiebung hat sich ein wenig verschoben

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

Lektion zu lernen: vermeiden Sie nicht-UTC Zeiten um jeden Preis, außer vielleicht für die Anzeige.


187
2018-02-17 22:44



Wie von anderen erklärt, gibt es dort eine zeitliche Diskontinuität. Es gibt zwei mögliche Zeitzonen-Offsets für 1927-12-31 23:54:08 beim Asia/Shanghai, aber nur ein Offset für 1927-12-31 23:54:07. Je nachdem, welcher Offset verwendet wird, gibt es entweder eine Differenz von einer Sekunde oder eine Differenz von 5 Minuten und 53 Sekunden.

Diese leichte Verschiebung von Offsets, anstatt der gewohnten einstündigen Sommerzeit (Sommerzeit), verschleiert das Problem ein wenig.

Beachten Sie, dass das 2013a-Update der Zeitzonendatenbank diese Diskontinuität einige Sekunden früher verschoben hat, der Effekt jedoch immer noch beobachtbar wäre.

Das neue java.time package on Java 8 lassen Sie es klarer sehen und stellen Sie Werkzeuge zur Verfügung. 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 sein, während durationAtLaterOffset wird fünf Minuten und 53 Sekunden sein.

Diese beiden Offsets sind ebenfalls identisch:

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

Aber diese zwei sind anders:

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

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

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

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 der Übergang eine Überlappung ist - in diesem Fall gibt es mehr als einen gültigen Versatz für dieses Datum / Zeit - oder eine Lücke - in diesem Fall ist dieses Datum / diese Zeit für diese Zonen-ID nicht gültig - mithilfe von isOverlap() und isGap() Methoden auf zot4.

Ich hoffe, dies hilft Leuten, mit solchen Problemen umzugehen, sobald Java 8 allgemein verfügbar wird, oder denen, die Java 7 verwenden, die den JSR 310-Backport übernehmen.


167
2018-01-03 14:43



IMHO der allgegenwärtige, implizit Die Lokalisierung in Java ist der größte Konstruktionsfehler. Es kann für Benutzerschnittstellen gedacht sein, aber ehrlich gesagt, wer Java wirklich für Benutzeroberflächen heute benutzt, außer für einige IDEs, wo Sie Lokalisierung grundsätzlich ignorieren können, weil Programmierer nicht genau die Zielgruppe dafür sind. Sie können es reparieren (besonders auf Linux-Servern) durch:

  • export LC_ALL = C TZ = UTC
  • Stellen Sie Ihre Systemuhr auf UTC ein
  • Verwenden Sie niemals lokalisierte Implementierungen, es sei denn, dies ist absolut notwendig (dh nur für die Anzeige)

Zum Java-Community-Prozess Mitglieder empfehle ich:

  • lokalisierte Methoden nicht als Standard festlegen, sondern vom Benutzer explizit eine Lokalisierung anfordern.
  • Verwenden Sie UTF-8 / UTC als FEST Standard, weil das heute einfach der Standard ist. Es gibt keinen Grund, etwas anderes zu tun, außer wenn Sie solche Threads erzeugen möchten.

Ich meine, komm schon, sind keine globalen statischen Variablen ein Anti-OO-Muster? Nichts anderes sind diese allgegenwärtigen Defaults, die von einigen rudimentären Umgebungsvariablen gegeben werden .......


137
2017-11-26 15:58