技識訓,該賞
點贊再看,養成習慣
大家好,我是小虛竹,之前有粉絲私聊我,問能不能把JAVA8 新的日期時間API(JSR-310)知識點梳理出來,答案是肯定的,誰讓我寵粉呢,由于內容偏多(超十萬字了),會拆成多篇來寫,
閑話就聊到這,請看下面的正文,
文章目錄
- 第一節:概念知識
- 時區
- UTC
- GMT
- CST
- DST
- ISO-8601
- 第二節:JDK8之前:時區/偏移量TimeZone
- 第三節:JDK8開始支持:時區/偏移量 ZoneId/ZoneOffset
- 時區的規則發生變化時,如何同步時區
- TZUpdater 工具介紹
- TZUpdater 工具用法
- 手動升級
- 操作步驟:
- 服務自動化升級
- 思路步驟:
- 此思路的好處:
- 系統默認的**ZoneId**
- 指定字串得到ZoneId和獲取所有的zoneIds
- 從日期中獲取時區
- ZoneOffset(時區偏移量)
- 最小/最大偏移量
- 時分秒構造偏移量
- ZoneRegion(地理區域)
- ZoneId的實體是ZoneOffset或ZoneRegion
- 推薦相關文章
- hutool日期時間系列文章
- 其他
- 參考:
- JSR-310:新日期時間API(一)
第一節:概念知識
時區
由于世界各國家與地區經度不同,地方時也有所不同,因此會劃分為不同的時區,
正式的時區劃分包括24個時區,每一時區由一個英文字母表示,每隔經度15°劃分一個時區,
為了克服時間上的混亂,1884年在華盛頓召開的一次國際經度會議(又稱國際子午線會議)上,規定將全球劃分為24個時區(東、西各12個時區),規定英國(格林尼治天文臺舊址)為中時區(零時區)、東1—12區,西1—12區,每個時區橫跨經度15度,時間正好是1小時,最后的東、西第12區各跨經度7.5度,以東、西經180度為界,每個時區的中央經線上的時間就是這個時區內統一采用的時間,稱為區時,相鄰兩個時區的時間相差1小時,
例如,中國東8區的時間總比泰國東7區的時間早1小時,而比日本東9區的時間晚1小時,
--參考自百度百科
時區經度分布如串列所示:
| 時區 | 時區經度范圍 | 時區中心線 |
|---|---|---|
| UTC(0時區) | 7.5°W~7.5°E | 0° |
| UTC+1(東1區) | 7.5°E~22.5°E | 15°E |
| UTC+2(東2區) | 22.5°E~37.5°E | 30°E |
| UTC+3(東3區) | 37.5°E~52.5°E | 45°E |
| UTC+4(東4區) | 52.5°E~67.5°E | 60°E |
| UTC+5(東5區) | 67.5°E~82.5°E | 75°E |
| UTC+6(東6區) | 82.5°E~97.5°E | 90°E |
| UTC+7(東7區) | 97.5°E~112.5°E | 105°E |
| UTC+8(東8區) | 112.5°E~127.5°E | 120°E |
| UTC+9(東9區) | 127.5°E~142.5°E | 135°E |
| UTC+10(東10區) | 142.5°E~157.5°E | 150°E |
| UTC+11(東11區) | 157.5°E~172.5°E | 165°E |
| UTC12(東、西12區) | 172.5°E~172.5°W | 180° |
| UTC-11(西11區) | 172.5°W~157.5°W | 165°W |
| UTC-10(西10區) | 157.5°W~142.5°W | 150°W |
| UTC-9(西9區) | 142.5°W~127.5°W | 135°W |
| UTC-8(西8區) | 127.5°W~112.5°W | 120°W |
| UTC-7(西7區) | 112.5°W~97.5°W | 105°W |
| UTC-6(西6區) | 97.5°W~82.5°W | 90°W |
| UTC-5(西5區) | 82.5°W~67.5°W | 75°W |
| UTC-4(西4區) | 67.5°W~52.5°W | 60°W |
| UTC-3(西3區) | 52.5°W~37.5°W | 45°W |
| UTC-2(西2區) | 37.5°W~22.5°W | 30°W |
| UTC-1(西1區) | 22.5°W~7.5°W | 15°W |
實際上,常常1個國家或1個省份同時跨著2個或更多時區,為了照顧到行政上的方便,常將1個國家或1個省份劃在一起,例如,中國幅員寬廣,差不多跨5個時區,但為了使用方便簡單,實際上在只用東八時區的標準時即北京時間為準,
UTC
協調世界時,又稱世界統一時間、世界標準時間、國際協調時間,由于英文(CUT)和法文(TUC)的縮寫不同,作為妥協,簡稱UTC,
協調世界時是以原子時秒長為基礎,在時刻上盡量接近于世界時的一種時間計量系統,
國際原子時的準確度為每日數納秒,而世界時的準確度為每日數毫秒,許多應用部門要求時間系統接近世界時UT,對于這種情況,一種稱為協調世界時的折中時標于1972年面世,為確保協調世界時與世界時相差不會超過0.9秒,在有需要的情況下會在協調世界時內加上正或負閏秒,因此協調世界時與國際原子時之間會出現若干整數秒的差別,兩者之差逐年積累,便采用跳秒(閏秒)的方法使協調時與世界時的時刻相接近,其差不超過1s,它既保持時間尺度的均勻性,又能近似地反映地球自轉的變化,
--參考自百度百科
協調世界時跟地區位置沒有相關,不代表當前時刻某個地方的時間,所以在說某個地方時間時要加上時區,例如:中國就是UTC+8,
UTC是時間標準,這個標準把世界分成UTC-12到UTC+12共24個時區,
GMT
GMT(Greenwich Mean Time)別名:格林尼治時間(有時候翻譯也叫格林威治),中文名:世界時,
GMT是指格林尼治所在地的標準時間,也是表示地球自轉速率的一種形式,以地球自轉為基礎的時間計量系統,地球自轉的角度可用地方子午線相對于地球上的基本參考點的運動來度量,為了測量地球自轉,人們在地球上選取了兩個基本參考點:春分點(見分至點)和平太陽點,由此確定的時間分別稱為恒星時和平太陽時,
--參考自百度百科
GMT并不等于UTC,只是格林尼治剛好在0時區上,所以GMT = UTC+0才是對的,
CST
CST可視為美國、澳大利亞、古巴或中國的標準時間
美國中部時間:Central Standard Time (USA) UT-6:00
澳大利亞中部時間:Central Standard Time (Australia) UT+9:30
中國標準時間:China Standard Time UT+8:00
古巴標準時間:Cuba Standard Time UT-4:00
--參考自百度百科
所以在換算CST時間時,要注意對應的時區,這是一個坑,
美國中部時間:CST=UTC/GMT-6;
中國標準時間:CST=UTC/GMT+8;
DST
DST(Daylight Saving Time)中文名:夏令時,
表示為了節約能源,人為規定時間的意思,也叫夏時制,夏令時(Daylight Saving Time:DST),又稱“日光節約時制”和“夏令時間”,在這一制度實行期間所采用的統一時間稱為“夏令時間”,一般在天亮早的夏季人為將時間調快一小時,可以使人早起早睡,減少照明量,以充分利用光照資源,從而節約照明用電,各個采納夏時制的國家具體規定不同,全世界有近110個國家每年要實行夏令時,
--參考自百度百科
中國實作DST時間范圍:1986年至1991年,
ISO-8601
國際標準化組織的國際標準ISO 8601是日期和時間的表示方法,全稱為《資料存盤和交換形式·資訊交換·日期和時間的表示方法》,目前最新為第三版ISO8601:2004,第一版為ISO8601:1988,第二版為ISO8601:2000
--參考自百度百科
年由4位數字組成YYYY,或者帶正負號的四或五位數字表示±YYYYY,以公歷公元1年為0001年,以公元前1年為0000年,公元前2年為-0001年,
月、日用兩位數字表示:MM、DD,
只使用數字為基本格式,使用短橫線"-"間隔開年、月、日為擴展格式,
小時、分和秒都用2位數表示,對UTC時間最后加一個大寫字母Z,其他時區用實際時間加時差表示,如UTC時間下午2點30分5秒表示為14:30:05Z或143005Z,當時的北京時間表示為22:30:05+08:00或223005+0800,也可以簡化成223005+08,
注:大家還記得java的Date類嗎?它默認就是使用ISO-8601表示的,
第二節:JDK8之前:時區/偏移量TimeZone
在JDK8之前,我們一直用java.util.TimeZone來表示和處理時區和偏移量,
TimeZone.getDefault() 獲得當前JVM所運行的時區,那它是怎么獲取默認時區的呢,之前有寫過分析文章,有興趣的可以了解下,這里就不再重復了,
JDK獲取默認時區的風險和最佳實踐
有時候需要做時區的時間轉換,比如一個時間要用北京時間和紐約時間顯示,實作:
這里沒有到SimpleDateFormat 來格式化時間是因為它是執行緒不安全的,選用執行緒安全的FastDateFormat,
Apache Commons Lang包支持,
有興趣可以了解下FastDateFormat 的原始碼分析:java的SimpleDateFormat執行緒不安全出問題了,虛竹教你多種解決方案
String patternStr = "yyyy-MM-dd HH:mm:ss";
// 北京時間(new出來就是默認時區的時間)
Date bjDate = new Date();
// 得到紐約的時區
TimeZone newYorkTimeZone = TimeZone.getTimeZone("America/New_York");
// 根據此時區 將北京時間轉換為紐約的Date
FastDateFormat fastDateFormat = FastDateFormat.getInstance(patternStr,newYorkTimeZone);
System.out.println("這是北京時間:" + FastDateFormat.getInstance(patternStr).format(bjDate));
System.out.println("這是紐約時間:" + fastDateFormat.format(bjDate));

