你好,我是A哥(YourBatman)。java
在JSR 310日期時間體系了,一共有三個API可用於表示日期時間:程序員
也許平時開發中你只用到過LocalDateTime這個API,那是極好的,可是不能止步於此,不然就too樣too森破了。數據庫
隨着場景的多樣性變化,我們開發者接觸到OffsetDateTime/ZonedDateTime的機率愈來愈大,但凡和國際化產生上關係的大機率都會用獲得它們。本文依然站在實用的角度,輔以具體代碼示例,介紹它三。網絡
下面這張圖是一個完整的日期時間,拆解各個部分的含義,一目瞭然(建議收藏此圖):架構
由於LocalDate、LocalTime等理解起來比較簡單,就不用再花筆墨介紹了,重點放在LocalDateTime、OffsetDateTime、ZonedDateTime它三身上。ide
ISO-8601日曆系統中不帶時區的日期時間。code
說明:ISO-8601日系統是現今世界上絕大部分國家/地區使用的,這就是咱們國人所說的公曆,有閏年的特性orm
LocalDateTime是一個不可變的日期-時間對象,它表示一個日期時間,一般被視爲年-月-日-小時-分鐘-秒。還能夠訪問其餘日期和時間字段,如day-of-year、day-of-week和week-of-year等等,它的精度能達納秒級別。中間件
該類不存儲時區,因此適合日期的描述,好比用於生日、deadline等等。可是請記住,若是沒有偏移量/時區等附加信息,一個時間是不能表示時間線上的某一時刻的。對象
最大/最小值:
@Test public void test1() { LocalDateTime min = LocalDateTime.MIN; LocalDateTime max = LocalDateTime.MAX; System.out.println("LocalDateTime最小值:" + min); System.out.println("LocalDateTime最大值:" + max); System.out.println(min.getYear() + "-" + min.getMonthValue() + "-" + min.getDayOfMonth()); System.out.println(max.getYear() + "-" + max.getMonthValue() + "-" + max.getDayOfMonth()); } 輸出: LocalDateTime最小值:-999999999-01-01T00:00 LocalDateTime最大值:+999999999-12-31T23:59:59.999999999 -999999999-1-1 999999999-12-31
構造:
@Test public void test2() { System.out.println("當前時區的本地時間:" + LocalDateTime.now()); System.out.println("當前時區的本地時間:" + LocalDateTime.of(LocalDate.now(), LocalTime.now())); System.out.println("紐約時區的本地時間:" + LocalDateTime.now(ZoneId.of("America/New_York"))); } 輸出: 當前時區的本地時間:2021-01-17T17:00:41.446 當前時區的本地時間:2021-01-17T17:00:41.447 紐約時區的本地時間:2021-01-17T04:00:41.450
注意,最後一個構造傳入了ZoneId,並非說LocalDateTime和時區有關了,而是告訴說這個Local指的是紐約,細品這句話。
計算:
@Test public void test3() { LocalDateTime now = LocalDateTime.now(ZoneId.systemDefault()); System.out.println("計算前:" + now); // 加3天 LocalDateTime after = now.plusDays(3); // 減4個小時 after = after.plusHours(-3); // 效果同now.minusDays(3); System.out.println("計算後:" + after); // 計算時間差 Period period = Period.between(now.toLocalDate(), after.toLocalDate()); System.out.println("相差天數:" + period.getDays()); Duration duration = Duration.between(now.toLocalTime(), after.toLocalTime()); System.out.println("相差小時數:" + duration.toHours()); } 輸出: 計算前:2021-01-17T17:10:15.381 計算後:2021-01-20T14:10:15.381 相差天數:3 相差小時數:-3
格式化:
@Test public void test4() { LocalDateTime now = LocalDateTime.now(ZoneId.systemDefault()); // System.out.println("格式化輸出:" + DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(now)); System.out.println("格式化輸出(本地化輸出,中文環境):" + DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT, FormatStyle.SHORT).format(now)); String dateTimeStrParam = "2021-01-17 18:00:00"; System.out.println("解析後輸出:" + LocalDateTime.parse(dateTimeStrParam, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss", Locale.US))); } 輸出: 格式化輸出(本地化輸出,中文環境):21-1-17 下午5:15 解析後輸出:2021-01-17T18:00
ISO-8601日曆系統中與UTC偏移量有關的日期時間。OffsetDateTime是一個帶有偏移量的日期時間類型。存儲有精確到納秒的日期時間,以及偏移量。能夠簡單理解爲 OffsetDateTime = LocalDateTime + ZoneOffset。
OffsetDateTime、ZonedDateTime和Instant它們三都能在時間線上以納秒精度存儲一個瞬間(請注意:LocalDateTime是不行的),也可理解我某個時刻。OffsetDateTime和Instant可用於模型的字段類型,由於它們都表示瞬間值而且還不可變,因此適合網絡傳輸或者數據庫持久化。
ZonedDateTime不適合網絡傳輸/持久化,由於即便同一個ZoneId時區,不一樣地方獲取到瞬時值也有可能不同
最大/最小值:
@Test public void test5() { OffsetDateTime min = OffsetDateTime.MIN; OffsetDateTime max = OffsetDateTime.MAX; System.out.println("OffsetDateTime最小值:" + min); System.out.println("OffsetDateTime最大值:" + max); System.out.println(min.getOffset() + ":" + min.getYear() + "-" + min.getMonthValue() + "-" + min.getDayOfMonth()); System.out.println(max.getOffset() + ":" + max.getYear() + "-" + max.getMonthValue() + "-" + max.getDayOfMonth()); } 輸出: OffsetDateTime最小值:-999999999-01-01T00:00+18:00 OffsetDateTime最大值:+999999999-12-31T23:59:59.999999999-18:00 +18:00:-999999999-1-1 -18:00:999999999-12-31
偏移量的最大值是+18,最小值是-18,這是由ZoneOffset內部的限制決定的。
構造:
@Test public void test6() { System.out.println("當前位置偏移量的本地時間:" + OffsetDateTime.now()); System.out.println("偏移量-4(紐約)的本地時間::" + OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.of("-4"))); System.out.println("紐約時區的本地時間:" + OffsetDateTime.now(ZoneId.of("America/New_York"))); } 輸出: 當前位置偏移量的本地時間:2021-01-17T19:02:06.328+08:00 偏移量-4(紐約)的本地時間::2021-01-17T19:02:06.329-04:00 紐約時區的本地時間:2021-01-17T06:02:06.330-05:00
計算:
略
格式化:
@Test public void test7() { OffsetDateTime now = OffsetDateTime.now(ZoneId.systemDefault()); System.out.println("格式化輸出(本地化輸出,中文環境):" + DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT, FormatStyle.SHORT).format(now)); String dateTimeStrParam = "2021-01-17T18:00:00+07:00"; System.out.println("解析後輸出:" + OffsetDateTime.parse(dateTimeStrParam)); } 輸出: 格式化輸出(本地化輸出,中文環境):21-1-17 下午7:06 解析後輸出:2021-01-17T18:00+07:00
轉換:
LocalDateTime -> OffsetDateTime
@Test public void test8() { LocalDateTime localDateTime = LocalDateTime.of(2021, 01, 17, 18, 00, 00); System.out.println("當前時區(北京)時間爲:" + localDateTime); // 轉換爲偏移量爲 -4的OffsetDateTime時間 // 一、-4地方的晚上18點 System.out.println("-4偏移量地方的晚上18點:" + OffsetDateTime.of(localDateTime, ZoneOffset.ofHours(-4))); System.out.println("-4偏移量地方的晚上18點(方式二):" + localDateTime.atOffset(ZoneOffset.ofHours(-4))); // 二、北京時間晚上18:00 對應的-4地方的時間點 System.out.println("當前地區對應的-4地方的時間:" + OffsetDateTime.ofInstant(localDateTime.toInstant(ZoneOffset.ofHours(8)), ZoneOffset.ofHours(-4))); } 輸出: 當前時區(北京)時間爲:2021-01-17T18:00 -4偏移量地方的晚上18點:2021-01-17T18:00-04:00 -4偏移量地方的晚上18點(方式二):2021-01-17T18:00-04:00 當前地區對應的-4地方的時間:2021-01-17T06:00-04:00
經過此例值得注意的是:LocalDateTime#atOffset()/atZone()
只是增長了偏移量/時區,本地時間是並無改變的。若想實現本地時間到其它偏移量的對應的時間只能經過其ofInstant()
系列構造方法。
OffsetDateTime -> LocalDateTime
@Test public void test81() { OffsetDateTime offsetDateTime = OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.ofHours(-4)); System.out.println("-4偏移量時間爲:" + offsetDateTime); // 轉爲LocalDateTime 注意:時間仍是未變的哦 System.out.println("LocalDateTime的表示形式:" + offsetDateTime.toLocalDateTime()); } 輸出: -4偏移量時間爲:2021-01-17T19:33:28.139-04:00 LocalDateTime的表示形式:2021-01-17T19:33:28.139
ISO-8601國際標準日曆系統中帶有時區的日期時間。它存儲全部的日期和時間字段,精度爲納秒,以及一個時區,帶有用於處理不明確的本地日期時間的時區偏移量。
這個API能夠處理從LocalDateTime -> Instant -> ZonedDateTime
的轉換,其中用zone時區來表示偏移量(並不是直接用offset哦)。兩個時間點之間的轉換會涉及到使用從ZoneId訪問的規則計算偏移量(換句話說:偏移量並不是寫死而是根據規則計算出來的)。
獲取瞬間的偏移量很簡單,由於每一個瞬間只有一個有效的偏移量。可是,獲取本地日期時間的偏移量並不簡單。存在這三種狀況:
這三種狀況若是要本身處理,估計頭都大了。這就是使用JSR 310的優點,ZonedDateTime全幫你搞定,讓你使用無憂。
ZonedDateTime可簡單認爲是LocalDateTime和ZoneId的組合。而ZoneOffset是其內置的動態計算出來的一個次要信息,以確保輸出一個瞬時值而存在,畢竟在某個瞬間偏移量ZoneOffset確定是肯定的。ZonedDateTime也能夠理解爲保存的狀態至關於三個獨立的對象:LocalDateTime、ZoneId和ZoneOffset。某個瞬間 = LocalDateTime + ZoneOffset。ZoneId肯定了偏移量如何改變的規則。因此偏移量咱們並不能自由設置(不提供set方法,構造時也不行),由於它由ZoneId來控制的。
構造:
@Test public void test9() { System.out.println("當前位置偏移量的本地時間:" + ZonedDateTime.now()); System.out.println("紐約時區的本地時間:" + ZonedDateTime.of(LocalDateTime.now(), ZoneId.of("America/New_York"))); System.out.println("北京實現對應的紐約時區的本地時間:" + ZonedDateTime.now(ZoneId.of("America/New_York"))); } 輸出: 當前位置偏移量的本地時間:2021-01-17T19:25:10.520+08:00[Asia/Shanghai] 紐約時區的本地時間:2021-01-17T19:25:10.521-05:00[America/New_York] 北京實現對應的紐約時區的本地時間:2021-01-17T06:25:10.528-05:00[America/New_York]
計算:
略
格式化:
略
轉換:
LocalDateTime -> ZonedDateTime
@Test public void test10() { LocalDateTime localDateTime = LocalDateTime.of(2021, 01, 17, 18, 00, 00); System.out.println("當前時區(北京)時間爲:" + localDateTime); // 轉換爲偏移量爲 -4的OffsetDateTime時間 // 一、-4地方的晚上18點 System.out.println("紐約時區晚上18點:" + ZonedDateTime.of(localDateTime, ZoneId.of("America/New_York"))); System.out.println("紐約時區晚上18點(方式二):" + localDateTime.atZone(ZoneId.of("America/New_York"))); // 二、北京時間晚上18:00 對應的-4地方的時間點 System.out.println("北京地區此時間對應的紐約的時間:" + ZonedDateTime.ofInstant(localDateTime.toInstant(ZoneOffset.ofHours(8)), ZoneOffset.ofHours(-4))); System.out.println("北京地區此時間對應的紐約的時間:" + ZonedDateTime.ofInstant(localDateTime, ZoneOffset.ofHours(8), ZoneOffset.ofHours(-4))); } 輸出: 當前時區(北京)時間爲:2021-01-17T18:00 紐約時區晚上18點:2021-01-17T18:00-05:00[America/New_York] 紐約時區晚上18點(方式二):2021-01-17T18:00-05:00[America/New_York] 北京地區此時間對應的紐約的時間:2021-01-17T06:00-04:00 北京地區此時間對應的紐約的時間:2021-01-17T06:00-04:00
OffsetDateTime -> ZonedDateTime
@Test public void test101() { OffsetDateTime offsetDateTime = OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.ofHours(-4)); System.out.println("-4偏移量時間爲:" + offsetDateTime); // 轉換爲ZonedDateTime的表示形式 System.out.println("ZonedDateTime的表示形式:" + offsetDateTime.toZonedDateTime()); System.out.println("ZonedDateTime的表示形式:" + offsetDateTime.atZoneSameInstant(ZoneId.of("America/New_York"))); System.out.println("ZonedDateTime的表示形式:" + offsetDateTime.atZoneSimilarLocal(ZoneId.of("America/New_York"))); } -4偏移量時間爲:2021-01-17T19:43:28.320-04:00 ZonedDateTime的表示形式:2021-01-17T19:43:28.320-04:00 ZonedDateTime的表示形式:2021-01-17T18:43:28.320-05:00[America/New_York] ZonedDateTime的表示形式:2021-01-17T19:43:28.320-05:00[America/New_York]
本例有值得關注的點:
atZoneSameInstant()
:將此日期時間與時區結合起來建立ZonedDateTime,以確保結果具備相同的Instant
atZoneSimilarLocal
:將此日期時間與時區結合起來建立ZonedDateTime,以確保結果具備相同的本地時間
我這裏貼出紐約2021年的夏令時時間區間:
也就是說在2021.03.14 - 2021.11.07期間,紐約的偏移量是-4,其他時候是-5。那麼再看這個例子(我把時間改成5月5號,也就是處於夏令營期間):
@Test public void test101() { OffsetDateTime offsetDateTime = OffsetDateTime.of(LocalDateTime.of(2021, 05, 05, 18, 00, 00), ZoneOffset.ofHours(-4)); System.out.println("-4偏移量時間爲:" + offsetDateTime); // 轉換爲ZonedDateTime的表示形式 System.out.println("ZonedDateTime的表示形式:" + offsetDateTime.toZonedDateTime()); System.out.println("ZonedDateTime的表示形式:" + offsetDateTime.atZoneSameInstant(ZoneId.of("America/New_York"))); System.out.println("ZonedDateTime的表示形式:" + offsetDateTime.atZoneSimilarLocal(ZoneId.of("America/New_York"))); } 輸出: -4偏移量時間爲:2021-05-05T18:00-04:00 ZonedDateTime的表示形式:2021-05-05T18:00-04:00 ZonedDateTime的表示形式:2021-05-05T18:00-04:00[America/New_York] ZonedDateTime的表示形式:2021-05-05T18:00-04:00[America/New_York]
看到了吧,偏移量變爲了-4。感覺到夏令時的「威力」了吧。
LocalDateTime、OffsetDateTime、ZonedDateTime這三個哥們,LocalDateTime好理解,通常都沒有異議。可是不少同窗對OffsetDateTime和ZonedDateTime傻傻分不清,這裏說說它倆的區別。
總的來講,OffsetDateTime和ZonedDateTime的區別主要在於ZoneOffset和ZoneId的區別。若是你只是用來傳遞數據,請使用OffsetDateTime,若你想在特定時區裏作時間顯示那麼請務必使用ZonedDateTime。
本着拒絕淺嘗輒止的態度,深度剖析了不少同窗可能不太熟悉的OffsetDateTime、ZonedDateTime兩個API。總而言之,想要真正掌握日期時間體系(不限於Java語言,而是全部語言,甚至平常生活),對時區、偏移量的瞭解是繞不過去的砍,這塊知識有所欠缺的朋友可往前翻翻補補課。
最後在使用它們三的過程當中,有兩個提醒給你:
看完了不必定懂,看懂了不必定會。來,文末3個思考題幫你覆盤:
GMT UTC CST ISO 夏令時 時間戳,都是些什麼鬼?
分享、成長,拒絕淺嘗輒止。關注【BAT的烏托邦】回覆關鍵字專欄有Spring技術棧、中間件等小而美的純原創專欄。本文已被 https://www.yourbatman.cn 收錄。
本文所屬專欄:JDK日期時間,公號後臺回覆專欄名便可獲取所有內容。
A哥(YourBatman):Spring Framework/Boot開源貢獻者,Java架構師。很是注重基本功修養,相信底層基礎決定上層建築,堅實基礎才能煥發程序員更強生命力。文章特色爲以小而美專欄形式重構知識體系,抽絲剝繭,致力於作人人能看懂的最好的專欄系列。可加我好友(fsx1056342982)共勉哦!
System.out.println("點個贊吧!"); print_r('關注【BAT的烏托邦】!'); var_dump('點個贊吧!'); NSLog(@"關注【BAT的烏托邦】!"); console.log("點個贊吧!"); print("關注【BAT的烏托邦】!"); printf("點個贊吧!"); cout << "關注【BAT的烏托邦】!" << endl; Console.WriteLine("點個贊吧!"); fmt.Println("關注【BAT的烏托邦】!"); Response.Write("點個贊吧!"); alert("關注【BAT的烏托邦】!"); echo("點個贊吧!");