系统时区与 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 运行环境时区就可以更新为指定时区

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 一、 JS 两个函数 得到标准时区的时间 转换服务器时区时间 利用Date对象得到本地时间 接下来,通过Data(...
    七弦桐语阅读 1,623评论 0 2
  • 开发中,难免要对一些日期数据进行处理,下面一些方法仅供参考 一、常用日期常量 1.一天的时间 毫秒数表示 publ...
    张鱼的故事阅读 2,499评论 3 3
  • pyspark.sql模块 模块上下文 Spark SQL和DataFrames的重要类: pyspark.sql...
    mpro阅读 10,017评论 0 13
  • 正常使用中有以下三种类型: 1. 使用Timestamp,则会在数据库里存储:2017-12-21 07:20:0...
    65fd424097f8阅读 6,135评论 0 2
  • 心1 我开始练书法。非常希望能练成道家的禅宗风味。 书法练心。你选一些字开始练吧。书法老师善意地说。对我传授着书法...
    温雅老师阅读 530评论 1 2

友情链接更多精彩内容