一個(gè)讓我驚掉下巴的時(shí)間格式化問(wèn)題!

先給你搞個(gè)程序看一下:
public class MainTest {
public static void main(String[] args) throws Exception {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = simpleDateFormat.parse("1900-01-01 08:00:00");
System.out.println(simpleDateFormat.format(date));
}
}
你說(shuō)上面的程序邏輯就是一個(gè)簡(jiǎn)單的時(shí)間格式化,你說(shuō)輸出結(jié)果是什么?
只是需要瞟一眼就知道,肯定是輸出這個(gè)結(jié)果呀:
1900-01-01 08:00:00
但是,你把上面的程序拿出來(lái),直接跑起來(lái),你會(huì)發(fā)現(xiàn)輸出結(jié)果竟然是這樣的:
1900-01-01 08:05:43
當(dāng)時(shí)就懵逼了。
我知道時(shí)差 8 小時(shí),是因?yàn)橛袝r(shí)區(qū)問(wèn)題。
我知道時(shí)間差 1 小時(shí),是因?yàn)橛邢牧顣r(shí)的原因。
但是這里差了 5 分 43 秒,有零有整,就讓我有點(diǎn)摸不著頭腦了。

上面這個(gè)案例就是一個(gè)讀者分享給我的,他們?cè)跀?shù)據(jù)庫(kù)里面默認(rèn)時(shí)間是 1900-01-01,再加上時(shí)區(qū)問(wèn)題,剛好變成了 1900-01-01 08:00:00,于是在通過(guò)程序做數(shù)據(jù)遷移的時(shí)候就踩到了這個(gè)莫名其妙的時(shí)間問(wèn)題。

同時(shí)他還給我附送了一個(gè)關(guān)于這個(gè) bug 的鏈接:
https://bugs.openjdk.java.net/browse/JDK-8266262

我乍一看,這個(gè) bug 還挺新的呢,屬于今年提出來(lái)的。
仔細(xì)又看了一眼發(fā)現(xiàn)是和之前的 bug 重復(fù)了:

但是這里提到了原因:
他說(shuō)可以看一下這個(gè)鏈接https://www.timeanddate.com/time/zone/china/shanghai?year=1900
這里面,在 1900 年的時(shí)候,發(fā)生了一個(gè)變化:
The timezone offset was UTC +8:05:43 hours all of the period.
雖然我沒(méi)太看明白具體是什么意思,但是我看到了“5 分 43 秒”:

我理解就是由于時(shí)區(qū)的變化,導(dǎo)致時(shí)間發(fā)生了重置。
接著我順藤摸瓜,在 stackoverflow 上找到了這個(gè):
https://stackoverflow.com/questions/6841333/why-is-subtracting-these-two-times-in-1927-giving-a-strange-result
當(dāng)時(shí)我就震驚了。
這個(gè) 10 年前被提出的問(wèn)題居然已經(jīng)被瀏覽過(guò) 746k 次了,非常熱門(mén)的問(wèn)題了,我居然沒(méi)注意到過(guò):

這個(gè)問(wèn)題具體是這樣的:

你就大概瞟一眼,我給你翻譯翻譯。
提問(wèn)者說(shuō),他發(fā)現(xiàn) 1927-12-31 23:54:07 到 1927-12-31 23:54:08 之間差了 353 秒,按理來(lái)說(shuō)應(yīng)該是 1 秒才對(duì)啊?
但是把時(shí)間改成下面這樣,又正常了:
String str3 = "1927-12-31 23:54:07";
String str4 = "1927-12-31 23:54:08";
我把程序粘出來(lái)你也可以跑一下,看看結(jié)果非常的神奇啊:
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);
}
但是當(dāng)我跑了一遍之后,我發(fā)現(xiàn):我去,說(shuō)好的 353 秒呢?
跑出來(lái)怎么是 1 秒呢,毫無(wú)毛病啊:

我甚至懷疑是 jdk 版本的問(wèn)題,于是我換了 jdk 9、11、15 都跑了一下,都是 1 秒!
這就很奇怪了啊。
感覺(jué)這個(gè)問(wèn)題提的就有問(wèn)題啊。