19-7=12 北京時間比紐約時間快12小時,

第三節:JDK8開始支持:時區/偏移量 ZoneId/ZoneOffset
JDK8中ZoneId表示時區的ID,ZoneOffset表示Greenwich/UTC的偏移量,

ZoneId 是用來替換java.util.TimeZone 的,
我們來研究下ZoneId ,ZoneId代表一個時區的ID,它是確定的,但是時區ID是有對應的規則,規則變化為java.time.zone.ZoneRules 決定,像夏令時規則是由各國政府定的,可能會變化,不同的年還不一樣,這個就交給JDK底層機制來保持同步,我們呼叫者不需要關心(不!要關心!當技術不再是黑盒時,才能做到心里有底! ),
時區的規則發生變化時,如何同步時區
TZUpdater 工具介紹
? 提供的 TZUpdater 工具 允許您使用更新的時區資料更新已安裝的 Java 開發工具包 (JDK) 和 Java 運行時環境 (JRE) 軟體,以適應不同國家/地區的夏令時 (DST) 更改,Oracle 依賴于通過 IANA 的時區資料庫公開提供的時區資料,
如果您無法使用 Oracle 最新的 JDK 或 JRE 更新版本,或者如果最新版本上的時區資料不是最新可用的,TZUpdater 工具提供了一種更新時區資料的方法,同時保持其他系統配置和依賴項不變.
TZUpdater 工具用法
TZUpdater 工具用于執行該工具的 JDK/JRE 軟體實體,每次執行都會修改 JDK/JRE 軟體,要將工具管理到 JDK/JRE 軟體的多個實體,
在安裝的 JDK/JRE 軟體上運行 TZUpdater 工具之前,您必須停止作業系統上的 JDK/JRE 軟體的任何正在運行的服務,
使用以下命令運行 TZUpdater 工具:
java -jar tzupdater.jar options
要成功更新時區資料,您應該確保您有足夠的權限來修改JDK_HOME /jre/lib或JRE_HOME /lib目錄,
如果未指定任何選項,則會顯示用法訊息,要更新時區資料,請使用-l或-f選項,
| 選項 | 描述 |
|---|---|
-h, --help
| 將用法列印到stdout并退出,如果指定此選項,則其他選項將被忽略, |
-V, --version | 列印工具版本、JRE 中的 tzdata 版本以及工具將更新到的 tzdata 版本,然后退出, |
-l, --location url-link-to-archive-file | 從提供的tzdata.tar.gz包中編譯、測驗和更新 JRE 時區資料,例如-l https://www.iana.org/time-zones/repository/tzdata-latest.tar.gz. 支持的 URL 協議:http://、https://、file://,如果未提供 URL 鏈接,該工具將使用位于 的最新 IANA tzdata 包https://www.iana.org/time-zones/repository/tzdata-latest.tar.gz, |
-f, --force | 強制 tzdata 更新,如果更新到較舊的 tzdata 版本,請使用此選項, |
-v, --verbose | 向 顯示詳細訊息stdout, |
手動升級
注意:
1、在安裝的 JDK/JRE 軟體上運行 TZUpdater 工具之前,您必須停止作業系統上的 JDK/JRE 軟體的任何正在運行的服務,
2、要成功更新時區資料,您應該確保您有足夠的權限來修改JDK_HOME /jre/lib或JRE_HOME /lib目錄,(linux系統:JRE目錄要有寫權限;windows系統:用管理員身份運行cmd)
3、如果系統上有多個JDK/JRE ,需要將該工具用于每個JDK/JRE中(每個JDK/JRE都要操作一遍)
4、更新成功后,要重新啟動此 JDK/JRE 實體上的應用程式服務(如果還沒更新,重啟下服務器試試)
操作步驟:
1、下載Oracle官方提供的tzupdater.jar包;下載地址
https://www.oracle.com/java/technologies/javase-tzupdater-downloads.html
把tzupdater.jar放到java目錄bin目錄下,比如
“C:\Program Files\JAVA\java-1.8.0-openjdk-1.8.0.201\bin\tzupdater.jar”;

