系統(tǒng)時(shí)區(qū)與 JAVA 運(yùn)行環(huán)境

在JAVA項(xiàng)目中遇到了一個(gè) Bug 因時(shí)區(qū)產(chǎn)生。現(xiàn)記錄解決過程。

1、時(shí)區(qū)

全球分為24個(gè)時(shí)區(qū),相鄰時(shí)區(qū)時(shí)間相差1個(gè)小時(shí)。

2、JAVA 獲取系統(tǒng)當(dāng)前時(shí)間方法

Date date = new Date();

System.currentTimeMillis();

3、結(jié)合時(shí)區(qū)與 Java 獲取系統(tǒng)時(shí)間

比如北京處于東八時(shí)區(qū),東京處于東九時(shí)區(qū),北京時(shí)間比東京時(shí)間晚1個(gè)小時(shí),而英國倫敦時(shí)間比北京晚7個(gè)小時(shí)(英國采用夏令時(shí)時(shí),8月英國處于夏令時(shí))。比如此刻北京時(shí)間是2017年8月24日11:17:10,則東京時(shí)間是2017年8月24日12:17:10,倫敦時(shí)間是2017年8月24日4:17:10。

既然Date里存放的是當(dāng)前時(shí)刻距1970年1月1日0點(diǎn)時(shí)刻的毫秒數(shù),如果此刻在倫敦、北京、東京有三個(gè)程序員同時(shí)執(zhí)行如下語句:

Date date = new Date();

System.currentTimeMillis();

那這三個(gè)date對象里存的毫秒數(shù)是相同的嗎?還是北京的比東京的小3600000(北京時(shí)間比東京時(shí)間晚1小時(shí),1小時(shí)為3600秒即3600000毫秒)?答案是,這3個(gè)Date里的毫秒數(shù)是完全一樣的。確切的說,Date對象里存的是自格林威治時(shí)間( GMT)1970年1月1日0點(diǎn)至Date對象所表示時(shí)刻所經(jīng)過的毫秒數(shù)。所以,如果某一時(shí)刻遍布于世界各地的程序員同時(shí)執(zhí)行new Date語句,這些Date對象所存的毫秒數(shù)是完全一樣的。也就是說,Date里存放的毫秒數(shù)是與時(shí)區(qū)無關(guān)的。

System.out.println(date);

那么北京的程序員將會打印出2017年8月24日11:17:10,而東京的程序員會打印出2017年8月24日12:17:10,倫敦的程序員會打印出2017年8月24日4:17:10。既然Date對象只存了一個(gè)毫秒數(shù),為什么這3個(gè)毫秒數(shù)完全相同的Date對象,可以打印出不同的時(shí)間呢?這是因?yàn)镾ysytem.out.println函數(shù)在打印時(shí)間時(shí),會取操作系統(tǒng)當(dāng)前所設(shè)置的時(shí)區(qū),然后根據(jù)這個(gè)時(shí)區(qū)將同毫秒數(shù)解釋成該時(shí)區(qū)的時(shí)間。當(dāng)然我們也可以手動設(shè)置時(shí)區(qū),以將同一個(gè)Date對象按不同的時(shí)區(qū)輸出。可以做如下實(shí)驗(yàn)驗(yàn)證:

Date date = new Date(1503544630000L);? // 對應(yīng)的北京時(shí)間是2017-08-24 11:17:10

SimpleDateFormat bjSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");? ? // 北京

bjSdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));? // 設(shè)置北京時(shí)區(qū)

SimpleDateFormat tokyoSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");? // 東京

tokyoSdf.setTimeZone(TimeZone.getTimeZone("Asia/Tokyo"));? // 設(shè)置東京時(shí)區(qū)

SimpleDateFormat londonSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // 倫敦

londonSdf.setTimeZone(TimeZone.getTimeZone("Europe/London"));? // 設(shè)置倫敦時(shí)區(qū)

System.out.println("毫秒數(shù):" + date.getTime() + ", 北京時(shí)間:" + bjSdf.format(date));

System.out.println("毫秒數(shù):" + date.getTime() + ", 東京時(shí)間:" + tokyoSdf.format(date));

輸出為:

毫秒數(shù):1503544630000, 北京時(shí)間:2017-08-24 11:17:10

毫秒數(shù):1503544630000, 東京時(shí)間:2017-08-24 12:17:10

毫秒數(shù):1503544630000, 倫敦時(shí)間:2017-08-24 04:17:10

可以看出,同一個(gè)Date對象,按不同的時(shí)區(qū)來格式化,將得到不同時(shí)區(qū)的時(shí)間。由此可見,Date對象里保存的毫秒數(shù)和具體輸出的時(shí)間(即年月日時(shí)分秒)是模型和視圖的關(guān)系,而時(shí)區(qū)(即Timezone)則決定了將同一個(gè)模型展示成什么樣的視圖。

