上一篇文章『Java 的時間日期 API』中,我們學習了由 Date、Calendar,DateFormat 等組成的「傳統時間日期 API」,但是傳統的處理接口設計並不是很友好,不易使用。終於,Java 8 借鑒第三方優秀開源庫 Joda-time,重新設計了一套 API。
那么本篇文章就來簡單學習一下新式的時間日期處理接口。
表示時刻的 Instant
Instant 和 Date 一樣,表示一個時間戳,用於描述一個時刻,只不過它較 Date 而言,可以描述更加精確的時刻。並且 Instant 是時區無關的。
Date 最多可以表示毫秒級別的時刻,而 Instant 可以表示納秒級別的時刻。例如:
- public static Instant now():根據系統當前時間創建一個 Instant 實例,表示當前時刻
- public static Instant ofEpochSecond(long epochSecond):通過傳入一個標准時間的偏移值來構建一個 Instant 實例
- public static Instant ofEpochMilli(long epochMilli):通過毫秒數值直接構建一個 Instant 實例
看看代碼:
public static void main(String[] args){
//創建 Instant 實例
Instant instant = Instant.now();
System.out.println(instant);
Instant instant1 = Instant.ofEpochSecond(20);
System.out.println(instant1);
Instant instant2 = Instant.ofEpochSecond(30,100);
System.out.println(instant2);
Instant instant3 = Instant.ofEpochMilli(1000);
System.out.println(instant3);
}
輸出結果:
2018-04-23T02:43:10.973Z
1970-01-01T00:00:20Z
1970-01-01T00:00:30.000000100Z
1970-01-01T00:00:01Z
可以看到,Instant 和 Date 不同的是,它是時區無關的,始終是格林零時區相關的,也即是輸出的結果始終格林零時區時間。
處理日期的 LocalDate
不同於 Calendar 既能處理日期又能處理時間,java.time 的新式 API 分離開日期和時間,用單獨的類進行處理。LocalDate 專注於處理日期相關信息。
LocalDate 依然是一個不可變類,它關注時間中年月日部分,我們可以通過以下的方法構建和初始化一個 LocalDate 實例:
- public static LocalDate now():截斷當前系統時間的年月日信息並初始化一個實例對象
- public static LocalDate of(int year, int month, int dayOfMonth):顯式指定年月日信息
- public static LocalDate ofYearDay(int year, int dayOfYear):根據 dayOfYear 可以推出 month 和 dayOfMonth
- public static LocalDate ofEpochDay(long epochDay):相對於格林零時區時間的日偏移量
看看代碼:
public static void main(String[] args){
//構建 LocalDate 實例
LocalDate localDate = LocalDate.now();
System.out.println(localDate);
LocalDate localDate1 = LocalDate.of(2017,7,22);
System.out.println(localDate1);
LocalDate localDate2 = LocalDate.ofYearDay(2018,100);
System.out.println(localDate2);
LocalDate localDate3 = LocalDate.ofEpochDay(10);
System.out.println(localDate3);
}
輸出結果:
2018-04-23
2017-07-22
2018-04-10
1970-01-11
需要注意一點,LocalDate 會根據系統中當前時刻和默認時區計算出年月日的信息。
除此之外,LocalDate 中還有大量關於日期的常用方法:
- public int getYear():獲取年份信息
- public int getMonthValue():獲取月份信息
- public int getDayOfMonth():獲取當前日是這個月的第幾天
- public int getDayOfYear():獲取當前日是這一年的第幾天
- public boolean isLeapYear():是否是閏年
- public int lengthOfYear():獲取這一年有多少天
- public DayOfWeek getDayOfWeek():返回星期信息
- 等等
這些方法都見名知意,此處不再贅述。
處理時間的 LocalTime
類似於 LocalDate,LocalTime 專注於時間的處理,它提供小時,分鍾,秒,毫微秒的各種處理,我們依然可以通過類似的方式創建一個 LocalTime 實例。
- public static LocalTime now():根據系統當前時刻獲取其中的時間部分內容
- public static LocalTime of(int hour, int minute):顯式傳入小時和分鍾來構建一個實例對象
- public static LocalTime of(int hour, int minute, int second):通過傳入時分秒構造實例
- public static LocalTime of(int hour, int minute, int second, int nanoOfSecond):傳入時分秒和毫微秒構建一個實例
- public static LocalTime ofSecondOfDay(long secondOfDay):傳入一個長整型數值代表當前日已經過去的秒數
- public static LocalTime ofNanoOfDay(long nanoOfDay):傳入一個長整型代表當前日已經過去的毫微秒數
同樣的,LocalTime 默認使用系統默認時區處理時間,看代碼:
public static void main(String[] a){
LocalTime localTime = LocalTime.now();
System.out.println(localTime);
LocalTime localTime1 = LocalTime.of(23,59);
System.out.println(localTime1);
LocalTime localTime2 = LocalTime.ofSecondOfDay(10);
System.out.println(localTime2);
}
輸出結果:
13:59:03.723
23:59
00:00:10
當然,LocalTime 中也同樣封裝了很多好用的工具方法,例如:
- public int getHour()
- public int getMinute()
- public int getSecond()
- public int getNano()
- public LocalTime withHour(int hour):修改當前 LocalTime 實例中的 hour 屬性並重新返回一個新的實例
- public LocalTime withMinute(int minute):類似
- public LocalTime withSecond(int second)
- 等等
LocalDateTime 類則是集成了 LocalDate 和 LocalTime,它既能表示日期,又能表述時間信息,方法都類似,只是有一部分涉及時區的轉換內容,我們待會說。
時區相關的日期時間處理 ZonedDateTime
無論是我們的 LocalDate,或是 LocalTime,甚至是 LocalDateTime,它們基本是時區無關的,內部並沒有存儲時區屬性,而基本用的系統默認時區。往往有些場景之下,缺乏一定的靈活性。
ZonedDateTime 可以被理解為 LocalDateTime 的外層封裝,它的內部存儲了一個 LocalDateTime 的實例,專門用於普通的日期時間處理。此外,它還定義了 ZoneId 和 ZoneOffset 來描述時區的概念。
ZonedDateTime 和 LocalDateTime 的一個很大的不同點在於,后者內部並沒有存儲時區,所以對於系統的依賴性很強,往往換一個時區可能就會導致程序中的日期時間不一致。
而后者則可以通過傳入時區的名稱,使用 ZoneId 進行匹配存儲,也可以通過傳入與零時區的偏移量,使用 ZoneOffset 存儲時區信息。
所以,構建一個 ZonedDateTime 實例有以下幾種方式:
- public static ZonedDateTime now():系統將以默認時區計算並存儲日期時間信息
- public static ZonedDateTime now(ZoneId zone):指定時區
- public static ZonedDateTime of(LocalDate date, LocalTime time, ZoneId zone):指定日期時間和時區
- public static ZonedDateTime of(LocalDateTime localDateTime, ZoneId zone)
- public static ZonedDateTime ofInstant(Instant instant, ZoneId zone):通過時刻和時區構建實例對象
- 等等
看代碼:
public static void main(String[] a){
ZonedDateTime zonedDateTime = ZonedDateTime.now();
System.out.println(zonedDateTime);
LocalDateTime localDateTime = LocalDateTime.now();
ZoneId zoneId = ZoneId.of("America/Los_Angeles");
ZonedDateTime zonedDateTime1 = ZonedDateTime.of(localDateTime,zoneId);
System.out.println(zonedDateTime1);
Instant instant = Instant.now();
ZoneId zoneId1 = ZoneId.of("GMT");
ZonedDateTime zonedDateTime2 = ZonedDateTime.ofInstant(instant,zoneId1);
System.out.println(zonedDateTime2);
}
輸出結果:
2018-04-23T16:10:29.510+08:00[Asia/Shanghai]
2018-04-23T16:10:29.511-07:00[America/Los_Angeles]
2018-04-23T08:10:29.532Z[GMT]
簡單解釋一下,首先第一個輸出應該沒什么問題,系統保存當前系統日期和時間以及默認的時區。
第二個小例子,LocalDateTime 實例保存了時區無關的當前日期時間信息,也就是這里的年月日時分秒,接着構建一個 ZonedDateTime 實例並傳入一個美國時區(西七區)。你會發現輸出的日期時間為西七區的 16 點 29 分。
像這種關聯了時區的日期時間就很能夠解決那種,換時區導致程序中時間錯亂的問題。因為我關聯了時區,無論你程序換到什么地方運行了,日期+時區 本就已經唯一確定了某個時刻,就相當於我在存儲某個時刻的時候,我說明了這是某某時區的某某時間,即便你換了一個地區,你也不至於把這個時間按自己當前的時區進行解析並直接使用了吧。
第三個小例子就更加的直接明了了,構建 ZonedDateTime 實例的時候,給定一個時刻和一個時區,而這個時刻值就是相對於給定時區的標准時間所經過的毫秒數。
有關 ZonedDateTime 的其他日期時間的處理方法和 LocalDateTime 是一樣的,因為 ZonedDateTime 是直接封裝了一個 LocalDateTime 實例對象,所以所有相關日期時間的操作都會間接的調用 LocalDateTime 實例的方法,我們不再贅述。
格式化日期時間
Java 8 的新式日期時間 API 中,DateTimeFormatter 作為格式化日期時間的主要類,它與之前的 DateFormat 類最大的不同就在於它是線程安全的,其他的使用上的操作基本類似。我們看看:
public static void main(String[] a){
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(formatter.format(localDateTime));
String str = "2008年08月23日 23:59:59";
DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
LocalDateTime localDateTime2 = LocalDateTime.parse(str,formatter2);
System.out.println(localDateTime2);
}
輸出結果:
2018年04月23日 17:27:24
2008-08-23T23:59:59
格式化主要有兩種情況,一種是將日期時間格式化成字符串,另一種則是將格式化的字符串裝換成日期時間對象。
DateTimeFormatter 提供將 format 方法將一個日期時間對象轉換成格式化的字符串,但是反過來的操作卻建議使用具體的日期時間類自己的 parse 方法,這樣可以省去類型轉換的步驟。
時間差
現實項目中,我們也經常會遇到計算兩個時間點之間的差值的情況,最粗暴的辦法是,全部幻化成毫秒數並進行減法運算,最后在轉換回日期時間對象。
但是 java.time 包中提供了兩個日期時間之間的差值的計算方法,我們一起看看。
關於時間差的計算,主要涉及到兩個類:
- Period:處理兩個日期之間的差值
- Duration:處理兩個時間之間的差值
例如:
public static void main(String[] args){
LocalDate date = LocalDate.of(2017,7,22);
LocalDate date1 = LocalDate.now();
Period period = Period.between(date,date1);
System.out.println(period.getYears() + "年" +
period.getMonths() + "月" +
period.getDays() + "天");
LocalTime time = LocalTime.of(20,30);
LocalTime time1 = LocalTime.of(23,59);
Duration duration = Duration.between(time,time1);
System.out.println(duration.toMinutes() + "分鍾");
}
輸出結果:
0年9月1天
209分鍾
顯然,年月日的日期間差值的計算使用 Period 類足以,而時分秒毫秒的時間的差值計算則需要使用 Duration 類。
最后,關於 java.time 包下的新式日期時間 API,我們簡單的學習了下,並沒有深入到源碼實現層次進行介紹,因為底層涉及大量的系統接口,涉及到大量的抽象類和實現類,有興趣的朋友可以自行閱讀 jdk 的源碼深入學習。
文章中的所有代碼、圖片、文件都雲存儲在我的 GitHub 上:
(https://github.com/SingleYam/overview_java)
歡迎關注微信公眾號:撲在代碼上的高爾基,所有文章都將同步在公眾號上。