it-swarm-vi.com

Tại sao trừ hai lần này (năm 1927) lại cho một kết quả kỳ lạ?

Nếu tôi chạy chương trình sau, phân tích hai chuỗi ngày tham chiếu cách nhau 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 ld4-ld3 không phải là 1 (như tôi mong đợi từ sự khác biệt một giây trong thời gian), nhưng 353?

Nếu tôi thay đổi ngày thành lần 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
6294
Freewind

Đó là 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 tại Thượng Hải. Về cơ bản vào nửa đêm cuối năm 1927, đồng hồ quay 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 dưới dạng sau đó ngày/giờ địa phương - do đó sự khác biệt.

Chỉ là một tập phim khác trong thế giới múi giờ thường kỳ lạ và tuyệt vời.

EDIT: Dừng nhấn! Lịch sử thay đổi ...

Câu hỏi ban đầu sẽ không còn thể hiện hành vi tương tự, 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 tiếp 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 dạng bài 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í không phải là dữ liệu lịch sử là an toàn.

EDIT: Lịch sử đã thay đổi một lần nữa ...

Trong TZDB 2014f, thời gian thay đổi đã chuyển sang 1900-12-31 và giờ chỉ là 343 giây thay đổi (vì vậy thời gian giữa tt+1 là 344 giây, nếu bạn thấy Ý tôi là).

EDIT: Để trả lời một câu hỏi xung quanh quá trình chuyển đổi vào năm 1900 ... có vẻ như cách xử lý Java múi giờ xử lý tất cả múi giờ chỉ đơn giản là ở trong thời gian tiêu chuẩn của họ cho bất kỳ thời điểm nào trước khi bắt đầu 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 Windows của tôi. Vì vậy, bất kỳ múi giờ nào có bất kỳ độ lệch nào ngoài thời gian chuẩn của nó vào đầu năm 1900 sẽ được coi 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 tiêu chuẩn "cố định" (đó là điều getRawOffset giả sử là một khái niệm hợp lệ) nên các thư viện khác không cần phải giới thiệu chuyển tiếp nhân tạo.

10447
Jon Skeet

Bạn đã gặp phải một gián đoạn thời gian địa phương :

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

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

1523
Michael Borgwardt

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

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

Khi tăng thời gian, bạn nên chuyển đổi trở lại 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 bộ qua bất kỳ khoảng thời gian nào mà giờ hoặc phút xảy ra hai lần.

Nếu bạn đã chuyển đổi sang 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ẽ trải qua 11:54:08 chiều LMT - 11:59:59 chiều LMT và sau đó 11:54:08 chiều CST - 11:59:59 chiều CST.

338
PatrickO

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

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

Và xem kết quả là:

1
284
Rajshri

Tôi rất tiếc phải nói, nhưng sự gián đoạn thời gian đã chuyển một chút trong

JDK 6 hai năm trước và trong JDK 7 chỉ gần đây trong cập nhật 25 .

Bài học để tìm hiểu: tránh những lần không phải UTC bằng mọi giá, ngoại trừ có thể để hiển thị.

202
user1050755

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

Sự thay đổi nhỏ này của sự bù đắp, thay vì tiết kiệm ánh sáng ban ngày một giờ (thời gian mùa hè) thông thường mà chúng ta đã quen, 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 sự gián đoạn này vài giây trước đó, nhưng hiệu quả vẫn có thể quan sát được.

Gói Java.time mới trên Java 8 cho phép sử dụng thấy rõ hơn điều này 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 offset này giống nhau:

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

Nhưng hai cái này thì khác:

// +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 đề khi so sánh 1927-12-31 23:59:59 với 1928-01-01 00:00:00, tuy nhiên, trong trường hợp này, đó là phần bù trước đó tạo ra sự phân kỳ dài hơn và đó là ngày sớm hơn có hai lần bù có thể xảy ra.

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

// 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 hơn một lần bù hợp lệ cho ngày/giờ đó - hoặc khoảng cách - trong trường hợp đó ngày/giờ đó không hợp lệ cho id khu vực đó - bằng cách sử dụng isOverlap()isGap() phương thức 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 một 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 sử dụng backport JSR 310.

180
Daniel C. Sobral

IMHO bản địa hóa phổ biến, ẩn trong Java là lỗ hổng thiết kế lớn nhất của nó. Nó có thể dành cho giao diện người dùng, nhưng thật lòng mà nói, 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 mà về cơ bản bạn có thể bỏ qua nội địa hóa vì các lập trình viên không chính xác là đối tượng mục tiêu cho nó. Bạn có thể sử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 (chỉ để hiển thị)

Theo Quy trình cộng đồng Java thành viên tôi khuyên dùng:

  • 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 rõ ràng bản địa hóa.
  • thay vào đó, hãy sử dụng UTF-8/UTC làm mặc định ĐÃ SỬA vì đó đơn giản là mặc định ngày nay. Không có lý do để làm một cái gì đó khác, ngoại trừ nếu bạn muốn sản xuất 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ẫu 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ơ .......

148
user1050755

Đó là một sự thay đổi thời gian vào năm 1927 tại Thượng Hải như những người khác đã đề cập. 

Về cơ bản, đó là 23:54:07 ở Thượng Hải, giờ chuẩn địa phương, sau đó một thời gian, nó chuyển sang ngày hôm sau tại 00:00:00. Nhưng sau đó, nó đã chuyển trở lại 23:54:08 cho thời gian tiêu chuẩn địa phương. Vì vậy, đó là lý do tại sao sự khác biệt là 343 giây chứ không phải 1 giây.

Điều này cũng có thể gây rối với những nơi khác như Mỹ. Ở Mỹ, họ có Giờ tiết kiệm ánh sáng ban ngày. Khi Thời gian tiết kiệm ánh sáng ban ngày bắt đầu, thời gian sẽ tiếp tục thêm 1 giờ. Nhưng sau một thời gian, Giờ tiết kiệm ánh sáng ban ngày kết thúc, nó lùi về 1 giờ so với múi giờ tiêu chuẩn. Vì vậy, đôi khi khi so sánh thời gian ở Mỹ, sự khác biệt là khoảng 3600 giây chứ không phải 1 giây.

Tốt hơn là sử dụng UTC khi thời gian không thay đổi trừ khi cần sử dụng thời gian không phải UTC như trong màn hình.

0
zixuan