Câu hỏi Tại sao trừ đi hai lần này (năm 1927) đưa ra kết quả lạ?


Nếu tôi chạy chương trình sau, nó phân tích hai chuỗi ngày tham chiếu lần 1 giây và so sánh chúng:

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

Đầu ra là:

353

Tại sao lại là ld4-ld3 không phải 1 (như tôi mong đợi từ sự khác biệt một giây trong lần), nhưng 353?

Nếu tôi thay đổi ngày thành thời gian 1 giây sau đó:

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

Sau đó ld4-ld3 sẽ là 1.


Phiên bản 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

6025
2017-07-27 08:15


gốc


Bạn có thực sự vấp ngã khi tình huống chính xác đó trong một kịch bản thực tế hay là câu hỏi này chỉ có nghĩa là một kẻ bối rối - chỉ vì niềm vui của nó? - Costi Ciudatu
@Costi Ciudatu: FWIW, tôi có thể dễ dàng hình dung điều này sắp tới là kết quả của việc giảm một lỗi lớn hơn - tức là, "Tại sao hai ngày này lại cách nhau một năm không cách nhau một năm?" - Brooks Moses
Câu trả lời thực sự là luôn luôn, luôn luôn sử dụng giây kể từ một kỷ nguyên để ghi nhật ký, giống như kỷ nguyên Unix, với đại diện số nguyên 64 bit (đã ký, nếu bạn muốn cho phép tem trước kỷ nguyên). Bất kỳ hệ thống thời gian thực nào cũng có một số hành vi phi tuyến tính, không đơn điệu như giờ nhảy vọt hoặc tiết kiệm ánh sáng ban ngày. - Phil H
ban đầu được đăng dưới dạng Oracle Bug ID 7070044 vào ngày 23 tháng 7 '11 - Arno
Như @ Ciostatu, tôi thực sự tự hỏi từ đó báo cáo lỗi nào OP đào cái này lên. Tôi cũng khá chắc chắn ngoại trừ việc là một câu đố này không hữu ích cho 99,9 (thêm rất nhiều 9 chữ số) phần trăm người dùng. Anh ấy nhận được huy chương cho danh tiếng "game". - Menelaos Bakopoulos


Các câu trả lời:


Đó là một sự thay đổi múi giờ vào ngày 31 tháng 12 tại Thượng Hải.

Xem trang này để biết chi tiết về năm 1927 ở Thượng Hải. Về cơ bản vào nửa đêm vào cuối năm 1927, các đồng hồ đã quay trở lại 5 phút và 52 giây. Vì vậy, "1927-12-31 23:54:08" thực sự đã xảy ra hai lần và có vẻ như Java đang phân tích cú pháp đó làm một lát sau có thể ngay lập tức cho ngày / giờ địa phương đó - do đó có sự khác biệt.

Chỉ là một tập khác trong thế giới kỳ lạ và kỳ lạ của các múi giờ.

CHỈNH SỬA: Dừng báo chí! Thay đổi lịch sử ...

Câu hỏi ban đầu sẽ không còn thể hiện hành vi tương tự nữa, nếu được xây dựng lại với phiên bản 2013a của TZDB. Trong năm 2013a, kết quả sẽ là 358 giây, với thời gian chuyển đổi là 23:54:03 thay vì 23:54:08.

Tôi chỉ nhận thấy điều này bởi vì tôi đang thu thập các câu hỏi như thế này trong Noda Time, dưới hình thức kiểm tra đơn vị... Bài kiểm tra hiện đã được thay đổi, nhưng nó chỉ hiển thị - thậm chí dữ liệu lịch sử cũng không an toàn.

CHỈNH SỬA: Lịch sử đã thay đổi một lần nữa ...

Trong TZDB 2014f, thời gian của sự thay đổi đã chuyển đến 1900-12-31 và giờ đây nó chỉ là một sự thay đổi 343 giây (vì vậy thời gian giữa t và t+1 là 344 giây, nếu bạn hiểu ý tôi là gì).