但是當(dāng)我讀了下面最高贊的答案之后,我才仿佛窺見(jiàn)了一點(diǎn)端倪。
這個(gè)回答比較長(zhǎng),我先全部截圖下來(lái)給你看看:

比較長(zhǎng)的原因是作者修改了幾次回答。
為什么會(huì)修改回答呢?
且往后看吧,一切的答案都藏在這里面。
我選關(guān)鍵的給你說(shuō)。
首先看第一段:

他說(shuō)(1927年) 12 月 31 日的時(shí)候,上海的時(shí)區(qū)發(fā)生了變化。
而關(guān)于 1927 年上海的詳細(xì)情況,他附上了一個(gè)超鏈接,這個(gè)超鏈接就是前面出現(xiàn)的網(wǎng)站,點(diǎn)進(jìn)去之后是這樣的:

但是這個(gè)里面顯示:
No further time changes in 1927 in Shanghai
翻譯過(guò)來(lái)就是:1927 年上海的時(shí)間沒(méi)有進(jìn)一步變化。
這特么就和他下面說(shuō)的那一坨內(nèi)容對(duì)不上了啊?
他下面說(shuō),在 1927 年底的午夜時(shí)分,時(shí)鐘往回走了 5 分 52 秒。因此,"1927-12-31 23:54:08"實(shí)際上發(fā)生了兩次,而 Java 取的是第二次的的時(shí)刻,因此存在差異。
看到這里其實(shí)我都懵逼了,這玩意前后不符啊,于是我又接著開(kāi)始搜索。
直到我發(fā)現(xiàn)了這個(gè):
https://coolshell.cn/articles/5075.html/comment-page-2#comments
這也是十年前的文章。
這里面作者把當(dāng)時(shí)網(wǎng)站截了個(gè)圖:

當(dāng)年的截圖顯示:
在1927年12月31日23:59:59時(shí),往后面的一秒應(yīng)該是1928年1月1日 0:0:0,但是這個(gè)時(shí)間被往后調(diào)整了5分52秒,而成了,1927年12月31日的,23:54:08,于是,完成了352秒的穿越。
這說(shuō)明了什么?
說(shuō)明數(shù)據(jù)發(fā)生了篡改,有人篡改了網(wǎng)頁(yè)上的信息!

到底是怎么回事呢?
我們回到 stackoverflow 接著往下看:

這是他第一次修改回答,因?yàn)?History changes...
歷史發(fā)了變化了...
他這里說(shuō),如果用 TZDB 的 2013a 版本的數(shù)據(jù),原來(lái)的問(wèn)題將不再表現(xiàn)出完全相同的行為。
在 2013a 中,結(jié)果將是 358 秒,過(guò)渡時(shí)間為 23:54:03,而不是 23:54:08。
他提到了一個(gè) TZDB,這是個(gè)啥東西呢?
我也不知道,但是我搜索了一下。

他應(yīng)該說(shuō)的是這個(gè)的東西。
https://www.iana.org/time-zones
看名字你也知道了,它是一個(gè)時(shí)區(qū)數(shù)據(jù)庫(kù),里面應(yīng)該是維護(hù)的時(shí)區(qū)相關(guān)的數(shù)據(jù)。
也就是說(shuō),在這個(gè)時(shí)區(qū)數(shù)據(jù)庫(kù)里面,用 2013a 版本的數(shù)據(jù),前面的代碼就是另外一種輸出了。
也就是說(shuō)數(shù)據(jù)確實(shí)發(fā)生了變化。
而關(guān)鍵的回答在于下一次編輯:

History has changed again...
歷史再次發(fā)生了變化。
在這個(gè)時(shí)區(qū)數(shù)據(jù)庫(kù)里面,2014f 版本中,變化的時(shí)間已經(jīng)移到了1900-12-31,現(xiàn)在只是一個(gè) 343 秒的變化。
343 秒?
不就是我們前面的 5 分 43 秒嗎?

