1. 程式人生 > >Java中的時間、時區和夏令時

Java中的時間、時區和夏令時

相關概念


時區
時區是地球上的區域使用同一個時間定義。以前,人們通過觀察太陽的位置(時角)決定時間,這就使得不同經度的地方的時間有所不同(地方時)。1863年,首次使用時區的概念。時區通過設立一個區域的標準時間部分地解決了這個問題。世界各個國家位於地球不同位置上,因此不同國家的日出、日落時間必定有所偏差。這些偏差就是所謂的時差。

 

理論時區
理論時區以被15整除的子午線為中心,向東西兩側延伸7.5度,即每15°劃分一個時區,這是理論時區。理論時區的時間採用其中央經線(或標準經線)的地方時。所以每差一個時區,區時相差一個小時,相差多少個時區,就相差多少個小時。東邊的時區時間比西邊的時區時間來得早。為了避免日期的紊亂,提出國際日期變更線的概念。

實際時區
         但是,為了避開國界線,有的時區的形狀並不規則,而且比較大的國家以國家內部行政分界線為時區界線,這是實際時區,即法定時區。

本初子午線
英語:Prime meridian,即0度經線,亦稱格林威治子午線、格林尼治子午線或本初經線,是經過英國格林尼治天文臺的一條經線(亦稱子午線)。本初子午線的東西兩邊分別定為東經和西經,於180度相遇。

國際日期變更線
         英語:InternationalDate Line,又名國際日界線、國際換日線或國際日期線,這條子午線由於穿越陸地,而在陸地變更日期既不方便也不可行,故實際使用的國際換日線是一條基本上只經過海洋表面的折線(見附圖)。

為了解決日期紊亂問題,大體以180度經線為日界線;由於照顧行政區域的統一,日界線並不完全沿180°的子午線劃分,而是繞過一些島嶼和海峽:由北往南通過白令海峽和阿留申、薩摩亞、斐濟、湯加等群島到達紐西蘭的東邊。

         須注意的是,是由東向西越過此線,(從0hr到24hr)日期需加一天;由西向東越過此線,(從24hr到0hr)日期需減一天;如:於2011年4月8日15:45向東航行跨過此線,時間應變為2011年4月7日15:45。原理是從零度經線所在時區向東每跨 1 個區間時鐘就撥快 1 小時, 而向西每跨 1 個區間時鐘就撥慢 1 小時, 如此一來, 到了另一端經線 180 度附近, 就會有 24 小時的落差。為了平衡此一誤差, 人們因而訂定了國際換日線。

UTC
協調世界時,又稱世界標準時間或世界協調時間,簡稱UTC(從英文“Coordinated Universal Time”/法文“Temps UniverselCoordonné”而來),是最主要的世界時間標準,其以原子時秒長為基礎,在時刻上儘量接近於格林尼治標準時間。

GMT
世界時UT即格林尼治時間,格林尼治所在地的標準時間。

Unix Time
Unix時間戳(英文為Unix epoch, Unix time, POSIX time 或 Unix timestamp)

是從1970年1月1日(UTC/GMT的午夜)開始所經過的秒數,不考慮閏秒。

 

夏令時
夏時制,夏時令(DaylightSaving Time:DST),又稱“日光節約時制”和“夏令時間”,是一種為節約能源而人為規定地方時間的制度,在這一制度實行期間所採用的統一時間稱為“夏令時間”。一般在天亮早的夏季人為將時間提前一小時,可以使人早起早睡,減少照明量,以充分利用光照資源,從而節約照明用電。各個採納夏時制的國傢俱體規定不同。目前全世界有近110個國家每年要實行夏令時。

    夏令時為一個時間段,一般在進入時刻將時鐘調快一小時(例如2:00調為3:00),在退出時刻將時鐘調慢一小時(例如3:00調為2:00)。

常用Java對時間的處理
本地時間
本地時間是一個相對概念,不同時區的8:00並不是同一個時刻。

從編碼的角度去理解為UTC的1970年1月1日00:00:00這一時刻,加上經過的時間差(兩個時刻間的時間偏移量),再換算為當前時區時間。

與Unix Time(UTC的1970年1月1日00:00:00)的時間差,是時間偏移的絕對值,這個值本身沒有時區屬性,是進行時間轉換的基準(重要)。

