Java TimeZone 和 Linux TimeZone问题

来源:互联网 发布:谢娜主持的网络综艺 编辑:程序博客网 时间:2024/05/22 23:16

转载连接: https://my.oschina.net/huawu/blog/4646

昨天发现跑在Linux上的java程序获取的默认时区有问题。

由于我所用Linux的时区由/etc/localtime所指的文件(如果环境变量TZ不存在时):
[xx:~]> ls -l /etc/localtime
lrwxrwxrwx 1 root root 18 Jun 21 2008 /etc/localtime -> /var/etc/localtime
[xx:~]> ls -l /var/etc/localtime
lrwxrwxrwx 1 root root 30 May 14 09:46 /var/etc/localtime -> /usr/share/zoneinfo/US/Eastern

开始时,我以为应该是和/etc/localtime指向的时区一样的,接着才发现原来java在没有TZ环境变量时取的是 /etc/sysconfig/clock

中的时时区。 Sun上面有和我这种情况相关的bug - Default timezone is incorrectly set occasionally on Linux**(http://bugs.sun.com/view_bug.do?bug_id=6456628)**, 里面描述了java vm取的默认timezone的算法:

1)如有环境变量 TZ设置,则用TZ中设置的时区

2)在 /etc/sysconfig/clock文件中找 “ZONE”的值

3)如何2)都没,就用/etc/localtime 和 /usr/share/zoneinfo 下的时区文件进行匹配,如找到匹配的,就返回对应的路径和文件名。

下面是我的测试:

java测试程序(来自:http://www.minaret.biz/tips/timezone.html)

import java.util.Date;import java.util.TimeZone;public class TimeTest {    public static void main(String args[]) {    long time = System.currentTimeMillis();    String millis = Long.toString(time);    Date date = new Date(time);    System.out.println("Current time in milliseconds = " + millis + " => " + date.toString());    System.out.println("Current time zone: " + TimeZone.getDefault().getID());    }}

查看本地时区设置:

[xx:~]> echo $TZ

(TZ 环境变量没设置)
[xx:~]> ls -l /var/etc/localtime
lrwxrwxrwx 1 root root 30 May 14 02:24 /var/etc/localtime -> /usr/share/zoneinfo/US/Arizona

[xx:~]>date
Fri May 14 02:30:05 MST 2010

date 命令显示的时间 和 /var/etc/localtime 指向的时间一致

查看/etc/sysconfig/clock中的时区设置(Redhat Linux)
[xx:~]> cat /etc/sysconfig/clock
ZONE=”America/New_York”
UTC=false
ARC=false

[xx:~]> java TimeTest
Current time in milliseconds = 1273829564349 => Fri May 14 05:32:44 EDT 2010
Current time zone: America/New_York
Current time zone display: Eastern Standard Time
[xx:~]>

TimeTest运行结果显示,java vm取得的的默认时区和 /etc/sysconfig/clock 中的设置一样。让我们来验证一下:
1)修改/etc/sysconfig/clock:
[xx:~]> vim /etc/sysconfig/clock
ZONE=”US/Central”
UTC=false
ARC=false
2)再运行 TimeTest
[xx:~]> java TimeTest
Current time in milliseconds = 1273829718269 => Fri May 14 04:35:18 CDT 2010
Current time zone: US/Central
Current time zone display: Central Standard Time

修改/var/etc/localtime 指向时区
先看看date显示:
[xx:~]> date
Fri May 14 02:36:37 MST 2010
[xx:~]> sudo ln -sf /usr/share/zoneinfo/US/Central /var/etc/localtime
查看date命令结果的变化
[xx:~]> date
Fri May 14 04:37:41 CDT 2010
可以到时间和时区自动变了

好,再看另外一种情况:当 TZ 这个环境变量存在并有设置时

首先看看TZ的值为空时,date命令结果的变化
[xx:~]> export TZ=
[xx:~]> date
Fri May 14 09:41:04 UTC 2010

时间变了,且时区显示是UTC(Universial Time Coordination).

也看看TimeTest的运行结果:
[xx:~]> java TimeTest
Current time in milliseconds = 1273830175690 => Fri May 14 09:42:55 GMT 2010
Current time zone: GMT
Current time zone display: Greenwich Mean Time
[xx:~]>

可以看出,java vm 默认时区是GMT。

给TZ赋某个时区:

[xx:~]> export TZ=”US/Central”
[xx:~]> date

Fri May 14 04:44:40 CDT 2010

date的输出跟着 TZ 变量马上调整过来

运行TimeTest
[xx:~]> java TimeTest
Current time in milliseconds = 1273830328966 => Fri May 14 04:45:28 CDT 2010
Current time zone: US/Central
Current time zone display: Central Standard Time

TimeTest 取得和TZ一样的时区

[xx:~]> sudo ln -sf /usr/share/zoneinfo/US/Eastern /var/etc/localtime
[xx:~]> date
Fri May 14 04:46:16 CDT 201

[xx:~]> export TZ=”US/Eastern”
[xx:~]> date
Fri May 14 05:47:58 EDT 2010

参考资料:

1.Default timezone is incorrectly set occasionally on Linux
http://bugs.sun.com/view_bug.do?bug_id=6456628

  1. 如何设置Linux时间
    http://www.hypexr.org/linux_date_time_help.php
  2. 解决java default Timezone 问题的方法
    http://www.minaret.biz/tips/timezone.html