2、查看當前時區資料庫版本,以windows為例,用管理員身份運行cmd,切換到tzupdater.jar對應的目錄:
java -jar tzupdater.jar -V

3、在線更新,以windows為例,用管理員身份運行cmd,切換到tzupdater.jar對應的目錄:(第3種和第4種更新方式任選一種)
java -jar tzupdater.jar -l https://www.iana.org/time-zones/repository/tzdata-latest.tar.gz

如圖所示,已經更新成功到了tzdata2021a版本了,
更新后的檔案是放在jre/lib/tzdb.dat ,如圖所示,它有備份歷史的版本,

4、離線更新:要先下載最新的時區資料,下載地址:
https://data.iana.org/time-zones/releases/

以windows為例,用管理員身份運行cmd,切換到tzupdater.jar對應的目錄:
java -jar tzupdater.jar -l file:///[path]/tzdata.tar.gz
注:
windows建議放在C盤根目錄下,路徑目錄也不要有中文;
用管理員身份運行cmd(需要寫權限);
如上面的命令所示,file后面的/是3個
5、以上執行完后,用第2步的查看當前時區資料庫版本命令,查看是否更新成功,

服務自動化升級
思路步驟:
1、設定定時任務(作業系統配置就行),執行tzupdater 更新時區的命令腳本;
2、新開一個時區服務,用來對外提供時區和夏令時規則讀取服務,獨立部署;
3、在時區服務中,寫個同步按鈕,用來執行tzupdater 更新時區的命令腳本;
4、在時區服務中,將timeZone資料定時寫到自定義的時區表中,提供維護功能,可以自定義新增修改洗掉timeZone資料,
此思路的好處:
1、其他服務不需要停止服務來更新時間,直接通過呼叫時區服務的資料,可保證獲取到最新的時區資料;
2、自動化的好處,避免了手動維護時區的繁瑣,人工介入有引發問題的風險;
3、時區服務和其他業務服務是拆分的,方便未來的擴展,
系統默認的ZoneId
@Test
public void timeZoneTest2(){
System.out.println("JDK 8之前做法:"+TimeZone.getDefault());
System.out.println("JDK 8之后做法:"+ZoneId.systemDefault());
}

