Java8中引入了新的庫java.time,提供更爲好用的日誌API,從此不再在Date、Calendar這些類中糾結。本文基於java.time API文檔進行記錄和總結。
包簡介
該包下的類定義了基礎的日期-時間概念,包括instant、duration、date、time、time-zone、period等,他們都是基於ISO日曆系統。
所有的類都是不可變的,意味着它們是線程安全的
各個子包的作用
- java.time.temporal 取得底層字段
- java.time.format 解析字符串或格式化
- java.time.chrono 中立的日曆API
API介紹
核心API
Instant
本質上是一個數字時間戳,即在時間線上的某一點,某一時刻。使用可以從Clock中取得。打印日誌或持久化時標記時間很有用。曾和System.currentMillis()關聯
Instant是與java.util.Date. ZonedDateTime同等的類
Instant的組成,包含一個long用於存儲epoch秒數,即1970-01-01T00:00:00Z到現在的秒數;還有一個int用於存儲納秒。
Clock
Clock提供訪問當前Instant,帶時區的日期和時間
他可以替代System.currentMillis()和TimeZone.getDefault(),用於獲取當前的毫秒數和默認時區
時鐘不是必須的,因爲上面的LocalDate等類都有一個now()方法。Clock存在的意義在於允許我們將時間當做對象進行傳遞,因爲一個Clock對象包含了比較全面的信息。
LocalDate
僅有日期,可用於存儲生日之類的數據
它不存儲時間或時區,它僅僅是對日期的描述,不能代表時間線上的一個瞬間
在日期上的操作支持很好,可以訪問年、月、日、年中日等
LocalTime
僅有時間,沒有日期,即所謂的wall time
LocalDateTime
存儲日期和時間,精確度爲納秒
它不存儲時區,因此不能代表時間線上的一個瞬間
ZonedDateTime
帶時區的日期和時間,在將ZoneId帶入計算時非常有用。儘可能不要使用它,帶有時區將使得應用變得複雜。
ZonedDateTime是和java.util.GregorianCalendar同等的類
Duration
兩個時間的差,納秒計
Period
表示對人類有意義的一段時間,比如一年、一天
API命名約定
java.time提供了豐富但統一的API,不同前綴代表不同意思
- of - 靜態工廠方法
- parse - 用於解析的靜態方法
- get - 獲取
- is - 檢查是否爲true
- with - setter的不可變等價方法
- plus - 向一個對象增加
- minus - 減少
- to - 將一個對象轉換爲另一個類型
- at - 將一個對象和另一個對象結合,例如將LocalDate和LocalTime結合成LocalDateTime
閏秒的處理
太陽日的長度是人類測量時間的標準方法,通常一個太陽日被分爲24小時60分鐘60秒,形成86400秒。
現代計時是以原子鐘爲基礎,它精確地定義了一個名爲SI second的東西代表1秒(這是銫原子躍遷的時間),SI second非常接近86400分之一天。
SI second和太陽時的關係,在於前者是觀測地球自轉得來,是絕對意義上的準確時間;後者是計時得來的,和地球自轉無關。在計算機系統中使用的時間就是計時得來。
然而,地球自轉並不絕對穩定,使得一天的長度會發生變化;隨着時間的推移,地球自轉變慢,一天的平均時間邊長,導致SI second和太陽時出現偏差。
UTC time-scale是將額外變化的時間捆綁成整秒的標準做法。現在的UTC時間於1972年提出,引入閏秒的概念,然而閏秒的定義是複雜的。鑑於此,java.time中的API定義了自己的time-scale
Java time-scale將每個日曆日精確地劃分爲86400個細分,即秒。 這些秒數可能與 SI second不同。
Java time-scale對不同時間段的定義略有不同,每個時間段都基於作爲公民時間基礎的協商一致的國際時間表。 每當修改或替換國際商定的時間表時,必須爲其定義新的 Java time-scale。
截止2013年,Java time-scale分爲兩部分
- 1972-11-03至今,java time-scale和UTC一致,與UTC沒有閏秒的時間相同,對於閏秒的天,閏秒被平均分配在一天的最後1000秒中,以保證每天看起來還是86400秒。即意味着這天的最後1000秒,java的每一秒都比普通的時間快或慢1ms
- 1972-11-03之前,UTC時間尚未提出,世界協商時間爲UT1,相當於格林威治上的太陽時,由於沒有閏秒的說法,java的time-sclae和世界協商時間一致。
時區ID
時區ID在整個系統中是唯一的,總共有三種時區ID
-
最簡單的時區ID,以+或-開頭。
-
帶有前綴的和偏移量後綴的時區ID。如GMT+2、UTC+01:00,合法的前綴有GMT、UTC、UT。由於它有固定偏移量,因此可以調用ZoneId.normalized()標準化成一個ZoneOffset
-
基於地理區域的ID。必須由兩個以上字符組成,且不能是UTC、GMT、UT、+、- 。基於地理區域的ID的轉換規則是被定義在配置中的,由ZoneRulesProvider實現。
翻看ZoneRulesProvider的手冊,發現對時區的配置可以自定義,按照指定的規則來即可。默認情況下,它使用了位於java home的lib文件夾下tzdb.dat文件中定義的數據加載規則。我們可以覆蓋這個操作,通過設置系統變量java.time.zone.DefaultZoneRulesProvider
時區由政府部門指定,且修改頻繁。有好幾個組織做這件事。Java默認使用IANA Time Zone Database (TZDB)的規則,其它組織包括IATA和微軟。每個組織的區域ID都不一樣,我們遵循默認的TZDB就好。
時區相關類
-
OffsetTime
存儲相對於UTC時間的時間和偏移量,如’11:30+01:00’
-
OffsetDateTime
存儲相對於UTC時間的日期時間和偏移量,如’2010-12-03T11:30+01:00’,精度也是納秒
Offsetdatetime、 ZonedDateTime 和 Instant都能存儲時間線上的一個瞬間,因爲他們有時區或偏移量
-
如上兩個Offset類最初是被設計用來支持網絡協議和數據庫操作的。因爲很多數據庫不支持’Europe/Paris這樣的時區,但支持+02:00這樣的偏移量
-
ZoneId
時區ID,是用來確定Instant和LocalDateTime之間的轉換規則的,有兩種ZoneID
- 固定偏移量,相對於UTC時間,如東八區。對應子類ZoneOffset
- 地理區域,如Europe/Paris,用於對應UTC時間某個偏移量的地區,對應子類ZoneRegion
實際規則由ZoneRules類描述。
ZoneRules定義了每個時區的時區偏移規則。這些規則對時區的歷史和未來轉換建模,Zoneoffsettransition用於過去時間的轉換;Zoneoffsettransitionrule基於算法對未來實現轉換。