CHỈNH SỬA: Để trả lời câu hỏi xung quanh một chuyển đổi vào năm 1900 ... nó trông giống như việc xử lý múi giờ Java xử lý tất cả các các múi giờ chỉ đơn giản là trong thời gian chuẩn của chúng cho bất kỳ khoảnh khắc nào trước khi bắt đầu năm 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);
            }
        }
    }
}

Đoạn mã trên không tạo ra đầu ra trên máy tính Windows của tôi. Vì vậy, bất kỳ múi giờ nào có bất kỳ chênh lệch nào khác với múi giờ chuẩn của nó vào đầu năm 1900 sẽ tính đó là một chuyển đổi. Bản thân TZDB có một số dữ liệu quay lại sớm hơn và không dựa vào bất kỳ ý tưởng nào về thời gian chuẩn "cố định" (đó là những gì getRawOffset giả định là một khái niệm hợp lệ) vì vậy các thư viện khác không cần phải giới thiệu quá trình chuyển đổi nhân tạo này.


9729
2017-07-27 08:31



@ Gareth: Không, nhưng kiểm tra các chi tiết về quá trình chuyển đổi múi giờ của Thượng Hải vào thời điểm đó là cổng gọi đầu tiên của tôi. Và tôi đã làm việc về thời gian chuyển đổi múi giờ cho Noda Thời gian gần đây, do đó, khả năng mơ hồ là khá nhiều ở vị trí hàng đầu trong suy nghĩ của tôi anyway ... - Jon Skeet
@ Johannes: Để biến nó trở thành múi giờ bình thường trên toàn cầu, tôi tin - kết quả bù đắp là UTC + 8. Paris đã làm điều tương tự vào năm 1911, ví dụ: timeanddate.com/worldclock/clockchange.html?n=195&year=1911 - Jon Skeet
@ Charles: trở lại sau đó, du khách biết thời gian địa phương sẽ khác nhau ở khắp mọi nơi (bởi vì nó đã được). Ngoài ra, đồng hồ được cơ khí và trôi dạt nhanh chóng, vì vậy mọi người chúng tôi sử dụng để điều chỉnh chúng theo tháp đồng hồ địa phương mỗi vài ngày anyway, ngay cả khi họ đã không đi du lịch. Vậy các đồng hồ tháp (cũng bị trôi dạt) được thiết lập như thế nào? Dễ dàng nhất bằng cách đặt chúng vào 12:00 khi mặt trời đạt đến đỉnh điểm hàng ngày ... nó khác nhau ở mọi nơi không trên cùng một kinh độ. Đây là tiêu chuẩn khá nhiều ở khắp mọi nơi cho đến khi lịch trình đường sắt yêu cầu một số loại tiêu chuẩn hóa. - Michael Borgwardt
Nhưng sau đó: làm thế nào trên trái đất có loại kiến ​​thức này sống sót qua các thời đại, do đó, gần một thế kỷ trước, nó được thực hiện trong phần mềm? Trong năm 2011, bất kỳ ai đề cập đến những kỳ quặc múi giờ đối với một kỹ sư không phải phần mềm đều được xem như là một mọt sách. (Và thực sự, mọi người mong đợi tất cả phần mềm để trừu tượng nó, và họ không cho một cái chết nếu nó mơ hồ, khi họ nói 'trưa', chúng tôi kỹ sư phần mềm nên đối phó với nó). Nhưng để tưởng tượng một người nào đó ở Thượng Hải, vào tháng 12 năm 1927, nghĩ rằng nó sẽ liên quan đến việc lưu ý một điều như vậy, và bằng cách nào đó thông tin này không bao giờ bị mất, bị xóa, bất cứ điều gì ... - phtrivier
@EdS. Nó thực sự chỉ mất anh ta 15 phút, lý do nó cho thấy một sự khác biệt 16 phút là do một sự khác biệt múi giờ nhẹ vào ngày 27 tháng 7 năm 2011 gây ra bởi cách tuyệt vời Jon Skeet được. - JessieArr


Bạn đã gặp phải một gián đoạn thời gian cục bộ:

Khi giờ chuẩn địa phương sắp đến ngày Chủ nhật, 1. tháng 1 năm 1928,   00:00:00 đồng hồ đã được quay ngược lại 0:05:52 giờ đến thứ Bảy, 31.   Tháng 12 năm 1927, 23:54:08 giờ chuẩn địa phương thay thế

Đây không phải là điều đặc biệt kỳ lạ và đã xảy ra khá nhiều ở khắp mọi nơi cùng một lúc hay lúc khác vì múi giờ đã bị thay đổi hoặc thay đổi do hành động chính trị hoặc hành chính.


1463
2017-07-27 08:38



@ Jason: Đối với việc đọc trước khi đi ngủ, tôi đề nghị cơ sở dữ liệu múi giờ IANA (bây giờ) (trước đây được quản lý bởi một anh chàng đáng yêu tên là Olson, tôi nghĩ) sẽ là một nguồn tài nguyên tuyệt vời: iana.org/time-zones. Theo như tôi biết, phần lớn thế giới nguồn mở (do đó các thư viện được đề cập) sử dụng điều này làm nguồn dữ liệu múi giờ chính của họ. - Sune Rasmussen


Đạo đức của sự kỳ lạ này là:

  • Sử dụng ngày và giờ trong UTC bất cứ khi nào có thể.
  • Nếu bạn không thể hiển thị ngày hoặc giờ trong UTC, hãy luôn cho biết múi giờ.
  • Nếu bạn không thể yêu cầu ngày / giờ đầu vào trong UTC, yêu cầu múi giờ được chỉ định rõ ràng.

580
2017-07-28 11:50



Chuyển đổi / lưu trữ thành UTC thực sự sẽ không giúp cho vấn đề được mô tả như bạn sẽ gặp phải sự gián đoạn trong việc chuyển đổi sang UTC. - unpythonic
@Mark Mann: nếu chương trình của bạn sử dụng UTC trong nội bộ ở mọi nơi, chuyển đổi sang / từ múi giờ địa phương chỉ trong giao diện người dùng, bạn sẽ không quan tâm về những gián đoạn như vậy. - Raedwald
@ Raedwald: Chắc chắn bạn sẽ - Thời gian UTC cho 1927-12-31 23:54:08 là gì? (Bỏ qua, vào lúc này, UTC đó thậm chí không tồn tại vào năm 1927). Tại một số điểm thời gian và ngày này đang đi vào hệ thống của bạn, và bạn phải quyết định phải làm gì với nó. Nói với người dùng rằng họ phải nhập thời gian vào UTC chỉ di chuyển vấn đề cho người dùng, nó không loại bỏ nó. - Nick Bastin
Tôi cảm thấy minh oan về số lượng hoạt động trên chủ đề này, đã được làm việc về ngày / thời gian refactoring của một ứng dụng lớn trong gần một năm nay. Nếu bạn đang làm một cái gì đó như lịch, bạn có thể không "đơn giản" lưu trữ UTC, như các định nghĩa của múi giờ mà nó có thể được trả lại sẽ thay đổi theo thời gian. Chúng tôi lưu trữ "thời gian của người dùng" - thời gian địa phương của người dùng và múi giờ của họ - và UTC để tìm kiếm và sắp xếp và bất cứ khi nào cơ sở dữ liệu IANA được cập nhật, chúng tôi tính toán lại tất cả thời gian UTC. - taiganaut


Khi tăng thời gian, bạn nên chuyển đổi thành UTC và sau đó cộng hoặc trừ. Chỉ sử dụng giờ địa phương để hiển thị.

Bằng cách này, bạn sẽ có thể đi qua bất kỳ giai đoạn nào mà giờ hoặc phút xảy ra hai lần.

Nếu bạn đã chuyển đổi thành UTC, hãy thêm từng giây và chuyển đổi thành giờ địa phương để hiển thị. Bạn sẽ đi qua 11:54:08 tối LMT - 11:59:59 tối LMT và sau đó 11:54:08 chiều CST - 11:59:59 tối CST.


320
2017-07-30 16:55





Thay vì chuyển đổi mỗi ngày, bạn sử dụng mã sau

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

Và xem kết quả là:

1

265
2018-05-16 05:31



Tôi e rằng đó không phải là trường hợp. Bạn có thể thử mã của tôi trong hệ thống của bạn, nó sẽ xuất 1bởi vì chúng tôi có các ngôn ngữ khác nhau. - Freewind
Điều đó chỉ đúng vì bạn chưa chỉ định miền địa phương trong đầu vào trình phân tích cú pháp. Đó là phong cách mã hóa xấu và một lỗ hổng thiết kế rất lớn trong Java - bản địa hóa vốn có của nó. Cá nhân, tôi đặt "TZ = UTC LC_ALL = C" ở khắp mọi nơi tôi sử dụng Java để tránh điều đó. Ngoài ra, bạn nên tránh mọi phiên bản địa phương của việc triển khai trừ khi bạn trực tiếp tương tác với người dùng và muốn rõ ràng nó. Không cho bất kỳ tính toán nào bao gồm bản địa hóa, luôn sử dụng múi giờ Locale.ROOT và UTC trừ khi thật cần thiết. - user1050755


Tôi xin lỗi để nói điều đó, nhưng thời gian gián đoạn đã di chuyển một chút trong

JDK 6 hai năm trước, và trong JDK 7 vừa mới đây cập nhật 25.

Bài học để tìm hiểu: tránh thời gian không phải UTC bằng mọi giá, ngoại trừ, có thể, để trưng bày.


188
2018-02-17 22:44



Điều này là không chính xác. Sự gián đoạn không phải là một lỗi - nó chỉ là một phiên bản mới hơn của TZDB có dữ liệu hơi khác nhau. Ví dụ, trên máy tính của tôi với Java 8, nếu bạn thay đổi mã rất nhẹ để sử dụng "1927-12-31 23:54:02" và "1927-12-31 23:54:03" bạn sẽ vẫn thấy gián đoạn - nhưng trong 358 giây bây giờ, thay vì 353. Ngay cả các phiên bản gần đây của TZDB cũng có một sự khác biệt - hãy xem câu trả lời của tôi để biết chi tiết. Không có lỗi thực sự ở đây, chỉ là một quyết định thiết kế xung quanh cách phân tích các giá trị văn bản ngày / giờ mơ hồ. - Jon Skeet
Vấn đề thực sự là các lập trình viên không hiểu rằng việc chuyển đổi giữa thời gian địa phương và quốc tế (theo một trong hai hướng) là không và không thể tin cậy 100%. Đối với các dấu thời gian cũ, dữ liệu chúng tôi có vào thời gian địa phương bị rung lắc là tốt nhất. Đối với các hành động chính trị timestamps trong tương lai có thể thay đổi thời gian phổ biến mà bản đồ địa phương đã cho. Đối với các dấu thời gian hiện tại và gần đây, bạn có thể có vấn đề rằng quá trình cập nhật cơ sở dữ liệu tz và triển khai các thay đổi có thể chậm hơn so với lịch trình thực hiện của pháp luật. - plugwash


Như được giải thích bởi những người khác, có một thời gian gián đoạn ở đó. Có hai khoảng thời gian có thể có cho 1927-12-31 23:54:08 tại Asia/Shanghai, nhưng chỉ có một bù đắp cho 1927-12-31 23:54:07. Vì vậy, tùy thuộc vào việc bù đắp được sử dụng, có một sự khác biệt một giây hoặc một sự khác biệt 5 phút và 53 giây.

Sự thay đổi nhỏ của bù đắp này, thay vì tiết kiệm ban ngày một giờ thông thường (giờ mùa hè) mà chúng ta thường sử dụng để che khuất vấn đề một chút.

Lưu ý rằng bản cập nhật 2013a của cơ sở dữ liệu múi giờ đã di chuyển gián đoạn này một vài giây trước đó, nhưng hiệu quả vẫn sẽ được quan sát.

Cái mới java.time gói trên Java 8 cho phép sử dụng xem điều này rõ ràng hơn và cung cấp các công cụ để xử lý nó. Được:

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