ZoneId.systemDefault()方法實作上是呼叫了TimeZone:
public static ZoneId systemDefault() {
return TimeZone.getDefault().toZoneId();
}
所以兩個的結果是一樣的(Asia/Shanghai),這個很正常,
TimeZone.toZoneId() 是java8 后加的方法,
/**
* Converts this {@code TimeZone} object to a {@code ZoneId}.
*
* @return a {@code ZoneId} representing the same time zone as this
* {@code TimeZone}
* @since 1.8
*/
public ZoneId toZoneId() {
String id = getID();
if (ZoneInfoFile.useOldMapping() && id.length() == 3) {
if ("EST".equals(id))
return ZoneId.of("America/New_York");
if ("MST".equals(id))
return ZoneId.of("America/Denver");
if ("HST".equals(id))
return ZoneId.of("America/Honolulu");
}
return ZoneId.of(id, ZoneId.SHORT_IDS);
}
指定字串得到ZoneId和獲取所有的zoneIds
System.out.println(ZoneId.of("America/New_York"));
System.out.println(ZoneId.of("Asia/Shanghai"));

@Test
public void ZoneIdTest2(){
Set<String> zoneIds = ZoneId.getAvailableZoneIds();
System.out.println("zoneIds長度:"+zoneIds.size());
for(String zoneId : zoneIds){
System.out.println(zoneId);
}
}