java.util.Date
Date本質上就是相對UnixTime的毫秒數,這一點從其建構函式上可以看出來:

public Date() {

        this(System.currentTimeMillis());

}

 

public Date(longdate) {

        fastTime =date;

}

 

那麼為什麼Date().toString()會有時區屬性呢(例如:“TueFeb 02 09:59:25 CST 2016”),可以通過閱讀其toString方法得到答案。

待補充

System.currentTimeMillis()
當前時刻與Unix Time的偏移毫秒數絕對值,與時區無關。

不同時區對同一時間的展示

java.text.SimpleDateFormat
Parse
為了避免理解混亂,我們在下面程式碼中明確設定兩個時區相關屬性,以下程式碼打印出來的結果是什麼?

public static void main(String[] args) throws Exception

{

    // 系統預設的時區,影響date.toString()列印的展示結果

    TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));

 

    String strTime = "2016-02-01 18:41:30";

    dateParseTest("UTC", strTime);

    dateParseTest("Asia/Shanghai", strTime);

}

 

private static void dateParseTest(String timeZoneId, String strTime) throws Exception

{

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

 

    // 指定傳入的yyyyMMdd是哪個時區的時間

    sdf.setTimeZone(TimeZone.getTimeZone(timeZoneId));

 

    Date time = sdf.parse(strTime);

    System.out.println(time);

}

 

列印結果:

Tue Feb 02 02:41:30 CST 2016

Mon Feb 01 18:41:30 CST 2016

 

 

將系統預設時區改為UTC,結果如何呢

public static void main(String[] args) throws Exception

{

    // 系統預設的時區,影響date.toString()列印的展示結果

    TimeZone.setDefault(TimeZone.getTimeZone("UTC"));

   

    String strTime = "2016-02-01 18:41:30";

    dateParseTest("UTC", strTime);

    dateParseTest("Asia/Shanghai", strTime);

}

 

private static void dateParseTest(String timeZoneId, String strTime) throws Exception

{

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

 

    // 指定傳入的yyyyMMdd是哪個時區的時間

    sdf.setTimeZone(TimeZone.getTimeZone(timeZoneId));

 

    Date time = sdf.parse(strTime);

    System.out.println(time);

}

 

列印結果:

Mon Feb 01 18:41:30 UTC 2016

Mon Feb 01 10:41:30 UTC 2016

 

上面兩個例子可以得出結論:

1.      sdf.setTimeZone(TimeZone.getTimeZone(timeZoneId));指定待解析的時間是哪個時區的時間。

2.      TimeZone.setDefault(TimeZone.getTimeZone("timeZoneId"));指定系統預設時區,影響Date.toString的列印。

 

Format
public static void main(String[] args) throws Exception

{  

    long now = 1454381552647L;

    TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));

    System.out.println(new Date(now));

       

    TimeZone.setDefault(TimeZone.getTimeZone("UTC"));

    System.out.println(new Date(now));

       

    dateFormatTest("UTC", now);

    dateFormatTest("Asia/Shanghai", now);

}

   

private static void dateFormatTest(String timeZoneId, long timeMillis) throws Exception

{

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

    

         // 指定解析出來的時間所屬時區

    sdf.setTimeZone(TimeZone.getTimeZone(timeZoneId));

 

         String strTime = sdf.format(new Date(timeMillis));

    System.out.println(strTime);

}

 

列印結果:

Tue Feb 02 10:52:32 CST 2016

Tue Feb 02 02:52:32 UTC 2016

2016-02-02 02:52:32.647

2016-02-02 10:52:32.647

 

結論:

1.      sdf.setTimeZone(TimeZone.getTimeZone(timeZoneId));指定格式化出來的時間是哪個時區的。

 

java.util.Calendar
Calendar本質上與Date類似,是相對於UnixTime的毫秒數,這一點從其setTime()方法可以看出來:

public final void setTime(Date date) {

    setTimeInMillis(date.getTime());

}

 

cal.setTimeZone()作用是指定Calendar以什麼時區的形式展示日曆。

public static void main(String[] args) throws Exception

