一起neng清楚Java8的時間嗎?
如果覺得對你有幫助,能否點個贊或關個注,以示鼓勵筆者呢?!博客目錄 | 先點這裏
- 前提概念
- 需要注意的時間概念
- 時間戳與時間
- 冬夏令時
- UTC GMT CST 傻傻分不清?
- 時間格式有哪些?
- UT, UTC, GMT, CST的區別
- Java的時間類
- LocalDateTime, Instant, OffsetDateTime, ZoneDateTime
- ZoneId 和 ZoneOffset
- 時間類API實踐
- 獲取Java所支持的所有時區
- 根據時區獲得偏移量
- 獲取當前時間
- 獲取當前時間戳
- 不同時區的時間進行轉換
- 時間類轉換實踐
- LocalDateTime與時間戳的互轉
- Date與時間戳的互轉
- LocalDateTime與Date的互轉
- 相關問題
- LocalDateTime轉換爲ZoneDateTime
- 爲什麼LocalDateTime轉換成Instant需要偏移量信息?
- 爲什麼時間戳是從1970年1月1日開始的?
前提概念
需要注意的時間概念
- 時區是存在冬令時,夏令時的概念,某些地區根據當前所處的時間段的不同,其時區偏移量也會有所不同。
- 每個國家的時區可能不一樣,一些國家可能同時存在多個時區的
- 要區別時間戳和時間的概念,以及時間戳是從何時開始算起的
時間戳與時間
UNIX時間戳
UNIX時間戳(UNIX Time Stamp)爲世界協調時間(Coordinated Universal Time,即UTC)。是1970年01月01日00時00分00秒到現在的總,秒數,與時區無關。Date
,LocalDateTime
JDK8以前的時間標準是Date類,之後的標準是LocalDateTime一系列。無論是Date,還是LocalDateTime都是無時區概念的,它們就只是一個瞬時時間。
冬夏令時
什麼是冬夏令時?
-
又稱
“日光節約時制”
和“夏令時間”
,是一種爲節約能源而人爲規定地方時間的制度,在這一制度實行期間所採用的統一時間稱爲 `“夏令時間” 。 -
一般在天亮早的夏季人爲將時間提前一小時,可以使人早起早睡,減少照明量,以充分利用光照資源,從而節約照明用電。各個採納夏時制的國傢俱體規定不同。目前全世界有近110個國家每年要實行夏令時。
-
所以要注意的問題是,一些國家或地區,根據當前所處的時間的不同以及當地採用夏令時政策與否,會導致當地的時間距離UTC 0時區的偏移量發生變化。
UTC GMT CST 傻傻分不清?
時間格式有哪些?
UT
(Universal Time 世界時)UTC
(協調世界時)又稱世界統一時間、世界標準時間、國際協調時間GMT
(格林尼治標準時間)一般指世界時CST
(中央標準時間)可視爲美國、澳大利亞、古巴或中國的標準時間。
UT, UTC, GMT, CST的區別
通常而言,如果對時間沒有嚴苛精確(相差1s)的要求,其實不我們是不需要關注這些時間格式的, 比如
北京時間 = UTC+8 = GMT+8 = UT+8
-
什麼是UT時間格式?
UT 稱之爲世界時,是基於天體觀察計算出來的時間。UT本身也是一個廣泛的概念,其包括UT0, UT1, UT2等子概念。其中UT0是完全按照天體運行計算出來的時間,而UT1則是在UT0的基礎上做了一些調整,UT2則是在UT0,UT1的基礎上做了調整。UT0是最原始的觀測結果計算值,UT1是修正了地球在長時間尺度下會產生的自轉軸漂移的影響,UT2則是爲了研究需要,比UT1多修正了季節性的影響 -
什麼是UTC時間格式?
UTC稱之爲世界標準時間或世界協調時間,是當今最主要的世界時間標準,以原子時秒長爲基礎。國際原子時的誤差爲每日數納秒,世界時的誤差爲每日數毫秒,所以爲了保證UTC和UT的時間誤差不超過0.9秒,在有需要的情況下,會在世界協調時間加上正負閏秒。 -
什麼是GMT時間格式?
GMT通常是指位於倫敦郊區的皇家格林尼治天文臺的標準時間,因爲本初子午線被定義在通過那裏的經線。 GMT的測量方式本質也是基於觀察天體運動, 但由於 GMT的測量方式是比較傳統的測量方式,實際地球每天的自轉是有些不規則的,而且正在減緩,所以格林尼治時間已經不再作爲時間標準時間。
注意:
但因爲GMT過去長期作爲世界時間的標準,所以GMT也成爲世界標準時間的代名詞。所以通常我們所說的GMT時間可以等價UTC世界協調時間。起碼在Java的時間類中,是這麼一回事。
CST的解釋未完待續…
Java的時間類
LocalDateTime, Instant, OffsetDateTime, ZoneDateTime
話說,LocalDateTime, Instant ,OffsetDateTime, ZoneDateTime在java8中都可以表示時間,但是他們具體有什麼區別呢?
private static void jdk8TimeTest() {
System.out.println(LocalDateTime.now());
System.out.println(Instant.now());
System.out.println(OffsetDateTime.now());
System.out.println(ZonedDateTime.now());
}
2019-11-18T15:01:59.835255
2019-11-18T07:01:59.835660Z
2019-11-18T15:01:59.840892+08:00
2019-11-18T15:01:59.841414+08:00[Asia/Shanghai]
- LocalDateTime是不帶時區信息的,它就是一個瞬時時間,而Instant, OffsetDateTime, ZoneDateTime則是帶有時區信息的。
- 準確的說,Instant, ZoneDateTime是帶有時區信息的,OffsetDateTime是沒有時區信息,但是帶有偏移量信息。爲什麼要這樣區分呢,說白了就是冬夏令時的概念讓相同時區在不同時間段會有不同的時間偏移量。因爲ZoneDateTime是與時區相關的,因爲冬夏令時,時間偏移量會有所不同,但是OffsetDateTime是沒有時區概念的,直接與時間偏移量相關,具有固定的時間偏移量
- 而Instant又是什麼呢?與其他三種時間格式最大的區別就是Instant並不是給人類看的,而是用來給機器看的時間。說白了,Instant就是時間戳的載體。對人類而言,給你一串時間戳和一串yyyy-MM-dd, 你肯定更樂意看到yyyy-MM-dd格式的時間。但是機器而言,它並不能識別出yyyy-MM-dd是什麼,你還需要將他轉義成時間戳,機器才能看的懂。
上圖是新老時間類的對應關係
ZoneId 和 ZoneOffset 兩兄弟
- ZoneId代表的是新時間類的地區信息 ,比如
Asia/Shanghai
- ZoneOffset代表的是新時間類的時間偏移量, 比如
+08:00
我們知道世界上有很多個國家,每個國家因爲所處地球位置的不同,所採取的時區也有所不同。ZoneId就是Java中可以描述不同地區的類。而ZoneOffset就代表是距離0時區的時間偏移量。通常我們根據唯一的地區信息,就可以獲取時區偏移量。但是因爲有的地區存在冬夏令時的區別,所以根據地區和所處的時間,該地區所採取的時間偏移量可能會有些許偏差。
所以通常我們通過ZoneId去獲取ZoneOffset時,需要傳入一個時刻信息,以正確獲得當前時刻該地區的時間偏移量
時間類API實踐
獲取Java所支持的所有時區
private static void getAvailableRegion() {
Set<String> regions = ZoneId.getAvailableZoneIds();
System.out.println(regions.size());
System.out.println(regions);
}
600
[Asia/Aden, America/Cuiaba, Etc/GMT+9, Etc/GMT+8, Africa/Nairobi, America/Marigot, Asia/Aqtau, Pacific/Kwajalein, America/El_Salvador, Asia/Pontianak, Africa/Cairo, Pacific/Pago_Pago, Africa/Mbabane, Asia/Kuching, Pacific/Honolulu, Pacific/Rarotonga, America/Guatemala, Australia/Hobart, Europe/London, America/Belize, America/Panama, Asia/Chungking, America/Managua, America/Indiana/Petersburg, Asia/Yerevan, Europe/Brussels, GMT, Europe/Warsaw, America/Chicago, Asia/Kashgar, Chile/Continental, Pacific/Yap, CET, Etc/GMT-1, Etc/GMT-0, Europe/Jersey, America/Tegucigalpa, Etc/GMT-5, Europe/Istanbul, America/Eirunepe, Etc/GMT-4, America/Miquelon, Etc/GMT-3, Europe/Luxembourg, Etc/GMT-2, Etc/GMT-9, America/Argentina/Catamarca, Etc/GMT-8, Etc/GMT-7, Etc/GMT-6, Europe/Zaporozhye, Canada/Yukon, Canada/Atlantic, Atlantic/St_Helena... ]
根據時區獲得偏移量
舉個墨西哥的例子 ,Mexico時區時間
/**
* 根據zoneId和0時區當前時間獲得此時zoneId地區的時間偏移量
*/
public static void main(String[] args) {
// 0時區
Long time2019_11_18 = 1574086401759L;
Long time2020_4_6 = 1586140180000L;
String mexico = "America/Mexico_City";
System.out.println(ZoneId.of(mexico).getRules().getOffset(Instant.ofEpochMilli(time2019_11_18)));
System.out.println(ZoneId.of(mexico).getRules().getOffset(Instant.ofEpochMilli(time2020_4_6)));
}
- 因爲時間不僅僅有時區的坑,還有冬夏令時的坑。所以某些地區的時區偏移量根據不同的時間是會發生變化的。
- 比如墨西哥首都所用的時區
America/Mexico_City
, 在UTC時間2019-11-18日採用時區偏移量是"-06:00"
; 在UTC時間2020-04-06日採用時區偏移量是"-05:00"
。所以有時候我們需要根據地區和當前時間去動態獲取時區偏移量
獲取當前時間
- 獲取默認時區的當前時間
private static void getCurTime() {
System.out.println(LocalDateTime.now());
System.out.println(LocalDateTime.now(Clock.systemDefaultZone()))
System.out.println(LocalDate.now());
System.out.println(LocalTime.now());
}
2019-11-18T11:33:18.838352
2019-11-18T11:33:18.838894
2019-11-18
11:33:18.839033
- 獲取UTC時區的當前時間
private static void getUTCCurTime() {
System.out.println(LocalDateTime.now(ZoneId.of("UTC")));
System.out.println(LocalDateTime.now(Clock.systemUTC()));
}
2019-11-18T03:33:48.602664
2019-11-18T03:33:48.603065
- 獲取某個時區的當前時間
private static void getRegionCurTime() {
// 獲取墨西哥時區的時間
System.out.println(LocalDateTime.now(ZoneId.of("America/Mexico_City")));
// 獲得上海時區的時間
System.out.println(LocalDateTime.now(ZoneId.of("Asia/Shanghai")));
// 獲得UTC+08:00時區的時間
System.out.println(LocalDateTime.now(ZoneOffset.ofHours(8)));
// 獲得UTC-08:00時區的時間
System.out.println(LocalDateTime.now(ZoneOffset.ofHours(-8)));
}
2019-11-17T21:16:02.704395
2019-11-18T11:16:02.707218
2019-11-18T11:16:02.707353
2019-11-17T19:16:02.707463
獲取當前時間戳
注意一個問題
- 時間戳是沒有時區概念的,它默認就是UTC時區,從1970年01月01日00時00分00秒到現在的秒數/毫秒數
- 時間戳是指格林威治時間1970年01月01日00時00分00秒(北京時間1970年01月01日08時00分00秒)起至現在的總秒數/毫秒數。
獲得當前時間的時間戳
/**
* 時間戳沒有時區的概念,Clock帶有時區的概念是爲轉爲Date做準備的
*/
private static void getCurTimestamp() {
// 通過系統函數獲取時間戳
System.out.println(System.currentTimeMillis());
// 通過Clock獲取時間戳
System.out.println(Clock.systemDefaultZone().millis());
System.out.println(Clock.systemUTC().millis());
// 通過Instant獲取時間戳
System.out.println(Instant.now().toEpochMilli());
System.out.println(Instant.now(Clock.systemUTC()).toEpochMilli());
}
1574048584043
1574048584064
1574048584064
1574048584064
1574048584064
- Instant, Clock的本質就是通過Clock獲取時間戳, 感覺還是直接currentTimeMillis來的舒服
不同時區的時間進行轉換
- LocalDateTime, ZoneDateTime, OffsetDateTime之間的時區轉換
private static void changeRegion() {
/**
* +8時區時間
*/
System.out.println(LocalDateTime.now(ZoneId.of("Asia/Shanghai")));
/**
* -6時區時間
*/
System.out.println(LocalDateTime.now(ZoneId.of("America/Mexico_City")));
/**
* zoneDataTime從+8區轉換到-6區的時間
*/
System.out.println(ZonedDateTime.now(ZoneId.of("Asia/Shanghai")).withZoneSameInstant(ZoneId.of("America/Mexico_City")));
/**
* LocalDateTime從+8區轉換到-6區的時間
*/
System.out.println(LocalDateTime.now(ZoneId.of("Asia/Shanghai")).atZone(ZoneId.of("Asia/Shanghai")).withZoneSameInstant(ZoneId.of("America/Mexico_City")).toLocalDateTime());
/**
* OffsetDateTime從+8區轉換到-6區的時間
*/
System.out.println(OffsetDateTime.now(ZoneId.of("Asia/Shanghai")).withOffsetSameInstant(ZoneId.of("America/Mexico_City").getRules().getOffset(LocalDateTime.now())));
}
2019-11-18T20:04:27.177675
2019-11-18T06:04:27.178720
2019-11-18T06:04:27.180764-06:00[America/Mexico_City]
2019-11-18T06:04:27.180896
2019-11-18T06:04:27.181374-06:00
時間類轉換實踐
LocalDateTime與時間戳的互轉
(一) LocalDateTime轉換爲時間戳
private static void jdk8TimeToLong() {
System.currentTimeMillis();
/**
* 將默認時區的當前時間,轉換爲時間戳
*/
System.out.println(LocalDateTime.now().atZone(Clock.systemDefaultZone().getZone()).toInstant().toEpochMilli());
System.out.println(LocalDateTime.now().toInstant(ZoneOffset.ofHours(8)).toEpochMilli());
System.out.println(ZonedDateTime.now().toInstant().toEpochMilli());
System.out.println(ZonedDateTime.now(Clock.systemDefaultZone()).toInstant().toEpochMilli());
System.out.println(OffsetDateTime.now().toInstant().toEpochMilli());
System.out.println(OffsetDateTime.now(Clock.systemDefaultZone()).toInstant().toEpochMilli());
/**
* 將UTC時區的當前時間,轉換成時間戳
*/
System.out.println(LocalDateTime.now(Clock.systemUTC()).toInstant(ZoneOffset.UTC).toEpochMilli());
System.out.println(ZonedDateTime.now(Clock.systemUTC()).toInstant().toEpochMilli());
System.out.println(OffsetDateTime.now(Clock.systemUTC()).toInstant().toEpochMilli());
/**
* 將指定時區(墨西哥-6)的當前時間,轉換成時間戳
*/
System.out.println(LocalDateTime.now(ZoneId.of("America/Mexico_City")).atZone(ZoneId.of("America/Mexico_City")).toInstant().toEpochMilli());
System.out.println(ZonedDateTime.now(ZoneId.of("America/Mexico_City")).toInstant().toEpochMilli());
System.out.println(OffsetDateTime.now(ZoneId.of("America/Mexico_City")).toInstant().toEpochMilli());
}
-
LocalDateTime是一個瞬時時間,這個類是並沒有時區的概念。
但是我們LocalDateTime.now()
獲取的時間實際上卻是某個時區的當前時間。理解這點很重要,雖然很繞,所以我們可以簡單的理解成,我們根據時區信息獲得了該時區的當前時間LocalDateTime, 但是它就類似一個寫死的字符串,僅僅表示一個瞬時時刻。 -
那麼我們如何將一個沒有時區概念的瞬時時間字符串,轉換成時間戳呢?
因爲時間戳也是沒有時區的概念,它的含義等同於UTC時區下,1970年到當前時刻的毫秒數。所以如果我們要將某個時區的當前時間轉換成時間戳。我們就需要經歷兩個步驟
(1) 將某時區的時間轉換爲UTC 0時區時間
(2) 然後計算1970年到該0時區時間的毫秒值 -
爲什麼需要轉換成0時區的時間?
(1) 因爲時間戳的定義就是0時區下,1970年01月01日00時00分00秒
到當前時刻(0)是毫秒數。或者說北京時間(+8)下,1970年01月01日08時00分00
秒到當前時刻(+8)是毫秒數。
(2) 比如我們的LocalDateTime是北京時區下獲得的時間。我們直接直接求1970年01月01日00時00分00秒到該北京時間時刻,就會發現求的的時間戳多了8個小時的毫秒數
時間戳 - @百度百科 -
所以我們可以看到LocalDateTime的toInstant方法是需要一個偏移量
該ZoneOfffset偏移量是指某時區到0時區的時間偏移量,比如相差多少秒,多少小時。當我們傳入偏移量後,toInstant方法就會根據偏移量自動處理時區帶來的時間戳偏差,並求得正確的時間戳
(一) 時間戳轉換爲LocalDateTime
private static void longToJDK8Time() {
/**
* 0時區-2019-11-18 02:29:40
* +8時區-2019-11-18 10:29:40
* -6時區-2019-11-17 20:29:40
*/
Long time = 1574044180000L;
/**
* 將時間戳轉換成默認時區的當前時間
*/
System.out.println(LocalDateTime.ofInstant(Instant.ofEpochMilli(time),ZoneId.systemDefault()));
System.out.println(Instant.ofEpochMilli(time).atZone(ZoneId.systemDefault()));
System.out.println(Instant.ofEpochMilli(time).atOffset(ZoneOffset.ofHours(8)));
/**
* 將時間戳轉換成UTC 0時區的當前時間
*/
System.out.println(LocalDateTime.ofInstant(Instant.ofEpochMilli(time),ZoneId.of("UTC")));
System.out.println(Instant.ofEpochMilli(time).atZone(ZoneId.of("UTC")));
System.out.println(Instant.ofEpochMilli(time).atOffset(ZoneOffset.UTC));
/**
* 將時間戳轉換成指定時區的當前時間
*/
System.out.println(LocalDateTime.ofInstant(Instant.ofEpochMilli(time),ZoneId.of("America/Mexico_City")));
System.out.println(Instant.ofEpochMilli(time).atZone(ZoneId.of("America/Mexico_City")));
System.out.println(Instant.ofEpochMilli(time).atOffset(ZoneOffset.of("-6")));
}
2019-11-18T10:29:40
2019-11-18T10:29:40+08:00[Asia/Shanghai]
2019-11-18T10:29:40+08:00
2019-11-18T02:29:40
2019-11-18T02:29:40Z[UTC]
2019-11-18T02:29:40Z
2019-11-17T20:29:40
2019-11-17T20:29:40-06:00[America/Mexico_City]
2019-11-17T20:29:40-06:00
Instant.ofEpochMilli(timestamp)
可以獲得0時區的Instant時刻。 然後將Instant轉換成我們所需要的LocalDateTime, OffsetDateTime, ZoneDateTime;
Date與時間戳的互轉
Date now = new Date(System.currentTimeMillis());
Long nowTime = now.getTime();
- 是不是有一種過於簡單,過於支持,過於舒服的趕腳
LocalDateTime和Date的互轉
public void convert() {
// LocalDateTime to Date
LocalDateTime localDateTime1 = LocalDateTime.now();
Long nowTime = localDateTime1.toInstant(ZoneOffset.ofHours(+0)).toEpochMilli();
Date date1 = new Date(nowTime);
// Date to LocalDateTime
Date date = new Date();
Instant instant = Instant.ofEpochMilli(date.getTime());
LocalDateTime localDateTime2 = LocalDateTime.ofInstant(instant, ZoneId.of("UTC"));
}
- LocalDateTime和Date之間的互轉,其中間類就是Instant , 再精確點,其實就是時間戳
相關問題
LocalDateTime轉換爲ZoneDateTime
LocalDateTime是沒有時區概念的,同時通過atZone方法轉換爲ZoneDateTime也是不會將時間轉換成對應時區的時間的。僅僅是在原LocalDateTime瞬時時間的基礎上加上時區的信息
public static void main(String[] args) {
System.out.println(LocalDateTime.now());
System.out.println(LocalDateTime.now().atZone(ZoneId.of("UTC")));
}
// 僅僅是增加了時區信息,時間並沒有發生改變
2019-11-18T16:12:23.020021
2019-11-18T16:12:23.020445Z[UTC]
爲什麼LocalDateTime轉換成Instant需要偏移量信息?
- LocalDateTime轉換成Instant需要時區或時間偏移量信息
// 我們獲取utc+8的當前時間
LocalDateTime date = LocalDateTime.now(ZoneOffset.ofHours(8));
// 然後將該時間轉換成Instant, 並告訴Instant這個date是utc+8的時間
Instant instant = date.toInstant(ZoneOffset.ofHours(8));
- ZoneDateTime或OffsetDateTime轉換成Instant則不需要時區或時間偏移量信息
Instant instant1 = ZonedDateTime.now(ZoneOffset.ofHours(8)).toInstant();
Instant instant2 = OffsetDateTime.now(ZoneOffset.ofHours(8)).toInstant();
因爲LocalDateTime
就是一個時間字符串,它沒有時區的概念。而Instant
是具有時區概念的。當我們把一個無時區概念的純粹時間字符串轉換成Instant時,我們就需要告訴Instant
, 這個時間字符串距離UTC 0時區
的時間偏移量是多少,這樣我們的Instant才能正確的表示LocalDateTime時刻的時間是正確時區的時刻。
說白了就是 , 計算機並不能知道LocalDateTime是什麼時區的時間,所以我們需要人爲的告訴計算機,這個LocalDateTime是哪個時區的時間,這樣我們才能知道LocalDateTime屬於哪個時區的時刻,而不屬於其他時間的時刻,才能得到一個帶時區概念,且正確的Instant.
同理,當我們將ZoneDateTime或OffsetDateTime轉換成Instant時就會發現,他們並不需要傳入ZoneOffset參數。這是因爲ZoneDateTime和OffsetDateTime本身是帶有時區或時間偏移量信息的。所以並不需要額外再告訴Instant時區信息
爲什麼時間戳是從1970年1月1日開始的?
Unix時間戳的定義
- Unix時間戳的定義是從格林尼治時間 1970年01日01月 00時00分00毫秒起至現在的總秒數,不考慮閏秒
爲什麼要從1970年開始?
-
最初計算機操作系統是32位,而時間也是用32位表示。32位能表示的最大值是2147483647。另外1年365天的總秒數是31536000,2147483647/31536000 = 68.1,也就是說32位能表示的最長時間是68年,從1970年開始算起,只能表示到68年2038年01月19日03時14分07秒,便會到達最大時間,過了這個時間點,所有32位操作系統時間便會變爲10000000 00000000 00000000 00000000 ,這樣便會出現時間迴歸的現象,造成時間異常
-
說白了就是因爲早期32位操作系統是哪個時間點發明的,後來隨着64位操作系統的出現,雖然已經可以解決時間迴歸的問題,但是爲了兼容以前32位操作系統的設計,把1970年作爲時間戳的開始時間的設計也就保留至今
服務端統一時間方案
- 數據庫統一時區設置爲UTC
- 服務端應用的時區統一設置爲UTC
時間偏移量前綴UTC+8 還是GMT+8 ?
參考資料
- 日期、時間 API 概述
- UNIX時間戳轉換、UNIX時間戳普通時間相互轉換
- Introduction to the Java 8 Date/Time API - @作者:Baeldung
- ZoneOffset in Java - @作者:Baeldung
- 如果覺得對你有幫助,能否點個贊或關個注,以示鼓勵筆者呢?!