指定的字串不能亂寫,不然會報錯,要在ZoneId.getAvailableZoneIds() 的集合范圍里,
從日期中獲取時區
System.out.println(ZoneId.from(ZonedDateTime.now()));
System.out.println(ZoneId.from(ZoneOffset.of("+8")));

從日期中獲取時區只支持帶有時區的TemporalAccessor ,像LocalDateTime,LocalDate是不可以的,會報錯,
try {
System.out.println(ZoneId.from(LocalDateTime.now()));
}catch (Exception e){
e.printStackTrace();
}
try {
System.out.println(ZoneId.from(LocalDate.now()));
}catch (Exception e){
e.printStackTrace();
}

ZoneId是抽象類,它有兩個繼承實作類:
- ZoneOffset:時區偏移量
- ZoneRegion:地理區域

ZoneOffset(時區偏移量)
時區偏移量是時區與Greenwich/UTC之間的時間差,一般是固定的小時數和分鐘數,
最小/最大偏移量
@Test
public void ZoneIdTest5(){
System.out.println("最小偏移量:" + ZoneOffset.MIN);
System.out.println("最小偏移量:" + ZoneOffset.MAX);
System.out.println("中心偏移量:" + ZoneOffset.UTC);
// 超出最大范圍
System.out.println(ZoneOffset.of("+100"));
}

超出最大范圍會報錯
時分秒構造偏移量
@Test
public void ZoneIdTest6(){
System.out.println(ZoneOffset.ofHours(10));
System.out.println(ZoneOffset.ofHoursMinutes(10, 10));
System.out.println(ZoneOffset.ofHoursMinutesSeconds(10, 10, 10));
System.out.println(ZoneOffset.ofHours(-10));
}

挺方便的,也簡單好理解,偏移量可以精確到秒級,

ZoneRegion(地理區域)
ZoneRegion表示地理區域,格式是:洲(州、國家)/城市,最常見的區域分類是時區資料庫(TZDB),
which defines regions such as ‘Europe/Paris’ and ‘Asia/Tokyo’.(TZDB使用“Europe/Paris”和“Asia/Tokyo”來區分地區,)
final class ZoneRegion extends ZoneId implements Serializable {
...
}
由原始碼可知,地理區域ZoneRegion是ZoneId的繼承實作類,
但是我們發現這個不是對外使用的,ZoneRegion的修飾符是default(只能由同包下的類呼叫),只能通過ZoneId來操作,
@Test
public void ZoneIdTest7(){
ZoneId zoneId = ZoneId.systemDefault();
System.out.println(zoneId);
}

