Java 8 的時間日期 API


上一篇文章『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)

歡迎關注微信公眾號:撲在代碼上的高爾基,所有文章都將同步在公眾號上。

image


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM