天天看點

對時區和時間轉換總是似懂非懂?看這一篇文章就夠了

作者:Java解白

大家好,我是阿建,一個始終站在技術一線的Leader。又到了一天最惬意的時光,泡上一杯綠茶,跟着我一起回顧平時無法系統梳理的知識。

今天來說說說時區和時間。這個問題在新手剛接觸的時候總是搞得似懂非懂,因為惬意的時光總是短暫,是以這裡隻是把最容易搞混的地方來捋清楚。

GMT和UTC時間差別與聯系

GMT時間

GMT(Greenwich Mean Time),格林威治時間。這是英國的格林威治皇家天文台為了海上霸權的擴張計劃,在十七世紀就開始進行天體觀測。為了天文觀測,選擇了穿過倫敦格林威治天文台子午儀中心的一條經線作為零度參考線,這條線,簡稱格林威治子午線。

它規定太陽每天經過位于英國倫敦郊區的皇家格林威治天文台的時間為中午12點。很顯然,因為地球每天的自轉是不規則的(正在緩慢減速)是以,格林尼治時間的精确度會越來越低,但是依照地球每天的自轉而制定的時間非常符合人的認知,是以說很适用。

1884年10月,在美國華盛頓召開了一個國際子午線會議,該會議将格林威治子午線設定為本初子午線,并将格林威治時間作為世界基準時間(UT, Universal Time)。由此也确定了全球24小時自然時區的劃分,所有時區都以和GMT 之間的偏移量做為參考。

時區概念

世界基準時間時區定義如下:

從格林威治本初子午線起,經度每向東或者向西間隔15°,就劃分一個時區,在這個區域内,大家使用同樣的标準時間。全球共分為24個标準時區,相鄰時區的時間相差一個小時。

1972年之前,格林威治時間(GMT)一直是世界時間的标準。1972年之後,GMT不再是一個時間标準了。

UTC

顯然,無論是GMT時間還是世界基準時間,其實都不是很精确,那麼不禁要問,找一個絕對精确的絕對時間不就行了?

很顯然,這不現實的。因為人類所能直接感覺的時間就是日升日落(地球自轉),因為這種符合作息規律才更容易被人接受和使用。想想全世界都用一種基于原子鐘的精确時間,有的地方是日上三竿,有的地方是漫漫黑夜,交流起來豈不是更加困難?

是以,為了既要精确,又要不違背實際的天文規律,UTC時間誕生了。

UTC(Coodinated Universal Time),協調世界時,又稱世界統一時間、世界标準時間、國際協調時間。由于英文(CUT)和法文(TUC)的縮寫不同,作為妥協,簡稱UTC。

UTC是現在全球通用的時間标準,全球各地都同意将各自的時間進行同步協調。

UTC 時間是經過平均太陽時(以格林威治時間GMT為準)、地軸運動修正後的新時标以及以秒為機關的國際原子時所綜合精算而成:因為地球自轉越來越慢,每年都會比前一年多出零點幾秒,每隔幾年協調世界時組織都會給世界時+1秒,讓基于原子鐘的世界時和基于天文學(人類感覺)的格林威治标準時間相差不至于太大。

是以,UTC與GMT基本上等同,但是如果要算時間差,肯定是用UTC時間更加精确,此外,UTC時間轉指格林威治時間,是以全世界其他地方的時間都要加一個偏移量來表示。

UTC時間的表示

如上文所說,UTC時間要帶上時區,那麼很顯然,UTC時間可以表示如下:

第一種:直接帶上時區

類型 示例
日期:年 1997
日期:年月 1997-07
日期:年月日 1997-07-16
日期加小時和分鐘 1997-07-16T19:20+01:00
日期加小時、分鐘和秒鐘 1997-07-16T19:20:30+01:00
日期加小時、分鐘、秒鐘和秒鐘的小數部分 1997-07-16T19:20:30.45+01:00

其中:

  • YYYY = 四位數年份
  • MM = 月份,從 01 到 12 的兩位數
  • DD = 幾号,從 01 到 31 的兩位數
  • T = 在日期後時間前的文本值
  • hh = 小時,從 00 到 23 的兩位數
  • mm = 分鐘,從 00 到 59 的兩位數
  • ss = 秒鐘,從 00 到 59 的兩位數
  • SSS = 一或多位數,表示秒的小數部分
  • 後面的+或者-表示時間的偏移量。

第二種,不帶時區,但是明确指出這是UTC時間

如果不帶時區訓示符,那麼,末尾應該帶上一個“Z”,表示這是UTC時間而非本地時間。比如說:2015-08-03T07:21:33Z。轉換成中原標準時間就是2015-08-03 15:21:33。

但其實,ISO其實規定了很多的時間格式,Java的DateTimeFormatter規定如下的合法格式:

常量 說明 示例
BASIC_ISO_DATE Basic ISO date '20111203'
ISO_LOCAL_DATE ISO Local Date '2011-12-03'
ISO_OFFSET_DATE ISO Date with offset '2011-12-03+01:00'
ISO_DATE ISO Date with or without offset '2011-12-03+01:00'; '2011-12-03'
ISO_LOCAL_TIME Time without offset '10:15:30'
ISO_OFFSET_TIME Time with offset '10:15:30+01:00'
ISO_TIME Time with or without offset '10:15:30+01:00'; '10:15:30'
ISO_LOCAL_DATE_TIME ISO Local Date and Time '2011-12-03T10:15:30'
ISO_OFFSET_DATE_TIME Date Time with Offset '2011-12-03T10:15:30+01:00'
ISO_ZONED_DATE_TIME Zoned Date Time '2011-12-03T10:15:30+01:00[Europe/Paris]'
ISO_DATE_TIME Date and time with ZoneId '2011-12-03T10:15:30+01:00[Europe/Paris]'
ISO_ORDINAL_DATE Year and day of year '2012-337'
ISO_WEEK_DATE Year and Week '2012-W48-6'
ISO_INSTANT Date and Time of an Instant '2011-12-03T10:15:30Z'
RFC_1123_DATE_TIME RFC 1123 / RFC 822 'Tue, 3 Jun 2008 11:05:30 GMT'

2.關于時區&偏移量

在JDK 8之前,Java使用java.util.TimeZone來表示時區。而在JDK8裡分别使用了ZoneId表示時區,ZoneOffset表示UTC的偏移量。

值得強調的是,時區和偏移量在概念和實際作用上是有較大差別的,主要展現在:

  1. UTC偏移量僅僅記錄了偏移的小時分鐘而已,除此之外無任何其它資訊。舉個例子:+08:00的意思是比UTC時間早8小時,沒有地理/時區含義,相應的-03:30代表的意思僅僅是比UTC時間晚3個半小時
  2. 時區是特定于地區而言的,它和地理上的地區(包括規則)強綁定在一起。比如整個中國都叫東八區,紐約在西五區等等

中國沒有夏令時,所有東八區對應的偏移量永遠是+8;紐約有夏令時,是以它的偏移量可能是-4也可能是-5。

綜合來看,時區更好用。令人惱火的夏令時問題,若你使用UTC偏移量去表示那麼就很麻煩,因為它可變:一年内的某些時期在原來基礎上偏移量 +1,某些時期 -1;但若你使用ZoneId時區去表示就很友善喽,比如紐約是西五區,你在任何時候擷取其當地時間都是能得到正确答案的,因為它内置了對夏令時規則的處理,也就是說啥時候+1啥時候-1時區自己門清,不需要API調用者關心。

如果将時區寫死成偏移量,在沒有時區規則(沒有夏令時)的國家不會存在問題,東八區和UTC+08:00效果永遠一樣。但在一些夏令時國家(如美國、法國等等),就隻能根據時區去擷取當地時間,是以當你不了解當地規則時,最好是使用時區而非偏移量。

ZoneId

它代表一個時區的ID,如Europe/Paris。它規定了一些規則可用于将一個Instant時間戳轉換為本地日期/時間LocalDateTime。

上面說了時區ZoneId是包含有規則的,實際上描述偏移量何時以及如何變化的實際規則由java.time.zone.ZoneRules定義。ZoneId則隻是一個用于擷取底層規則的ID。之是以采用這種方法,是因為規則是由政府定義的,并且經常變化,而ID是穩定的。

可以在這裡檢視标準時區ID:标準時區ID

是以,要用ZoneId來作為時區。不能單純用偏移量。

關于Unix時間戳與Instant

Unix時間(Unix Time),是計算機世界的時間,也叫做POSIX時間或紀元時間(Epoch Time),是用來記錄時間的流逝,是以也常被叫做時間戳。

定義為從1970-01-01T00:00:00開始流逝的秒數,不考慮閏秒。之後的時間是正數,之前的是負數。

從定義可以看到,它隻代表了從Unix紀元開始流逝的秒數,是以你身處地球上何處,這個時間都是一樣的。

一般Unix時間都是精确到秒,但也有些地方Unix時間是精确到毫秒的(比如MySQL 5.6.4之後開始支援到微秒)

Unix時間戳是從1970年1月1日(UTC+00)開始所經過的秒數,不考慮閏秒。時間戳表示的時間是準确、恒定的。就連時間+日期+時區也不行——時區不是恒定不變的,這是一個全球統一的絕對時間。

是以,如果說我們不想對時區進行各種轉換,那麼最好是存儲Unix時間戳,再轉換成對應的UTC帶時區時間,或者直接存儲UTC時間。

Instant

Instant類傳回的值計算從 1970 年 1 月 1 日(1970-01-01T00:00:00Z)第一秒開始的時間(精确到納秒),也稱為 EPOCH。 發生在時期之前的瞬間具有負值,并且發生在時期後的瞬間具有正值,是以這個時間就等于UTC時間。

String output = instant.toString();
複制代碼           

輸出:

2023-02-20 T19:15:25.864Z
複制代碼           

Instant簡單了解為就是用一個對象來表示unix時間戳。時間等于目前的UTC時間

繼續閱讀