4.從字符串中讀取時(shí)間

有時(shí)我們會遇到從一個(gè)字符串中讀取時(shí)間的要求,即從字符串中解析時(shí)間并得到一個(gè)Date對象,比如將"2017-8-24 11:17:10"解析為一個(gè)Date對象?,F(xiàn)在問題來了,這個(gè)時(shí)間到底指的是北京時(shí)間的2017年8月24日11:17:10,還是東京時(shí)間的2017年8月24日11:17:10?如果指的是北京時(shí)間,那么這個(gè)時(shí)間對應(yīng)的東京時(shí)間2017年8月24日12:17:10;如果指的是東京時(shí)間,那么這個(gè)時(shí)間對應(yīng)的北京時(shí)間就是2017年8月24日10:17:10。因此,只說年月日時(shí)分秒而不說是哪個(gè)時(shí)區(qū)的,是有歧義的,沒有歧義的做法是,給出一個(gè)時(shí)間字符串,同時(shí)指明這是哪個(gè)時(shí)區(qū)的時(shí)間。

從字符串中解析時(shí)間的正確作法是:指定時(shí)區(qū)來解析。示例如下:

String timeStr = "2017-8-24 11:17:10"; // 字面時(shí)間

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai")); // 設(shè)置北京時(shí)區(qū)

Date d = sdf.parse(timeStr);

System.out.println(sdf.format(d) + ", " + d.getTime());

輸出為:

2017-08-24 11:17:10, 1503544630000,

將一個(gè)時(shí)間字符串按不同時(shí)區(qū)來解釋,得到的Date對象的值是不同的。驗(yàn)證如下:

String timeStr = "2017-8-24 11:17:10"; // 字面時(shí)間

SimpleDateFormat bjSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

bjSdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));

Date bjDate = bjSdf.parse(timeStr);? // 解析

System.out.println("字面時(shí)間: " + timeStr +",按北京時(shí)間來解釋:" + bjSdf.format(bjDate) + ", " + bjDate.getTime());

SimpleDateFormat tokyoSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");? // 東京

tokyoSdf.setTimeZone(TimeZone.getTimeZone("Asia/Tokyo"));? // 設(shè)置東京時(shí)區(qū)

Date tokyoDate = tokyoSdf.parse(timeStr); // 解析

System.out.println("字面時(shí)間: " + timeStr +",按東京時(shí)間來解釋:"? + tokyoSdf.format(tokyoDate) + ", " + tokyoDate.getTime());

輸出為:

字面時(shí)間: 2017-8-24 11:17:10,按北京時(shí)間來解釋:2017-08-24 11:17:10, 1503544630000

字面時(shí)間: 2017-8-24 11:17:10,按東京時(shí)間來解釋:2017-08-24 11:17:10, 1503541030000

可以看出,對于"2017-8-24 11:17:10"這個(gè)字符串,按北京時(shí)間來解釋得到Date對象的毫秒數(shù)是

1503544630000;而按東京時(shí)間來解釋得到的毫秒數(shù)是1503541030000,前者正好比后者大于3600000毫秒即1個(gè)小時(shí),正好是北京時(shí)間和東京時(shí)間的時(shí)差。這很好理解,北京時(shí)間2017-08-24 11:17:10對應(yīng)的毫秒數(shù)是1503544630000,而東京時(shí)間2017-08-24 11:17:10對應(yīng)的北京時(shí)間其實(shí)是2017-08-24 10:17:10(因?yàn)楸本r(shí)間比東京時(shí)間晚1個(gè)小時(shí)),北京時(shí)間2017-08-24 10:17:10自然比北京時(shí)間2017-08-24 11:17:10少3600000毫秒。

5.將字符串表示的時(shí)間轉(zhuǎn)換成另一個(gè)時(shí)區(qū)的時(shí)間字符串

綜合以上分析,如果給定一個(gè)時(shí)間字符串,并告訴你這是某個(gè)時(shí)區(qū)的時(shí)間,要將它轉(zhuǎn)換為另一個(gè)時(shí)區(qū)的時(shí)間并輸出,正確的做法是:

1.將字符串按原時(shí)區(qū)轉(zhuǎn)換成Date對象;

2.將Date對象格式化成目標(biāo)時(shí)區(qū)的時(shí)間。

比如,將北京時(shí)間"2017-8-24 11:17:10"輸出成東京時(shí)間,代碼為:

String timeStr = "2017-8-24 11:17:10"; // 字面時(shí)間

SimpleDateFormat bjSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

bjSdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));

Date date = bjSdf.parse(timeStr);? // 將字符串時(shí)間按北京時(shí)間解析成Date對象