好了,現(xiàn)在時(shí)差能對(duì)上了,343 秒,但是時(shí)間還是沒(méi)對(duì)上啊。
我們的測(cè)試時(shí)間 1900-01-01 08:00:00,他這里寫(xiě)的時(shí)間是 1900-12-31。
差了整整一年呢?
好,我們看他最后一次編輯的內(nèi)容:

我個(gè)人理解他要表達(dá)的意思是這樣的。
Java 為了在時(shí)區(qū)上統(tǒng)一標(biāo)準(zhǔn),所以來(lái)了個(gè)一刀切的政策。
統(tǒng)一的標(biāo)準(zhǔn)就是讓 UTC 時(shí)區(qū)下 1900 年之前的任何瞬間都是標(biāo)準(zhǔn)時(shí)間。
至于產(chǎn)生的時(shí)差嘛...
就在最開(kāi)始的時(shí)候補(bǔ)上去吧。
所以,1900-01-01 00:00:00 加上 8 小時(shí)時(shí)差,是 1900-01-01 08:00:00,在這個(gè)基礎(chǔ)上預(yù)先加上 27 年后來(lái)自 1927-12-31 那個(gè)午夜由于時(shí)間回?fù)軒?lái)的 343 秒。
1900-01-01 08:05:43,我個(gè)人認(rèn)為就是這樣來(lái)的。
而前面 stackoverflow 里面對(duì)應(yīng)的那個(gè)程序,我們現(xiàn)在執(zhí)行是輸出 1,但是在 10 年前,輸出結(jié)果確實(shí)是 353 。
就像我把程序改成這樣:

最終的輸出結(jié)果不是 1,而是 -342。
時(shí)間,發(fā)生了“倒流”。
好了,又是一個(gè)沒(méi)啥卵用的知識(shí)點(diǎn)。
最后,再補(bǔ)充兩個(gè)冷知識(shí)。
第一個(gè)是我在 jdk bug 列表里面追溯了一下,能找到最早提出相關(guān)問(wèn)題的時(shí)間是 2005 年:
https://bugs.openjdk.java.net/browse/JDK-6281408

在這個(gè)里面,官方是這樣回復(fù)的:

這個(gè)問(wèn)題不會(huì)被修復(fù),以避免任何兼容性問(wèn)題。
意思就是:?jiǎn)栴}我知道了,但是這玩意不太好弄,bug 先變成 feature 吧,就先這樣吧。
別問(wèn),問(wèn)就是有歷史原因在里面。
第二個(gè)冷知識(shí)是,前面提到的,時(shí)區(qū)在 1927 年發(fā)生了變化。
你知道為什么嗎?
我在某網(wǎng)站上找到了這樣的描述:
https://zh.wikipedia.org/wiki/%E4%B8%AD%E5%9C%8B%E6%99%82%E5%8D%80

1928 年,就是民國(guó) 17 年。
那一年,中共軍史上最早的紅軍,中國(guó)工農(nóng)紅軍第四軍成立。
那一年,毛主席在井岡山建立農(nóng)村革命根據(jù)地。
那一年,星星之火,已成燎原之勢(shì)。

荒腔走板
這期的荒腔走板,我就只放一張圖:

至于這個(gè)政策最終會(huì)落實(shí)成什么樣呢?
到時(shí)候大家有機(jī)會(huì)來(lái)成都軟件園逛逛吧,看看燈火通明的高樓大廈,那是年輕人在燃燒的樣子。
算了,再放一張圖吧。

最后說(shuō)一句
好了,看到了這里了,轉(zhuǎn)發(fā)、在看、點(diǎn)贊隨便安排一個(gè)吧,要是你都安排上我也不介意。寫(xiě)文章很累的,需要一點(diǎn)正反饋。給各位讀者朋友們磕一個(gè)了!
與優(yōu)秀的人在一起,自己也會(huì)優(yōu)秀起來(lái)
高質(zhì)量技術(shù)交流群,您還沒(méi)加入嗎?
趕緊點(diǎn)擊加入我們,享受一起成長(zhǎng)的快樂(lè)!
往期推薦
點(diǎn)擊閱讀原文,送你免費(fèi)Spring Boot教程!
