在Linux上使用system-config-date工具來設置了一個TimeZone之後(設置的結果會被記錄在/etc/sysconfig/clock文件中),在進出夏令時的時候Java中取的時間不能跟著夏令時的時鐘變化,從而導致Java中的系統時間出現混亂。
我們寫了一個小程序用來獲取當前機器上的TimeZone信息,以及打印一些時間看看進/出夏令時的時候時間的變化。
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.TimeZone;
public class TimeZoneTest {
public static void main(String[] args) {
TimeZone tz = TimeZone.getDefault();
System.out.println("tz: " + tz);
int offset = tz.getRawOffset();
System.out.println("raw offset: " + offset);
int dstSavings = tz.getDSTSavings();
System.out.println("dstSavings: " + dstSavings);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
while(true) {
Calendar cal = Calendar.getInstance();
String msg = "[" + sdf.format(cal.getTime()) + "] " + cal.getTime();
msg += ", offset: " + TimeZone.getDefault().getOffset(cal.getTimeInMillis());
System.out.println(msg);
try {
Thread.sleep(60 * 1000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
}
下面兩個正確的Case是在設置了TimeZone為“America/Los_Angeles”的情況下運行的結果
Case 1: 設置系統時間為2014-03-09 01:59:00 AM
tz: sun.util.calendar.ZoneInfo[id="<STRONG>America/Los_Angeles</STRONG>",offset=-28800000,dstSavings=3600000,<STRONG>useDaylight=true</STRONG>,transitions=185,lastRule=java.util.SimpleTimeZone[id=America/Los_Angeles,offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]]
raw offset: -28800000
<STRONG>dstSavings: 3600000</STRONG>
[<STRONG>2014-03-09 01:59:03</STRONG>] Sun Mar 09 01:59:03 PST 2014, offset: -28800000
[<STRONG>2014-03-09 03:00:03</STRONG>] Sun Mar 09 03:00:03 PDT 2014, offset: -25200000
[2014-03-09 03:01:03] Sun Mar 09 03:01:03 PDT 2014, offset: -25200000
Case 2: 設置系統時間為2014-11-02 12:59:00 AM
tz: sun.util.calendar.ZoneInfo[id="<STRONG>America/Los_Angeles</STRONG>",offset=-28800000,dstSavings=3600000,<STRONG>useDaylight=true</STRONG>,transitions=185,lastRule=java.util.SimpleTimeZone[id=America/Los_Angeles,offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]]
raw offset: -28800000
<STRONG>dstSavings: 3600000</STRONG>
[<STRONG>2014-11-02 01:59:32</STRONG>] Sun Nov 02 01:59:32 PDT 2014, offset: -25200000
[<STRONG>2014-11-02 01:00:32</STRONG>] Sun Nov 02 01:00:32 PST 2014, offset: -28800000
[2014-11-02 01:01:32] Sun Nov 02 01:01:32 PST 2014, offset: -28800000
Case 3: 設置TimeZone為“America/North_Dakota/Center”,設置系統時間為2014-03-09 01:59:00 AM,再運行上面的程序發現
tz: sun.util.calendar.ZoneInfo[id="<STRONG>GMT-06:00</STRONG>",offset=-21600000,<STRONG>dstSavings=0,useDaylight=false</STRONG>,transitions=0,lastRule=null]
raw offset: -21600000
<STRONG>dstSavings: 0</STRONG>
[<STRONG>2014-03-09 01:59:12</STRONG>] Sun Mar 09 01:59:12 GMT-06:00 2014, offset: -21600000, loader start date: Sun Mar 09 01:59:00 GMT-06:00 2014
[<STRONG>2014-03-09 02:00:12</STRONG>] Sun Mar 09 02:00:12 GMT-06:00 2014, offset: -21600000, loader start date: Sun Mar 09 02:00:00 GMT-06:00 2014
[2014-03-09 02:01:12] Sun Mar 09 02:01:12 GMT-06:00 2014, offset: -21600000, loader start date: Sun Mar 09 02:01:00 GMT-06:00 2014
從結果中可以看到TimeZone ID=GMT-06:00, dstSavings=0,並且在時間從1:59變到2:00的時候時間並沒有向後調整到3:00 ,這說明Java沒有找到對應的時區信息(也許這是Java的一個bug),所以Java就不知道當前這個時區是不是使用了夏令時,所以最終導致取得的時間不對。
對於這類問題,可以通過下面兩個方法解決
1. 使用TZ環境變量,然後在運行Java程序
$ export TZ=America/North_Dakota/Center
$ java TimeZoneTest
2. 使用-Duser.timezone=America/North_Dakota/Center作為Java虛擬機的系統參數
java -Duser.timezone=America/North_Dakota/Center TimeZoneTest
另外,可以通過下面的一些命令查看系統的timezone信息
cat /usr/share/zoneinfo/zone.tab
zdump /usr/share/zoneinfo/*
zdump -v /usr/share/zoneinfo/America/New_York 查看夏令時時間變化點
zdump -v /etc/localtime
tz_convert /usr/share/zoneinfo/
sudo cat /etc/sysconfig/clock