Dlaczego odjęcie tych dwóch razy (w 1927 roku) daje dziwny wynik?

Jeśli uruchomię następujący program, który parsuje dwa ciągi daty odnoszące się do czasów 1 sekundy od siebie i porównuje je:

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

Wyjście to:

353

Dlaczego ld4-ld3 NIE 1 (Jak oczekiwałbym po jednosekundowej różnicy czasów), ale 353?

Jeśli zmienię daty NA czasy 1 sekundę później:

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

Wtedy ld4-ld3 będzie 1.


Wersja 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
Author: B. Desai, 2011-07-27

8 answers

To zmiana strefy czasowej 31 grudnia w Szanghaju.

Zobacz

Na tej stronie szczegóły dotyczące 1927 roku w Szanghaju. Zasadniczo o północy pod koniec 1927 roku Zegary cofnęły się o 5 minut i 52 sekundy. Tak więc "1927-12-31 23:54:08" zdarzyło się dwa razy i wygląda na to, że Java parsuje to jako później możliwy moment dla tej lokalnej daty / czasu - stąd różnica.

Kolejny epizod w często dziwnym i cudownym świecie czasu strefy.

EDIT: Stop press! Historia się zmienia...

Pierwotne pytanie nie wykazywałoby już takiego samego zachowania, gdyby przebudowano je z wersją 2013a TZDB. W 2013a wynik wynosił 358 sekund, z czasem przejścia 23:54:03 zamiast 23:54:08.

Zauważyłem to tylko dlatego, że zbieram takie pytania w czasie Noda, w formie testów jednostkowych ... Test został zmieniony, ale to tylko pokazuje - nawet nie dane historyczne są bezpieczne.

EDIT: historia znów się zmieniła...

W TZDB 2014f, czas zmiany został przesunięty do 1900-12-31, a teraz jest to zaledwie 343 sekund zmiany (więc czas między t i t+1 jest 344 sekund, jeśli widzisz, co mam na myśli).

EDIT: aby odpowiedzieć na pytanie wokół przejścia o 1900... wygląda na to, że implementacja Java timezone traktuje wszystkie strefy czasowe jako po prostu znajdujące się w ich standardowym czasie przez dowolną chwilę przed początek 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);
            }
        }
    }
}

Powyższy kod nie generuje żadnych danych wyjściowych na moim komputerze z systemem Windows. Więc każda strefa czasowa, która ma inny offset niż standardowy na początku 1900 roku, zaliczy to jako przejście. Samo TZDB ma pewne dane pochodzące wcześniej niż to, i nie opiera się na żadnej idei" stałego " czasu standardowego (który jest tym, co getRawOffset zakłada jako poprawną koncepcję), więc inne biblioteki nie muszą wprowadzać tego sztucznego przejścia.

 9862
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-24 14:51:42

Napotkałeś nieciągłość czasu lokalnego :

Kiedy lokalny czas standardowy zbliżał się do niedzieli, 1. Styczeń 1928, 00: 00: 00 Zegary zostały odwrócone do tyłu 0: 05: 52 godzin do soboty, 31. Grudzień 1927, 23: 54: 08 czasu lokalnego standardowego

Nie jest to szczególnie dziwne i działo się prawie wszędzie w tym czy innym czasie, ponieważ strefy czasowe zostały zamienione lub zmienione z powodu działań politycznych lub administracyjnych.

 1485
Author: Michael Borgwardt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-27 08:38:51

Morał tej dziwności to:

  • Użyj dat i godzin w UTC, o ile to możliwe.
  • Jeśli nie możesz wyświetlić daty lub godziny w UTC, zawsze wskaż strefę czasową.
  • Jeśli nie możesz wymagać wprowadzenia daty / czasu w UTC, wymagaj wyraźnie wskazanej strefy czasowej.
 593
Author: Raedwald,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-28 11:50:08

Podczas zwiększania czasu należy konwertować z powrotem na UTC, a następnie dodać lub odjąć. Czas lokalny służy tylko do wyświetlania.

W ten sposób będziesz mógł przejść przez wszystkie okresy, w których godziny lub minuty zdarzają się dwa razy.

