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

Jul 27 2011

Jika saya menjalankan program berikut, yang mem-parsing dua string tanggal yang mereferensikan 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

Mengapa ld4-ld3tidak 1(seperti yang saya harapkan dari perbedaan waktu satu detik), tetapi 353?

Jika saya mengubah tanggal menjadi kali 1 detik kemudian:

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

Kemudian ld4-ld3akan 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

Jawaban

11177 JonSkeet Jul 27 2011 at 15:31

Ini adalah perubahan zona waktu pada 31 Desember di Shanghai.

Lihat halaman ini untuk rincian tahun 1927 di Shanghai. Pada dasarnya pada tengah malam di penghujung tahun 1927, jam mundur 5 menit dan 52 detik. Jadi "1927/12/31 23:54:08" benar-benar terjadi dua kali, dan sepertinya Jawa parsing itu sebagai kemudian instan mungkin untuk itu lokal tanggal / waktu - maka perbedaan.

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

EDIT: Berhenti tekan! Perubahan sejarah ...

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

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

EDIT: Sejarah telah berubah lagi ...

Di TZDB 2014f, waktu perubahan telah berpindah ke 1900-12-31, dan sekarang hanya perubahan 343 detik (jadi waktu antara tdan t+1344 detik, jika Anda mengerti maksud saya).

EDIT: Untuk menjawab pertanyaan seputar transisi pada 1900 ... sepertinya penerapan zona waktu Java memperlakukan semua zona waktu hanya dalam waktu standarnya untuk sesaat sebelum dimulainya 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 di mesin Windows saya. Jadi setiap zona waktu yang memiliki offset selain standarnya pada awal tahun 1900 akan menghitungnya sebagai transisi. TZDB sendiri memiliki beberapa data yang kembali lebih awal dari itu, dan tidak bergantung pada gagasan tentang waktu standar "tetap" (yang getRawOffsetdianggap sebagai konsep yang valid) sehingga pustaka lain tidak perlu memperkenalkan transisi buatan ini.

1644 MichaelBorgwardt Jul 27 2011 at 15:38

Anda mengalami penghentian waktu lokal :

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

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

688 Raedwald Jul 28 2011 at 18:50

Moral dari keanehan ini adalah:

  • Gunakan tanggal dan waktu dalam UTC jika memungkinkan.
  • Jika Anda tidak dapat menampilkan tanggal atau waktu dalam UTC, selalu tunjukkan zona waktu.
  • Jika Anda tidak dapat meminta tanggal / waktu input dalam UTC, minta zona waktu yang ditunjukkan secara eksplisit.
379 PatrickO Jul 30 2011 at 23:55

Saat menambah waktu Anda harus mengubah kembali ke UTC dan kemudian menambah atau mengurangi. Gunakan waktu setempat hanya untuk tampilan.

Dengan cara ini Anda akan dapat melewati periode mana pun di mana jam atau menit terjadi dua kali.

Jika Anda mengonversi ke UTC, tambahkan setiap detik, dan ubah ke waktu lokal untuk tampilan. Anda akan pergi melalui 11:54:08 LMT - 11:59:59 LMT dan kemudian 11:54:08 CST - 11:59:59 CST.

320 Rajshri May 16 2012 at 12:31

Alih-alih mengonversi setiap tanggal, Anda dapat menggunakan kode berikut:

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

Dan kemudian lihat bahwa hasilnya adalah:

1
236 user1050755 Feb 18 2014 at 05:44

Maaf untuk mengatakannya, tetapi penghentian waktu telah bergerak sedikit

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

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

208 DanielC.Sobral Jan 03 2014 at 21:43

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

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

Perhatikan bahwa pembaruan 2013a database zona waktu memindahkan penghentian ini beberapa detik lebih awal, tetapi efeknya masih dapat diamati.

java.timePaket baru di Java 8 memungkinkan penggunaan untuk 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 durationAtEarlierOffsetakan menjadi satu detik, sedangkan durationAtLaterOffsetakan menjadi lima menit dan 53 detik.

Juga, kedua 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:59dengan 1928-01-01 00:00:00, meskipun, dalam kasus ini, offset yang lebih awal menghasilkan divergensi yang lebih panjang, dan tanggal sebelumnya yang memiliki dua kemungkinan offset.