Sau đó durationAtEarlierOffset sẽ là một giây, trong khi durationAtLaterOffset sẽ là năm phút và 53 giây.

Ngoài ra, hai phần bù này giống nhau:

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

Nhưng hai điều này là khác nhau:

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

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

Bạn có thể thấy cùng một vấn đề so sánh 1927-12-31 23:59:59 với 1928-01-01 00:00:00, mặc dù, trong trường hợp này, đó là bù đắp trước đó tạo ra sự phân kỳ dài hơn, và đó là ngày trước đó có hai khoảng cách có thể xảy ra.

Một cách khác để tiếp cận vấn đề này là kiểm tra xem có sự chuyển tiếp nào đang diễn ra hay không. Chúng ta có thể làm như sau:

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

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

Bạn có thể kiểm tra xem quá trình chuyển đổi có trùng lặp hay không - trong trường hợp đó có nhiều lần bù trừ hợp lệ cho ngày / giờ đó - hoặc khoảng trống - trong trường hợp ngày / giờ không hợp lệ cho id vùng đó - bằng cách sử dụng isOverlap() và isGap() phương pháp trên zot4.

Tôi hy vọng điều này sẽ giúp mọi người xử lý loại vấn đề này khi Java 8 trở nên phổ biến rộng rãi, hoặc cho những người sử dụng Java 7, người áp dụng backport JSR 310.


168
2018-01-03 14:43



Chào Daniel, tôi đã chạy đoạn mã của bạn nhưng nó không cho đầu ra như mong đợi. như durationAtEarlierOffset và durationAtLaterOffset cả hai đều chỉ 1 giây và cả zot3 và zot4 đều là null. Tôi đã thiết lập chỉ cần sao chép và chạy mã này trên máy tính của tôi. có điều gì cần được thực hiện ở đây không. Hãy cho tôi biết nếu bạn muốn xem một đoạn mã. Đây là mã tutorialspoint.com/… bạn có thể cho tôi biết những gì đang xảy ra ở đây không. - vineeshchauhan
@vineeshchauhan Nó phụ thuộc vào phiên bản Java, bởi vì điều này đã thay đổi trong tzdata, và các phiên bản khác nhau của gói JDK các phiên bản khác nhau của tzdata. Trên Java được cài đặt của riêng tôi, thời gian là 1900-12-31 23:54:16 và 1900-12-31 23:54:17, nhưng điều đó không hoạt động trên trang web bạn đã chia sẻ, vì vậy họ đang sử dụng một phiên bản Java khác với I. - Daniel C. Sobral


IMHO phổ biến, ngầm nội địa hóa trong Java là lỗ hổng thiết kế lớn nhất duy nhất của nó. Nó có thể là dành cho giao diện người dùng, nhưng thẳng thắn, người thực sự sử dụng Java cho giao diện người dùng ngày nay ngoại trừ một số IDE nơi bạn có thể bỏ qua nội địa hóa vì người lập trình không chính xác là đối tượng mục tiêu cho nó. Bạn có thể sửa chữa nó (đặc biệt là trên các máy chủ Linux) bằng cách:

  • xuất LC_ALL = C TZ = UTC
  • đặt đồng hồ hệ thống của bạn thành UTC
  • không bao giờ sử dụng các triển khai được bản địa hóa trừ khi thực sự cần thiết (nghĩa là chỉ hiển thị)

Đến Quy trình cộng đồng Java thành viên tôi khuyên bạn nên:

  • làm cho các phương thức được bản địa hóa không phải là mặc định, nhưng yêu cầu người dùng yêu cầu nội địa hóa một cách rõ ràng.
  • sử dụng UTF-8 / UTC làm ĐÃ SỬA mặc định thay vì chỉ đơn giản là mặc định ngày hôm nay. Không có lý do gì để làm điều gì khác, ngoại trừ nếu bạn muốn tạo ra các chủ đề như thế này.

Ý tôi là, thôi nào, không phải các biến tĩnh toàn cầu là một mô hình chống OO sao? Không có gì khác là những mặc định phổ biến được đưa ra bởi một số biến môi trường thô sơ .......


137
2017-11-26 15:58