java date時區 timezone - 為什麼減去這兩次(在1927年)給出一個奇怪的結果?




4 Answers

這是12月31日在上海的時區變化。

有關1927年在上海的詳細信息,請參閱此頁 。 基本上在1927年底的午夜,時鐘倒流了5分52秒。 所以“1927-12-31 23:54:08”實際上發生了兩次,看起來Java正在將其解析為當地日期/時間的後續可能時刻 - 因此差異。

只是在經常奇怪和精彩的時區世界的另一集。

編輯:停止按! 歷史變遷......

如果使用TZDB版本2013a重建,原始問題將不再表現出完全相同的行為。 在2013a中,結果為358秒,轉換時間為23:54:03而不是23:54:08。

我只注意到這一點,因為我在Noda Time中以TZDB的形式收集這樣的問題......測試現在已經改變了,但它只是顯示 - 甚至歷史數據都不安全。

編輯:歷史再次發生變化......

在TZDB 2014f中,更改的時間已經變為1900-12-31,現在只有343秒的變化(所以tt+1之間的時間是344秒,如果你明白我的意思)。

編輯:回答關於1900年轉換的問題...看起來Java時區實現將所有時區視為在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);
            }
        }
    }
}

上面的代碼在我的Windows機器上沒有輸出。 因此,任何在1900年開始時具有除標準偏移之外的任何偏移的時區都將其視為過渡。 TZDB本身有一些數據早於此,並且不依賴於“固定”標準時間的任何想法(這是getRawOffset假定為有效概念),因此其他庫不需要引入這種人工轉換。

calendar taiwan

如果我運行以下程序,它解析引用時間間隔為1秒的兩個日期字符串並比較它們:

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

輸出是:

353

為什麼ld4-ld3不是1 (正如我所預期的那樣,時間ld4-ld3 1秒),但353

如果我將日期更改為1秒後的時間:

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

然後ld4-ld3將為1

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



這種陌生感的道德是:

  • 盡可能使用UTC中的日期和時間。
  • 如果您無法以UTC格式顯示日期或時間,請始終指明時區。
  • 如果您不能要求以UTC格式輸入日期/時間,則需要明確指定的時區。



您可以使用以下代碼,而不是轉換每個日期

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

並看到結果是:

1



正如其他人所解釋的那樣,那裡有一段時間不連續。 Asia/Shanghai 1927-12-31 23:54:08有兩種可能的時區偏移,但1927-12-31 23:54:07只有一次偏移。 因此,根據使用的偏移量,存在一秒差異或5分53秒差異。

這種輕微的偏移轉移,而不是我們習慣的通常的一小時夏令時(夏令時),可以稍微模糊一下這個問題。

請注意,時區數據庫的2013a更新在幾秒鐘之前移動了這種不連續性,但效果仍然可以觀察到。

Java 8上的新java.time包讓我們可以更清楚地看到它,並提供處理它的工具。 鑑於:

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

然後durationAtEarlierOffset將是一秒,而durationAtLaterOffset將是五分53秒。

此外,這兩個偏移是相同的:

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

但這兩者是不同的:

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

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

你可以看到比較1927-12-31 23:59:591928-01-01 00:00:00相同的問題,但在這種情況下,它是產生較長分歧的早期偏移,它是較早的日期,有兩個可能的抵消。

另一種方法是檢查是否正在進行轉換。 我們可以這樣做:

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

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

您可以檢查轉換是否是重疊 - 在這種情況下,該日期/時間有多個有效偏移量 - 或間隙 - 在這種情況下,日期/時間對於該區域ID無效 - 通過使用isOverlap()isGap()方法。

我希望這有助於人們在Java 8廣泛使用後處理這類問題,或者使用採用JSR 310反向端口的Java 7的人。




Related