Cara lain untuk melakukan ini adalah dengan memeriksa apakah ada transisi yang sedang 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 tumpang tindih di mana ada lebih dari satu offset yang valid untuk tanggal / waktu itu atau celah di mana tanggal / waktu itu tidak valid untuk id zona itu - dengan menggunakan metode isOverlap()dan isGap()pada zot4.

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

173 user1050755 Nov 26 2014 at 22:58

IMHOlokalisasi implisit yang menyebar di Jawa adalah satu-satunya kelemahan desain terbesarnya. Ini mungkin ditujukan untuk antarmuka pengguna, tetapi sejujurnya, yang benar-benar menggunakan Java untuk antarmuka pengguna saat ini kecuali untuk beberapa IDE di mana Anda pada dasarnya dapat mengabaikan pelokalan karena programmer bukanlah target audiensnya. Anda dapat memperbaikinya (terutama di server Linux) dengan:

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

Kepada anggota Java Community Process, saya merekomendasikan:

  • buat metode yang dilokalkan, bukan default, tetapi mengharuskan pengguna untuk meminta pelokalan secara eksplisit.
  • gunakan UTF-8/UTCsebagai default TETAP sebagai gantinya karena itu hanyalah default hari ini. Tidak ada alasan untuk melakukan hal lain, kecuali jika Anda ingin menghasilkan utas seperti ini.

Maksud saya, ayolah, bukankah variabel statis global merupakan pola anti-OO? Tidak ada lagi default yang meresap yang diberikan oleh beberapa variabel lingkungan yang belum sempurna .......

25 zixuan Feb 11 2019 at 04:47

Seperti yang dikatakan orang lain, ini adalah perubahan waktu pada tahun 1927 di Shanghai.

Itu 23:54:07di Shanghai, dalam waktu standar lokal, tetapi kemudian setelah 5 menit dan 52 detik, itu beralih ke hari berikutnya pada 00:00:00, dan kemudian waktu standar lokal diubah kembali ke 23:54:08. Jadi, itulah mengapa perbedaan antara kedua waktu tersebut adalah 343 detik, bukan 1 detik, seperti yang Anda perkirakan.

Waktu juga bisa mengacaukan di tempat lain seperti AS. AS memiliki Waktu Musim Panas. Ketika Daylight Saving Time dimulai, waktu maju 1 jam. Namun setelah beberapa saat, Waktu Musim Panas akan berakhir, dan mundur 1 jam kembali ke zona waktu standar. Jadi terkadang saat membandingkan waktu di AS, perbedaannya sekitar 3600detik, bukan 1 detik.

Tetapi ada sesuatu yang berbeda tentang perubahan dua kali ini. Yang terakhir berubah terus menerus dan yang pertama hanyalah perubahan. Itu tidak berubah kembali atau berubah lagi dengan jumlah yang sama.

Lebih baik menggunakan UTC kecuali jika perlu menggunakan waktu non-UTC seperti di layar.

5 NeerajBansal Jul 06 2020 at 21:10

Ini terjadi karena itulah aturan zona waktu untuk tahun 31 Desember 1927 di Shanghai. Jika Anda membuka halaman ini dan memilih "Perubahan zona waktu untuk 1900 - 1924", Anda akan melihat bahwa pada tahun 1900 tanggal dan waktunya adalah "UTC +8: 05: 43 jam semua periode".

Jadi Java hanya menunjukkan waktu yang dikonfigurasi untuk zona waktu itu, pada tahun itu.

jika Anda mengubah zona waktu default ke Hong Kong, hasil yang ditampilkan akan benar.

TimeZone.setDefault(TimeZone.getTimeZone("Asia/Hong_Kong"));

Perhatikan bahwa zona waktu berubah dari CST (Waktu Standar China, "setara 3 huruf" ke Asia / Shanghai) menjadi HKT (nama 3 huruf untuk zona waktu Hong Kong).

Tetapi mengubah zona waktu bukanlah solusi yang baik. Jadi, gunakan waktu sistem UTC jika memungkinkan. Ini akan selalu memberikan waktu lokal setelah konversi.