Pertanyaan Mengapa mengurangi dua kali ini (pada tahun 1927) memberikan hasil yang aneh?


Jika saya menjalankan program berikut, yang mem-parsing dua string tanggal referensi kali 1 detik terpisah dan membandingkannya:

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

Outputnya adalah:

353

Kenapa ld4-ld3 tidak 1 (Seperti yang saya harapkan dari perbedaan satu detik di kali), tapi 353?

Jika saya mengubah tanggal ke waktu 1 detik kemudian:

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

Kemudian ld4-ld3 akan 1.


Versi 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

6015
2017-07-27 08:15


asal


Jawaban:


Ini adalah perubahan zona waktu pada 31 Desember di Shanghai.

Lihat halaman ini untuk rincian 1927 di Shanghai. Pada dasarnya pada tengah malam pada akhir tahun 1927, jam kembali 5 menit dan 52 detik. Jadi "1927-12-31 23:54:08" sebenarnya terjadi dua kali, dan sepertinya Java mem-parsingnya sebagai kemudian mungkin instan untuk tanggal / waktu lokal - maka perbedaannya.

Hanya episode lain di dunia waktu yang sering aneh dan indah dari zona waktu.

EDIT: Berita terakhir! Riwayat berubah ...

Pertanyaan asli tidak akan lagi menunjukkan perilaku yang sama, jika dibangun kembali dengan versi 2013a TZDB. Pada 2013a, hasilnya adalah 358 detik, dengan waktu transisi 23:54:03 daripada 23:54:08.

Saya hanya memperhatikan ini karena saya mengumpulkan pertanyaan seperti ini di Noda Time, dalam bentuk tes unit... Tes sekarang telah diubah, tetapi itu hanya menunjukkan - bahkan tidak ada data historis yang aman.

EDIT: Sejarah telah berubah lagi ...

Di TZDB 2014f, waktu perubahan telah berubah menjadi 1900-12-31, dan sekarang hanya perubahan 343 detik (sehingga waktu antara t dan t+1 adalah 344 detik, jika Anda melihat apa yang saya maksudkan).

EDIT: Untuk menjawab pertanyaan seputar transisi pada tahun 1900 ... tampaknya memperlakukan memperlakukan zona waktu Java semua zona waktu hanya berada dalam waktu standar untuk sesaat sebelum awal 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);
            }
        }
    }
}

Kode di atas tidak menghasilkan output pada mesin Windows saya. Jadi setiap zona waktu yang memiliki offset selain yang standar pada awal tahun 1900 akan menghitungnya sebagai transisi. TZDB sendiri memiliki beberapa data lebih awal dari itu, dan tidak bergantung pada ide apa pun tentang waktu standar "tetap" (yang adalah apa getRawOffset diasumsikan sebagai konsep yang valid) sehingga pustaka lain tidak perlu memperkenalkan transisi buatan ini.


9719
2017-07-27 08:31



Anda telah menemui a diskontinuitas waktu lokal:

Ketika waktu standar lokal akan mencapai Minggu, 1. Januari 1928,   00:00:00 jam diputar mundur 0:05:52 jam hingga Sabtu, 31.   Desember 1927, 23:54:08 waktu standar lokal gantinya

Ini tidak terlalu aneh dan telah terjadi di mana-mana pada satu waktu atau lainnya ketika zona waktu dialihkan atau diubah karena tindakan politik atau administratif.


1462
2017-07-27 08:38



Moral dari keanehan ini adalah:

  • Gunakan tanggal dan waktu dalam UTC jika memungkinkan.
  • Jika Anda tidak dapat menampilkan tanggal atau waktu dalam UTC, selalu menunjukkan zona waktu.
  • Jika Anda tidak dapat meminta tanggal / waktu masukan dalam UTC, membutuhkan zona waktu yang ditunjukkan secara eksplisit.

579
2017-07-28 11:50



Ketika menambah waktu Anda harus mengkonversikan kembali ke UTC dan kemudian menambah atau mengurangi. Gunakan waktu lokal hanya untuk tampilan.

Dengan cara ini Anda akan dapat berjalan melalui setiap periode di mana jam atau menit terjadi dua kali.

Jika Anda dikonversi ke UTC, tambahkan setiap detik, dan konversikan ke waktu lokal untuk ditampilkan. Anda akan pergi melalui 11:54:08 siang LMT - 11:59:59 malam LMT dan kemudian 11:54:08 sore CST - 11:59:59 malam CST.


