转载连接: 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
- 如何设置Linux时间
http://www.hypexr.org/linux_date_time_help.php - 解决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/timezone
Asia/Shanghai
$cat /etc/sysconfig/clock
ZONE="Asia/Jakarta"
UTC=false
ARC=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;
}