博主在廈門,所以默認獲取的時區ID是Asia/Shanghai,
ZoneId的實體是ZoneOffset或ZoneRegion
ZoneId of(String zoneId, boolean checkAvailable) 原始碼分析:
/**
* Parses the ID, taking a flag to indicate whether {@code ZoneRulesException}
* should be thrown or not, used in deserialization.
*
* @param zoneId the time-zone ID, not null
* @param checkAvailable whether to check if the zone ID is available
* @return the zone ID, not null
* @throws DateTimeException if the ID format is invalid
* @throws ZoneRulesException if checking availability and the ID cannot be found
*/
static ZoneId of(String zoneId, boolean checkAvailable) {
Objects.requireNonNull(zoneId, "zoneId");
if (zoneId.length() <= 1 || zoneId.startsWith("+") || zoneId.startsWith("-")) {
return ZoneOffset.of(zoneId);
} else if (zoneId.startsWith("UTC") || zoneId.startsWith("GMT")) {
return ofWithPrefix(zoneId, 3, checkAvailable);
} else if (zoneId.startsWith("UT")) {
return ofWithPrefix(zoneId, 2, checkAvailable);
}
return ZoneRegion.ofId(zoneId, checkAvailable);
}
private static ZoneId ofWithPrefix(String zoneId, int prefixLength, boolean checkAvailable) {
String prefix = zoneId.substring(0, prefixLength);
if (zoneId.length() == prefixLength) {
return ofOffset(prefix, ZoneOffset.UTC);
}
...
}

由原始碼可知:
- zoneId長度小于等于1位,或者以“+”或“-”開頭的,創建的是ZoneOffset實體
- 以“UTC”,“UT”或“GMT”開頭的,創建的是ZoneRegion實體
- 不符合以上兩種的,創建的是ZoneRegion實體
@Test
public void ZoneIdTest8(){
ZoneId zoneId1 = ZoneId.of("+8");
ZoneId zoneId2 = ZoneId.of("+08:00");
ZoneId zoneId3 = ZoneId.of("UT+8");
ZoneId zoneId4 = ZoneId.of("Asia/Shanghai");
System.out.println();
}

推薦相關文章
hutool日期時間系列文章
1DateUtil(時間工具類)-當前時間和當前時間戳
2DateUtil(時間工具類)-常用的時間型別Date,DateTime,Calendar和TemporalAccessor(LocalDateTime)轉換
3DateUtil(時間工具類)-獲取日期的各種內容
4DateUtil(時間工具類)-格式化時間
5DateUtil(時間工具類)-決議被格式化的時間
6DateUtil(時間工具類)-時間偏移量獲取
7DateUtil(時間工具類)-日期計算
8ChineseDate(農歷日期工具類)
9LocalDateTimeUtil(JDK8+中的{@link LocalDateTime} 工具類封裝)
10TemporalAccessorUtil{@link TemporalAccessor} 工具類封裝
其他
要探索JDK的核心底層原始碼,那必須掌握native用法
萬字博文教你搞懂java原始碼的日期和時間相關用法
java的SimpleDateFormat執行緒不安全出問題了,虛竹教你多種解決方案
原始碼分析:JDK獲取默認時區的風險和最佳實踐
參考:
JSR-310:新日期時間API(一)
時區:https://baike.baidu.com/item/%E6%97%B6%E5%8C%BA/491122?fr=aladdin
UTC:https://baike.baidu.com/item/%E5%8D%8F%E8%B0%83%E4%B8%96%E7%95%8C%E6%97%B6/787659?fromtitle=UTC&fromid=5899996&fr=aladdin
GMT:https://baike.baidu.com/item/%E4%B8%96%E7%95%8C%E6%97%B6/692237?fromtitle=GMT&fromid=6026868&fr=aladdin
CST:https://baike.baidu.com/item/CST/14822063?fr=aladdin
DST:https://baike.baidu.com/item/%E5%A4%8F%E4%BB%A4%E6%97%B6/1809579?fromtitle=DST&fromid=1203186&fr=aladdin
ISO-8601:https://baike.baidu.com/item/ISO%208601/3910715?fr=aladdin
TZUpdater :https://www.oracle.com/java/technologies/javase/tzupdater-readme.html
IANA時區資料版本:https://data.iana.org/time-zones/releases/
JRE 軟體中的時區資料版本:https://www.oracle.com/java/technologies/tzdata-versions.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/294701.html
標籤:java
上一篇:# Day12-Java基礎