Jeśli przekonwertowałeś NA UTC, dodaj każdą sekundę i przekonwertuj na czas lokalny do wyświetlenia. Przeszedłbyś 23: 54: 08 LMT - 11:59:59 11: 54: 08 - 11:59:59 p. m. CST.

 327
Author: PatrickO,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-27 20:04:04

Zamiast konwertować każdą datę, używasz następującego kodu

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

I zobacz wynik to:

1
 272
Author: Rajshri,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-02 11:47:10

Przykro mi to mówić, ale nieciągłość czasowa przesunęła się trochę w

JDK 6 dwa lata temu, a w JDK 7 niedawno w update 25 .

Lekcja do nauczenia się: unikaj za wszelką cenę czasów innych niż UTC, może poza wyświetlaniem.

 193
Author: user1050755,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-31 05:30:57

Jak wyjaśniają inni, istnieje nieciągłość czasowa. Istnieją dwa możliwe przesunięcia strefy czasowej dla 1927-12-31 23:54:08 w Asia/Shanghai, ale tylko jedno przesunięcie dla 1927-12-31 23:54:07. Tak więc, w zależności od tego, który przesunięcie jest używane, jest albo jedna sekunda różnica lub 5 minut i 53 sekund różnica.

To niewielkie przesunięcie przesunięć, zamiast zwykłej jednogodzinnej oszczędności czasu letniego, do której jesteśmy przyzwyczajeni, trochę zasłania problem.

Należy zauważyć, że aktualizacja 2013a bazy danych strefy czasowej została przeniesiona ta nieciągłość kilka sekund wcześniej, ale efekt będzie nadal obserwowalny.

Nowy pakiet java.time w Javie 8 pozwala lepiej to zobaczyć i zapewnia narzędzia do obsługi.

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

Wtedy durationAtEarlierOffset będzie jedna sekunda, podczas durationAtLaterOffset będzie pięć minut i 53 sekundy.

Również te dwa przesunięcia są takie same:

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

Ale te dwa są różne:

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

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

Można zobaczyć ten sam problem porównując 1927-12-31 23:59:59 z 1928-01-01 00:00:00, jednak w tym przypadek, to wcześniejsze przesunięcie powoduje dłuższą rozbieżność, a wcześniejsza Data ma dwa możliwe przesunięcia.

Innym sposobem podejścia do tego jest sprawdzenie, czy nastąpi przejście. Możemy to zrobić tak:

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

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

Możesz sprawdzić, czy przejście jest nakładaniem się-w tym przypadku istnieje więcej niż jedno ważne przesunięcie dla danej daty / czasu-lub luka-w tym przypadku data / czas nie jest ważny dla identyfikatora strefy - za pomocą metod isOverlap() i isGap() na zot4.

Mam nadzieję, że pomoże to ludziom uporać się z tego typu problemami, gdy Java 8 stanie się powszechnie dostępna, lub tym, którzy używają Java 7, którzy przyjmują backport JSR 310.

 172
Author: Daniel C. Sobral,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-05 18:56:57

IMHO wszechobecna, implicit lokalizacja w Javie jest jej największą wadą projektową. Może być przeznaczony dla interfejsów użytkownika, ale szczerze mówiąc, kto tak naprawdę używa Javy do interfejsów użytkownika dzisiaj z wyjątkiem Niektórych IDE, gdzie można w zasadzie ignorować lokalizację, ponieważ programiści nie są dokładnie grupą docelową dla niego. Możesz to naprawić (szczególnie na serwerach Linuksowych) przez:

  • export LC_ALL=C TZ=UTC
  • ustaw zegar systemowy NA UTC
  • nigdy nie używaj zlokalizowanych implementacje, chyba że jest to absolutnie konieczne (ie for display only)

Do Java Community Process członków polecam:

  • Aby metody zlokalizowane nie były domyślne, ale wymagały od użytkownika jawnego żądania lokalizacji.
  • użyj UTF-8 / UTC jako naprawiono domyślne, ponieważ jest to po prostu domyślne dzisiaj. Nie ma powodu, aby robić coś innego, chyba że chcesz produkować takie wątki.

I mean, come on, are ' T globalne zmienne statyczne wzorzec anty-OO? Nic innego nie jest te wszechobecne wartości domyślne podane przez niektóre podstawowe zmienne środowiskowe.......

 142
Author: user1050755,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-24 14:29:48