————-补充:
1.jre时区支持查看目录:
java.home/jre/lib/zi
2.linux时区支持查看目录:
ls -l /usr/share/zoneinfo
单个java应用,可以在在启动时候设置时区:

java -Duser.timezone=GMT+08 ...jar

3.liunx系统时区设置详细连接:
http://coolnull.com/235.html

4.没有权限的情况下,如何实现改动
1)Java启动时候增加
-Duser.timezone=对应时区
2)linux修改:

 export TZ='Asia/Jakarta'    ----测试结果单引号有效果,双引号有问题  echo $TZ

可以通过命令tzselect 选择对应时区,显示正确的格式


遇到问题:

$ echo $TZ$ ls -l /etc/localtime lrwxrwxrwx 1 root root 33 Oct 28  2016 /etc/localtime ->  /usr/share/zoneinfo/Asia/Shanghai$ cat /etc/sysconfig/clock  ZONE="Asia/Jakarta" UTC=false ARC=false  

结果启动时候使用的是/Asia/Shanghai时区,和上面的时区处理顺序有差异!(暂时没有权限修改测试,待解决)

/etc/sysconfig/clock
配置文件里面支持 UTC,ARC,SRM,ZONE 这几个配置选项。

UTC – 指定 BIOS 中保存的时间是否是 GMT/UTC 时间,true 或 yes 表示 BIOS 里面保存的时间是 UTC
时间,false 或 no 表示 BIOS 里面保存的时间是本地时间。 ZONE – 指定时区,ZONE
的值是一个文件的相对路径名,这个文件是相对 /usr/share/zoneinfo 目录下的一个时区文件。比如 ZONE
的值可以是:“Asia/Shanghai”, “US/Pacific”, “UTC” 等(默认为ZONE=”Etc/UTC”)。 ARC –
这个选项一般配置 false 或 no,在一些特殊硬件下才配置该选项为 true 或 yes。 SRM – 同 ARC,该选项一般配置
false 或 no,在一下特殊硬件下才配置该选项为 true 或 yes


遇到比较奇葩的情况:
美国服务器:
查看系统时区为:

 date -R Tue, 11 Jul 2017 18:59:13 +0700
$echo $TZ$ cat /etc/timezoneAsia/Shanghai$cat /etc/sysconfig/clockZONE="Asia/Jakarta"UTC=falseARC=false

如果java启动的时候不设置时区,默认为时区为UTC+07的时区
如果java启动时候配置为:
java -server -Duser.timezone=UTC+07
则有效果,时区为+7时区
如果java启动时候配置为:
java -server -Duser.timezone=UTC+06
则无效果,时区还是为+7时区
如果java启动时候配置为:
java -server -Duser.timezone=UTC+08
则有效果,但是时区是为+0时区
如果java启动时候配置为:
java -server -Duser.timezone=GMT+06
则有效果,时区还是为+6时区


查询了JDK底层代码:
又对GMT 格式的专门解析方法:

 static final String         GMT_ID        = "GMT"private static TimeZone getTimeZone(String ID, boolean fallback) {        TimeZone tz = ZoneInfo.getTimeZone(ID);        if (tz == null) {            tz = parseCustomTimeZone(ID);            if (tz == null && fallback) {                tz = new ZoneInfo(GMT_ID, 0);            }        }        return tz;    } private static final TimeZone parseCustomTimeZone(String id) {        int length;        // Error if the length of id isn't long enough or id doesn't        // start with "GMT".        if ((length = id.length()) < (GMT_ID_LENGTH + 2) ||            id.indexOf(GMT_ID) != 0) {            return null;        }        ZoneInfo zi;        // First, we try to find it in the cache with the given        // id. Even the id is not normalized, the returned ZoneInfo        // should have its normalized id.        zi = ZoneInfoFile.getZoneInfo(id);        if (zi != null) {            return zi;        }        int index = GMT_ID_LENGTH;        boolean negative = false;        char c = id.charAt(index++);        if (c == '-') {            negative = true;        } else if (c != '+') {            return null;        }        int hours = 0;        int num = 0;        int countDelim = 0;        int len = 0;        while (index < length) {            c = id.charAt(index++);            if (c == ':') {                if (countDelim > 0) {                    return null;                }                if (len > 2) {                    return null;                }                hours = num;                countDelim++;                num = 0;                len = 0;                continue;            }            if (c < '0' || c > '9') {                return null;            }            num = num * 10 + (c - '0');            len++;        }        if (index != length) {            return null;        }        if (countDelim == 0) {            if (len <= 2) {                hours = num;                num = 0;            } else {                hours = num / 100;                num %= 100;            }        } else {            if (len != 2) {                return null;            }        }        if (hours > 23 || num > 59) {            return null;        }        int gmtOffset =  (hours * 60 + num) * 60 * 1000;        if (gmtOffset == 0) {            zi = ZoneInfoFile.getZoneInfo(GMT_ID);            if (negative) {                zi.setID("GMT-00:00");            } else {                zi.setID("GMT+00:00");            }        } else {            zi = ZoneInfoFile.getCustomTimeZone(id, negative ? -gmtOffset : gmtOffset);        }        return zi;    }
原创粉丝点击