{  

    Date time = new Date(1454381552647L);

       

    // 系統預設時區為Asia/Shanghai,就不舉其他時區的例子了

    System.out.println(time);

       

    Calendar cal = Calendar.getInstance();

    cal.setTime(time);

       

    cal.setTimeZone(TimeZone.getTimeZone("UTC"));

    System.out.println(cal.get(Calendar.HOUR));

       

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

    System.out.println(cal.get(Calendar.HOUR));

}

 

結果:

Tue Feb 02 10:52:32 CST 2016

2

10

 

Java對夏令時的展示
Java的jdk在Date的toString中已經包含夏令時的計算,以下程式碼可以印證:

public static void main(String[] args) throws Exception

{

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

    String sTime = "1986-09-13 22:00:00";

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

    TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));

       

    Date time = sdf.parse(sTime);

    System.out.println(time.getTime());

    System.out.println(time);

       

    Calendar cd = Calendar.getInstance();

    cd.setTime(time);

       

    // 2小時以後是幾點?

    cd.add(Calendar.HOUR, 2); 

    time = cd.getTime();

       

    System.out.println("------------------------------");

    System.out.println(time.getTime());

    System.out.println(time);

}

 

列印結果:

527000400000

Sat Sep 13 22:00:00 CDT 1986

------------------------------

527007600000

Sat Sep 13 23:00:00 CST 1986

 

中華人民共和國在1986年~1991年實行了夏令時制度,每年夏令時實行時間如下:

1986年5月4日至9月14日(1986年因是實行夏令時的第一年,從5月4日開始到9月14日結束)

1987年4月12日至9月13日,

1988年4月10日至9月11日,

1989年4月16日至9月17日,

1990年4月15日至9月16日,

1991年4月14日至9月15日。

 

上面程式碼中1986-09-1322:00:00加上2小時,應該變為1986-09-13 24:00:00(或者1986-09-14 00:00:00),但由於在9月14日零點退出夏令時,時鐘向後調整1小時,實際變為1986-09-13 23:00:00。

注意:從9月14日零點退出夏令時,java的Date.toString列印的時區也從CDT恢復為CST( ChinaStandard Time UT+8:00)。

 

如何得到供展示的時間格式?
通過上面種種分析,我們得到結論,各種介面、模組、系統間,最不容易出現誤解的針對時間的引數傳遞方式,就是傳遞相對Unix Time的毫秒數。

那麼我們如何從毫秒數格式化為需要的展示形式呢?(明顯直接Date.toString不能滿足各種格式需要)

假設需求為:展示時間格式為“5:23AM,February 2 2016”並且在夏令時的情況下,展示為“5:23AM, February 2 2016 DST”,程式碼如何實現呢。

以下為樣例程式碼:

public static void main(String[] args) throws Exception

{

    // 該時間為Sat Sep 13 22:00:00 CDT 1986(在中國的夏令時中)

    Date time = new Date(527000400000L);

       

    // 明確當前系統時區,Date.toString會列印為該時區時間

    TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));

 

    System.out.println(time);

       

    // 將這個時間格式化為Asia/Shanghai時區的展示形式(在夏令時中)

    formatDst(time, "Asia/Shanghai");

       

    // 將這個時間格式化為GMT+8時區的展示形式(無夏令時)

    formatDst(time, "GMT+8");

       

    // 將這個時間格式化為UTC時區的展示形式(無夏令時)

    formatDst(time, "UTC");

}

 

private static void formatDst(Date time, String timeZoneId)

{

    SimpleDateFormat sdf = new SimpleDateFormat("H:ma, MMMM d yyyy",

            Locale.US);

 

    TimeZone tz = TimeZone.getTimeZone(timeZoneId);

    sdf.setTimeZone(tz);

    String strTime = sdf.format(time);

 

    // 判斷該時間在tz時區中是否處於夏令時中

    if (tz.inDaylightTime(time))

    {

        strTime = strTime + " DST";

    }

       

    System.out.println(strTime);

}

 

列印結果:

Sat Sep 13 22:00:00 CDT 1986

22:0PM, September 13 1986 DST

21:0PM, September 13 1986

13:0PM, September 13 1986

 

附件


時區分佈圖

--------------------- 
作者:fanst_ 
來源:CSDN 
原文:https://blog.csdn.net/fanst_/article/details/50627744 
版權宣告:本文為博主原創文章,轉載請附上博文連結!