319
2017-07-30 16:55



Daripada mengubah setiap tanggal, Anda menggunakan kode berikut

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

Dan lihat hasilnya adalah:

1

264
2018-05-16 05:31



Saya minta maaf untuk mengatakan itu, tetapi waktu diskontinuitas telah sedikit bergerak

JDK 6 dua tahun lalu, dan di JDK 7 baru-baru ini di perbarui 25.

Pelajaran untuk dipelajari: hindari waktu non-UTC di semua biaya, kecuali, mungkin, untuk ditampilkan.


187
2018-02-17 22:44



Seperti yang dijelaskan oleh orang lain, ada diskontinuitas waktu di sana. Ada dua kemungkinan offset zona waktu untuk 1927-12-31 23:54:08 di Asia/Shanghai, tetapi hanya satu offset untuk 1927-12-31 23:54:07. Jadi, tergantung pada offset yang digunakan, ada perbedaan satu detik atau 5 menit dan 53 detik.

Pergeseran offset yang sedikit ini, alih-alih penghematan siang hari satu jam yang biasa (waktu musim panas) yang biasa kita gunakan, mengaburkan masalah sedikit.

Perhatikan bahwa pembaruan database zona waktu 2013a memindahkan diskontinuitas ini beberapa detik sebelumnya, tetapi efeknya masih dapat diamati.

Yang baru java.time paket di Java 8 biar digunakan melihat ini lebih jelas, dan menyediakan alat untuk menanganinya. Diberikan:

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

Kemudian durationAtEarlierOffset akan menjadi satu detik, sementara durationAtLaterOffset akan lima menit dan 53 detik.

Juga, dua offset ini sama:

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

Tetapi keduanya berbeda:

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

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

Anda dapat melihat masalah yang sama dibandingkan 1927-12-31 23:59:59 dengan 1928-01-01 00:00:00Namun, dalam kasus ini, offset sebelumnya yang menghasilkan divergensi yang lebih panjang, dan itu adalah tanggal yang lebih awal yang memiliki dua kemungkinan offset.

Cara lain untuk mendekati ini adalah untuk memeriksa apakah ada transisi yang terjadi. Kita bisa melakukan ini seperti ini:

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

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

Anda dapat memeriksa apakah transisi tersebut tumpang tindih - dalam hal ini ada lebih dari satu offset yang valid untuk tanggal / waktu - atau celah - dalam hal mana tanggal / waktu tidak valid untuk id zona tersebut - dengan menggunakan isOverlap() dan isGap() metode aktif zot4.

Saya harap ini membantu orang-orang menangani masalah semacam ini begitu Java 8 menjadi tersedia secara luas, atau bagi mereka yang menggunakan Java 7 yang mengadopsi JSR 310 backport.


167
2018-01-03 14:43



IMHO yang meresap, implisit lokalisasi di Jawa adalah cacat desain tunggal terbesarnya. Ini mungkin ditujukan untuk antarmuka pengguna, tetapi terus terang, siapa yang benar-benar menggunakan Java untuk antarmuka pengguna saat ini kecuali untuk beberapa IDE di mana Anda pada dasarnya dapat mengabaikan lokalisasi karena pemrogram tidak tepat sasaran audiens untuk itu. Anda dapat memperbaikinya (terutama di server Linux) dengan:

  • ekspor LC_ALL = C TZ = UTC
  • atur jam sistem Anda ke UTC
  • jangan pernah menggunakan implementasi yang dilokalkan kecuali benar-benar diperlukan (yaitu hanya untuk tampilan)

Ke Proses Komunitas Java anggota yang saya sarankan:

  • membuat metode yang dilokalkan bukan default, tetapi mengharuskan pengguna untuk secara eksplisit meminta pelokalan.
  • gunakan UTF-8 / UTC sebagai TETAP default bukan karena itu hanya default hari ini. Tidak ada alasan untuk melakukan sesuatu yang lain, kecuali jika Anda ingin menghasilkan untaian seperti ini.

Maksud saya, ayolah, bukankah variabel statis global merupakan pola anti-OO? Tidak ada yang lain adalah default merembes yang diberikan oleh beberapa variabel lingkungan dasar .......


137
2017-11-26 15:58