Bunları neden iki kez çıkarmak (1927'de) garip bir sonuç veriyor?


6827

Aşağıdaki programı, 1 saniye arayla iki tarih dizesini ayrıştıran ve karşılaştıran aşağıdaki programı çalıştırırsam:

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

Çıktı:

353

Neden olduğu ld4-ld3değil 1(ı zamanlarda bir saniyelik farktan beklediğiniz gibi), ancak 353?

Tarihleri ​​1 saniye sonraya değiştirirsem:

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

O ld4-ld3zaman olacak 1.


Java sürümü:

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

23
Bu bir yerel ayar sorunu olabilir.
Thorbjørn Ravn Andersen

72
Asıl cevap, her zaman, her zaman, 64 bit tamsayı gösterimi ile (kayıttan önce damgalara izin vermek istiyorsanız imzalı), Unix dönemi gibi, kayıt için bir çağdan beri her zaman saniye kullanmaktır. Herhangi bir gerçek dünya zaman sistemi, sıçrama saatleri veya gün ışığından yararlanma gibi doğrusal olmayan, monotonik olmayan bazı davranışlara sahiptir.
Phil H

22
Lol. 2011'de jdk6 için sabitlediler. Sonra, iki yıl sonra, jdk7'de de sabitlenmesi gerektiğini keşfettiler .... 7u25 itibariyle sabit, tabii ki, sürüm notunda herhangi bir ipucu bulamadım. Bazen Oracle'ın kaç hata düzelttiğini ve PR nedenleriyle kimseye söylemediğini merak ediyorum.
user1050755

8
Bu tür şeyler hakkında harika bir video: youtube.com/watch?v=-5wpm-gesOY
Thorbjørn Ravn Andersen

4
@PhilH Güzel olan şey, yine de artık saniye olacak. Yani bu bile işe yaramıyor.
12431234123412341234123 16:17

Yanıtlar:


10872

31 Aralık'ta Şanghay'da bir saat dilimi değişikliği.

Şangay'daki 1927 detayları için bu sayfaya bakın . Temel olarak 1927'nin sonunda gece yarısı, saatler 5 dakika 52 saniye geri gitti. Yani "1927/12/31 23:54:08" aslında iki kez oldu ve Java olarak yeniden ayrıştırma gibi görünüyor daha sonra yerel tarih / zaman mümkün anlık - dolayısıyla fark.

Sadece garip ve harika zaman dilimleri dünyasında başka bir bölüm.

EDIT: Basın durdurun! Tarih değişiklikleri ...

Orijinal soru, TZDB'nin 2013a sürümü ile yeniden oluşturulduğunda, aynı davranışı göstermeyecektir . 2013a'da sonuç, geçiş süresi 23:54:08 yerine 23:54:03 olacak şekilde 358 saniye olacaktır.

Bunu sadece fark ettim çünkü Noda Time'da, birim testler şeklinde böyle sorular topluyorum ... Test şimdi değişti, ama göstermeye devam ediyor - geçmiş veriler bile güvenli değil.

EDIT: Tarih yeniden değişti ...

TZDB 2014f, değişim zamanı 1900-12-31 taşındı ve (arasındaki zaman yüzden şimdi sadece 343 saniye değişikliğidir tve t+1344 saniyedir, ne demek istediğimi görürseniz).

DÜZENLEME: 1900'de bir geçişle ilgili bir soruyu yanıtlamak için ... Java saat dilimi uygulaması, tüm saat dilimlerini 1900 UTC'nin başlangıcından önce herhangi bir an için standart saatlerinde olduğu gibi ele alıyor gibi görünüyor :

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

Yukarıdaki kod Windows makinemde çıktı üretmiyor. Dolayısıyla, 1900'ün başında standart olandan başka herhangi bir ofseti olan herhangi bir saat dilimi bunu bir geçiş olarak sayar. TZDB'nin kendisinden daha önceki bazı veriler vardır ve "sabit" bir standart zaman ( getRawOffsetgeçerli bir kavram olduğu varsayılır) fikrine dayanmaz, bu nedenle diğer kütüphanelerin bu yapay geçişi tanıtması gerekmez.


25
@Jon: Meraktan, saatlerini neden bu kadar "garip" bir aralıkla geri getirdiler? Bir saat gibi bir şey mantıklı görünüyordu, ama nasıl oldu 5: 52 dakika?
Johannes Rudolph

63
@Johannes: Küresel olarak daha normal bir saat dilimi yapmak için, inanıyorum - sonuçta ortaya çıkan ofset UTC + 8. Paris 1911'de de aynı şeyi yaptı, örneğin: timeanddate.com/worldclock/clockchange.html?n=195&year=1911
Jon Skeet

34
@Jon Java / .NET'in Eylül 1752'de başa çıkıp çıkmadığını biliyor musunuz? Ben her zaman unix sistemlerinde 9 952 cal insanları gösteren seviyorum
Bay Moose

30
Öyleyse neden halt 5 dakika boyunca çuvaldan çıkmıştı?
Igby Largeman

25
@Charles: Birçok yerde o zamanlar daha az geleneksel ofset vardı. Bazı ülkelerde, farklı şehirlerin her birinin coğrafi olarak mümkün olduğunca yakın olması için kendi ofsetleri vardı.
Jon Skeet

1602

Yerel bir zaman süreksizliği ile karşılaştınız :

Yerel standart saat 1: Pazar günü ulaşmak üzereyken, 1 Ocak 1928, 00:00:00 saatler, saat 01:05:52 saat, Cumartesi, 31. Aralık 1927, 23:54:08 yerine yerel standart saat

Bu özellikle garip değildir ve zaman dilimleri politik veya idari eylemler nedeniyle değiştirildiği veya değiştiği için hemen hemen her yerde gerçekleşmiştir.


661

Bu garipliğin ahlakı:

  • Tarihleri ​​ve saatleri UTC'de mümkün olan her yerde kullanın.
  • UTC'de bir tarih veya saat görüntüleyemiyorsanız, her zaman saat dilimini belirtin.
  • UTC'de bir giriş tarihi / saati gerektiremiyorsanız, açıkça belirtilen bir saat dilimi isteyin.

75
UTC'ye dönüştürme / depolama, UTC'ye dönüşümde süreksizlikle karşılaşacağınız için açıklanan soruna gerçekten yardımcı olmaz.
unpythonic

23
@Mark Mann: Programınız dahili olarak her yerde UTC kullanıyorsa, yalnızca kullanıcı arayüzünde yerel bir saat dilimine dönüşüyorsa, bu tür kesintilerle ilgilenmezsiniz .
Raedwald

66
@Raedwald: Tabii ki - 1927-12-31 23:54:08 için UTC saati nedir? (Şimdilik UTC'nin 1927'de bile mevcut olmadığını görmezden gelmek). At bazı noktada bu saat ve tarih sisteminize geliyor ve onunla ne yapacağını karar vermek zorunda. Kullanıcıya UTC'ye zaman girmeleri gerektiğini söylemek sorunu sadece kullanıcıya taşır, ortadan kaldırmaz.
Nick Bastin

72
Neredeyse bir yıldır büyük bir uygulamanın tarih / saat yeniden düzenlenmesi üzerinde çalışarak, bu iş parçacığındaki etkinlik miktarında haklı hissediyorum. Takvim gibi bir şey yapıyorsanız, içinde oluşturulabileceği saat dilimlerinin tanımları zaman içinde değişeceğinden UTC'yi "basitçe" saklayamazsınız. Arama ve sıralama için "kullanıcının niyet zamanını" - kullanıcının yerel saatini ve saat dilimini - ve UTC'yi depolarız ve IANA veritabanı her güncellendiğinde tüm UTC saatlerini yeniden hesaplarız.
taiganaut

366

Süreyi uzatırken UTC'ye dönmeli ve sonra eklemeli ya da çıkarmalısınız. Yerel saati yalnızca görüntüleme için kullanın.

Bu şekilde saatlerin veya dakikaların iki kez meydana geldiği dönemlerde yürüyebileceksiniz.

UTC'ye dönüştürdüyseniz, her saniyeyi ekleyin ve görüntüleme için yerel saate dönüştürün. Sen 23:54:08 üzerinden gideceğini LMT'de - 11:59:59 pm LMT ve ardından 11:54:08 CST - 11:59:59 CST.


309

Her tarihi dönüştürmek yerine aşağıdaki kodu kullanabilirsiniz:

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

Ve sonra sonucun:

1

72
Korkarım durum böyle değil. Kodumu sisteminizde deneyebilirsiniz, çıktı alacak 1, çünkü farklı yerel ayarlarımız var.
Freewind

14
Bu yalnızca doğru çünkü ayrıştırıcı girdisinde yerel ayarı belirtmediniz. Bu kötü kodlama stili ve Java'daki devasa bir tasarım hatası - doğası gereği yerelleştirmesi. Şahsen, bunu önlemek için Java kullandığım her yere "TZ = UTC LC_ALL = C" koydum. Ayrıca, bir kullanıcıyla doğrudan etkileşimde bulunmadığınız ve açıkça istemediğiniz sürece uygulamanın her yerelleştirilmiş sürümünden kaçınmalısınız. Yerelleştirmeler dahil olmak üzere HİÇBİR hesaplama yapmayın, kesinlikle gerekli olmadıkça daima Locale.ROOT ve UTC saat dilimlerini kullanın.
user1050755

226

Söylediğim için üzgünüm, ama zaman süreksizliği biraz

JDK 6 iki yıl önce ve JDK 7'de son 25 güncellemesinde .

Öğrenilmesi gereken dersler: Belki gösterim dışında, UTC dışındaki zamanlardan kaçının.


27
Bu yanlış. Süreksizlik bir hata değil - sadece TZDB'nin daha yeni bir versiyonunun biraz farklı verilere sahip olması. Örneğin, Java 8 yüklü makinemde, kodu "1927-12-31 23:54:02" ve "1927-12-31 23:54:03" kullanmak için çok az değiştirirseniz, süreksizlik - ama şimdi 353 yerine 358 saniye. TZDB'nin daha yeni sürümlerinin başka bir farkı daha var - ayrıntılar için cevabım bakın. Burada gerçek bir hata yok, sadece tarih / saat metin değerlerinin ne kadar ayrıştırıldığına dair bir tasarım kararı.
Jon Skeet

6
Asıl sorun, programcıların yerel ve evrensel zaman (her iki yönde) arasındaki dönüşümün% 100 güvenilir olmadığını ve anlayamayacağıdır. Eski zaman damgaları için yerel saatin ne olduğuna dair elimizdeki veriler en iyi ihtimalle titrektir. Gelecek zaman damgaları için siyasi eylemler, belirli bir yerel zamanın hangi evrensel zamanla eşleşeceğini değiştirebilir. Mevcut ve yakın geçmiş zaman damgaları için, tz veritabanını güncelleme ve değişiklikleri kullanıma sunma sürecinin yasaların uygulama programından daha yavaş olabileceği sorunu yaşayabilirsiniz.
plugwash

200

Başkaları tarafından açıklandığı gibi, orada bir süreksizlik var. Orada iki olası zaman dilimi uzaklıklar içindir 1927-12-31 23:54:08de Asia/Shanghai, ama sadece bir için ofset 1927-12-31 23:54:07. Yani, hangi ofseti kullanıldığına bağlı olarak, ya bir saniye fark ya da 5 dakika ve 53 saniye fark vardır.

Ofsetlerin bu hafif kayması, alışkın olduğumuz olağan bir saatlik yaz saati (yaz saati) yerine, sorunu biraz gizliyor.

Saat dilimi veritabanının 2013a güncellemesinin bu süreksizliği birkaç saniye önce taşıdığını, ancak etkinin hala gözlemlenebilir olacağını unutmayın.

java.timeJava 8'deki yeni paket bunu daha açık bir şekilde görmenizi sağlar ve bunu ele almak için araçlar sağlar. Verilen:

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

O durationAtEarlierOffsetzaman bir saniye, durationAtLaterOffsetbeş dakika 53 saniye olacak.

Ayrıca, bu iki ofset aynıdır:

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

Ancak bu ikisi farklı:

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

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

Bununla 1927-12-31 23:59:59birlikte aynı sorunu görebilirsiniz 1928-01-01 00:00:00, ancak bu durumda, daha uzun sapma üreten erken ofsettir ve iki olası ofsete sahip olan önceki tarihtir.

Buna yaklaşmanın bir başka yolu da bir geçiş olup olmadığını kontrol etmektir. Bunu şu şekilde yapabiliriz:

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

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

Kullanarak - Birden fazla geçerli olduğunu tarih / saat veya tarih / saat o bölge kimliği için geçerli olmayan bir boşluk için ofset olduğu yerde geçiş bir örtüşme olup olmadığını kontrol edebilirsiniz isOverlap()ve isGap()ilgili yöntemler zot4.

Umarım bu, Java 8 yaygın olarak kullanılabilir hale geldiğinde veya JSR 310 backport'u benimseyen Java 7 kullananlara bu tür bir sorunu çözme konusunda yardımcı olur.


1
Merhaba Daniel, kodunuzu çalıştırdım ama beklendiği gibi çıktı vermiyor. durationAtEarlierOffset ve durationAtLaterOffset gibi her ikisi de yalnızca 1 saniyedir ve ayrıca zot3 ve zot4'ün ikisi de boştur. Bu kodu kopyaladım ve makinemde çalıştırdım. burada yapılması gereken bir şey var mı? Bir kod parçası görmek istiyorsanız bana bildirin. İşte tutorialspoint.com/… burada neler olduğunu bana bildirir misiniz.
vineeshchauhan

2
@vineeshchauhan Java'nın sürümüne bağlıdır, çünkü bu tzdata'da değişmiştir ve JDK'nın farklı sürümleri farklı tzdata sürümlerini bir araya getirir. Kendi yüklediğim Java'da, zamanlar 1900-12-31 23:54:16ve 1900-12-31 23:54:17paylaştığınız sitede çalışmıyor, bu yüzden I'den farklı bir Java sürümü kullanıyorlar
Daniel C. Sobral

167

IMHO, Java'daki yaygın, örtük yerelleştirme, en büyük tasarım kusurudur. Kullanıcı arayüzleri için düşünülmüş olabilir, ancak açıkçası, programcıların tam olarak hedef kitlesi olmadığı için yerelleştirmeyi temel olarak göz ardı edebileceğiniz bazı IDE'ler dışında bugün kullanıcı arayüzleri için Java'yı gerçekten kullanan kullanıcılar. Bunu (özellikle Linux sunucularında) şu şekilde düzeltebilirsiniz:

  • dışa aktar LC_ALL = C TZ = UTC
  • sistem saatinizi UTC olarak ayarlayın
  • kesinlikle gerekli olmadıkça asla yerelleştirilmiş uygulamaları kullanmayın (yani yalnızca gösterim için)

To Java Community Process üyeleri Ben tavsiye:

  • yerelleştirilmiş yöntemleri varsayılan yapmak, ancak kullanıcının açıkça yerelleştirme istemesini gerektirir.
  • UTF-8 / UTC'yi FIXED varsayılanı olarak kullanın, çünkü bu sadece bugünün varsayılanı. Bunun gibi iplik üretmek istiyorsanız, başka bir şey yapmak için hiçbir neden yoktur.

Yani, hadi, küresel statik değişkenler bir anti-OO paterni değil mi? Başka hiçbir şey, bazı temel çevre değişkenleri tarafından verilen yaygın temerrütler değildir .......


21

Diğerlerinin söylediği gibi, 1927'de Şanghay'da bir zaman değişikliği.

O iken 23:54:07Shanghai, yerel standart saat, ama 5 dakika ve 52 saniye sonra, o ertesi gün döndü 00:00:00, ardından yerel standart saat geri değişti 23:54:08. Bu yüzden iki kez arasındaki fark, beklediğiniz gibi 1 saniye değil 343 saniyedir.

Zaman, ABD gibi başka yerlerde de berbat olabilir. ABD'de Yaz Saati Uygulaması var. Yaz Saati Uygulaması başladığında saat 1 saat ileri gider. Ancak bir süre sonra Yaz Saati uygulaması sona erer ve standart saat dilimine 1 saat geri gider. Yani bazen ABD'deki zamanları karşılaştırırken fark 36001 saniye değil saniye civarındadır.

Ancak bu iki zaman değişikliği hakkında farklı bir şey var. İkincisi sürekli değişir ve birincisi sadece bir değişiklikti. Aynı miktarda geri dönmedi ya da tekrar değişmedi.

Ekrandaki gibi UTC olmayan bir zaman kullanmanız gerekmedikçe, zamanın değişmediği UTC'yi kullanmak daha iyidir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.