SimpleDateFormat tokyoSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");? // 東京

tokyoSdf.setTimeZone(TimeZone.getTimeZone("Asia/Tokyo"));? // 設(shè)置東京時(shí)區(qū)

System.out.println("北京時(shí)間: " + timeStr +"對應(yīng)的東京時(shí)間為:"? + tokyoSdf.format(date));

輸出為:

北京時(shí)間:2017-8-24 11:17:10對應(yīng)的東京時(shí)間為:2017-08-24 12:17:10

5、系統(tǒng)時(shí)區(qū)的修改

查看系統(tǒng)當(dāng)前時(shí)區(qū)

# date -R

#tzselect

Please identify a location so that time zone rules can be set correctly.

Please select a continent, ocean, "coord", or "TZ".

1) Africa

2) Americas

3) Antarctica

4) Asia

5) Atlantic Ocean

6) Australia

7) Europe

8) Indian Ocean

9) Pacific Ocean

10) coord - I want to use geographical coordinates.

11) TZ - I want to specify the time zone using the Posix TZ format.

# 4

Please select a country whose clocks agree with yours.

1) Afghanistan? ? ? ? ? ? 18) Israel? ? ? ? ? ? ? ? ? ? ? ? 35) Palestine

2) Armenia? ? ? ? ? ? ? ? ? 19) Japan? ? ? ? ? ? ? ? ? ? 36) Philippines

3) Azerbaijan? ? ? ? ? ? ? ? ? ? 20) Jordan? ? ? ? ? ? ? ? ? ? ? ? 37) Qatar

4) Bahrain? ? ? ? ? ? ? ? ? 21) Kazakhstan? ? ? ? ? ? ? ? ? ? 38) Russia

5) Bangladesh? ? ? ? ? ? ? ? ? ? 22) Korea (North)? ? ? ? ? ? ? ? 39) Saudi Arabia

6) Bhutan? ? ? ? ? ? ? ? ? ? 23) Korea (South)? ? ? ? ? ? ? ? 40) Singapore

7) Brunei? ? ? ? ? ? ? ? ? ? 24) Kuwait? ? ? ? ? ? ? ? ? ? ? ? 41) Sri Lanka

8) Cambodia? ? ? ? ? ? ? ? ? ? ? 25) Kyrgyzstan? ? ? ? ? ? ? ? ? ? 42) Syria

9) China? ? ? ? ? ? ? ? ? ? 26) Laos? ? ? ? ? ? ? ? ? ? ? 43) Taiwan

10) Cyprus? ? ? ? ? ? ? ? ? ? 27) Lebanon? ? ? ? ? ? ? ? ? ? ? 44) Tajikistan

11) East Timor? ? ? ? ? ? ? ? ? ? 28) Macau? ? ? ? ? ? ? ? ? ? 45) Thailand

12) Georgia? ? ? ? ? ? ? ? ? 29) Malaysia? ? ? ? ? ? ? ? ? ? ? 46) Turkmenistan

13) Hong Kong? ? ? ? ? ? ? ? ? ? 30) Mongolia? ? ? ? ? ? ? ? ? ? ? 47) United Arab Emirates

14) India? ? ? ? ? ? ? ? ? ? 31) Myanmar (Burma)? ? ? ? ? ? ? ? ? 48) Uzbekistan

15) Indonesia? ? ? ? ? ? ? ? ? ? 32) Nepal? ? ? ? ? ? ? ? ? ? 49) Vietnam

16) Iran? ? ? ? ? ? ? ? ? ? ? 33) Oman? ? ? ? ? ? ? ? ? ? ? 50) Yemen

17) Iraq? ? ? ? ? ? ? ? ? ? ? 34) Pakistan

# 9

Please select one of the following time zone regions.

1) Beijing Time

2) Xinjiang Time

# 1

The following information has been given:? ? China? ? Beijing TimeTherefore TZ='Asia/Shanghai' will be used.


更改系統(tǒng)時(shí)區(qū)

#sudo cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

#reboot

6、JAVA運(yùn)行環(huán)境的時(shí)區(qū)

因?yàn)?JDK再安裝時(shí),記錄了系統(tǒng)的時(shí)區(qū)。

System.getProperty("user.timezone");

System.getProperty("user.country");

所以就算修改了系統(tǒng)的時(shí)區(qū),也無法改變 JDK 的環(huán)境時(shí)區(qū)。解決辦法有兩個(gè)

1、重新安裝? JVM

2、通過命令設(shè)置更新時(shí)區(qū)

#timedatectl set-timezone Asia/Shanghai

通過此命令更新時(shí)區(qū)后。JVM 運(yùn)行環(huán)境時(shí)區(qū)就可以更新為指定時(shí)區(qū)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容