系统时区与 JAVA 运行环境

在JAVA项目中遇到了一个 Bug 因时区产生。现记录解决过程。

1、时区

全球分为24个时区,相邻时区时间相差1个小时。

2、JAVA 获取系统当前时间方法

Date date = new Date();

System.currentTimeMillis();

3、结合时区与 Java 获取系统时间

比如北京处于东八时区,东京处于东九时区,北京时间比东京时间晚1个小时,而英国伦敦时间比北京晚7个小时(英国采用夏令时时,8月英国处于夏令时)。比如此刻北京时间是2017年8月24日11:17:10,则东京时间是2017年8月24日12:17:10,伦敦时间是2017年8月24日4:17:10。

既然Date里存放的是当前时刻距1970年1月1日0点时刻的毫秒数,如果此刻在伦敦、北京、东京有三个程序员同时执行如下语句:

Date date = new Date();

System.currentTimeMillis();

那这三个date对象里存的毫秒数是相同的吗?还是北京的比东京的小3600000(北京时间比东京时间晚1小时,1小时为3600秒即3600000毫秒)?答案是,这3个Date里的毫秒数是完全一样的。确切的说,Date对象里存的是自格林威治时间( GMT)1970年1月1日0点至Date对象所表示时刻所经过的毫秒数。所以,如果某一时刻遍布于世界各地的程序员同时执行new Date语句,这些Date对象所存的毫秒数是完全一样的。也就是说,Date里存放的毫秒数是与时区无关的。

System.out.println(date);

那么北京的程序员将会打印出2017年8月24日11:17:10,而东京的程序员会打印出2017年8月24日12:17:10,伦敦的程序员会打印出2017年8月24日4:17:10。既然Date对象只存了一个毫秒数,为什么这3个毫秒数完全相同的Date对象,可以打印出不同的时间呢?这是因为Sysytem.out.println函数在打印时间时,会取操作系统当前所设置的时区,然后根据这个时区将同毫秒数解释成该时区的时间。当然我们也可以手动设置时区,以将同一个Date对象按不同的时区输出。可以做如下实验验证:

Date date = new Date(1503544630000L); // 对应的北京时间是2017-08-24 11:17:10

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

bjSdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));  // 设置北京时区

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

tokyoSdf.setTimeZone(TimeZone.getTimeZone("Asia/Tokyo"));  // 设置东京时区

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

londonSdf.setTimeZone(TimeZone.getTimeZone("Europe/London"));  // 设置伦敦时区

System.out.println("毫秒数:" + date.getTime() + ", 北京时间:" + bjSdf.format(date));

System.out.println("毫秒数:" + date.getTime() + ", 东京时间:" + tokyoSdf.format(date));

输出为:

毫秒数:1503544630000, 北京时间:2017-08-24 11:17:10

毫秒数:1503544630000, 东京时间:2017-08-24 12:17:10

毫秒数:1503544630000, 伦敦时间:2017-08-24 04:17:10

可以看出,同一个Date对象,按不同的时区来格式化,将得到不同时区的时间。由此可见,Date对象里保存的毫秒数和具体输出的时间(即年月日时分秒)是模型和视图的关系,而时区(即Timezone)则决定了将同一个模型展示成什么样的视图。

4.从字符串中读取时间

有时我们会遇到从一个字符串中读取时间的要求,即从字符串中解析时间并得到一个Date对象,比如将"2017-8-24 11:17:10"解析为一个Date对象。现在问题来了,这个时间到底指的是北京时间的2017年8月24日11:17:10,还是东京时间的2017年8月24日11:17:10?如果指的是北京时间,那么这个时间对应的东京时间2017年8月24日12:17:10;如果指的是东京时间,那么这个时间对应的北京时间就是2017年8月24日10:17:10。因此,只说年月日时分秒而不说是哪个时区的,是有歧义的,没有歧义的做法是,给出一个时间字符串,同时指明这是哪个时区的时间。

从字符串中解析时间的正确作法是:指定时区来解析。示例如下:

String timeStr = "2017-8-24 11:17:10"; // 字面时间

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

sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai")); // 设置北京时区

Date d = sdf.parse(timeStr);

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

输出为:

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

将一个时间字符串按不同时区来解释,得到的Date对象的值是不同的。验证如下:

String timeStr = "2017-8-24 11:17:10"; // 字面时间

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

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

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

System.out.println("字面时间: " + timeStr +",按北京时间来解释:" + bjSdf.format(bjDate) + ", " + bjDate.getTime());

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

tokyoSdf.setTimeZone(TimeZone.getTimeZone("Asia/Tokyo"));  // 设置东京时区

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

System.out.println("字面时间: " + timeStr +",按东京时间来解释:"  + tokyoSdf.format(tokyoDate) + ", " + tokyoDate.getTime());

输出为:

字面时间: 2017-8-24 11:17:10,按北京时间来解释:2017-08-24 11:17:10, 1503544630000

字面时间: 2017-8-24 11:17:10,按东京时间来解释:2017-08-24 11:17:10, 1503541030000

可以看出,对于"2017-8-24 11:17:10"这个字符串,按北京时间来解释得到Date对象的毫秒数是

1503544630000;而按东京时间来解释得到的毫秒数是1503541030000,前者正好比后者大于3600000毫秒即1个小时,正好是北京时间和东京时间的时差。这很好理解,北京时间2017-08-24 11:17:10对应的毫秒数是1503544630000,而东京时间2017-08-24 11:17:10对应的北京时间其实是2017-08-24 10:17:10(因为北京时间比东京时间晚1个小时),北京时间2017-08-24 10:17:10自然比北京时间2017-08-24 11:17:10少3600000毫秒。

5.将字符串表示的时间转换成另一个时区的时间字符串

综合以上分析,如果给定一个时间字符串,并告诉你这是某个时区的时间,要将它转换为另一个时区的时间并输出,正确的做法是:

1.将字符串按原时区转换成Date对象;

2.将Date对象格式化成目标时区的时间。

比如,将北京时间"2017-8-24 11:17:10"输出成东京时间,代码为:

String timeStr = "2017-8-24 11:17:10"; // 字面时间

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

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

Date date = bjSdf.parse(timeStr);  // 将字符串时间按北京时间解析成Date对象

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

tokyoSdf.setTimeZone(TimeZone.getTimeZone("Asia/Tokyo"));  // 设置东京时区

System.out.println("北京时间: " + timeStr +"对应的东京时间为:"  + tokyoSdf.format(date));

输出为:

北京时间:2017-8-24 11:17:10对应的东京时间为:2017-08-24 12:17:10

5、系统时区的修改

查看系统当前时区

# 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.


更改系统时区

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

#reboot

6、JAVA运行环境的时区

因为 JDK再安装时,记录了系统的时区。

System.getProperty("user.timezone");

System.getProperty("user.country");

所以就算修改了系统的时区,也无法改变 JDK 的环境时区。解决办法有两个

1、重新安装  JVM

2、通过命令设置更新时区

#timedatectl set-timezone Asia/Shanghai

通过此命令更新时区后。JVM 运行环境时区就可以更新为指定时区

你可能感兴趣的:(系统时区与 JAVA 运行环境)