1. 程式人生 > 程式設計 >20 個案例教你在 Java 8 中如何處理日期和時間?

20 個案例教你在 Java 8 中如何處理日期和時間?

前言

前面一篇文章寫了《SimpleDateFormat 如何安全的使用?》,裡面介紹了 SimpleDateFormat 如何處理日期/時間,以及如何保證執行緒安全,及其介紹了在 Java 8 中的處理時間/日期預設就執行緒安全的 DateTimeFormatter 類。那麼 Java 8 中該怎麼樣處理生活中常見的一些日期/時間呢?比如:計算一週後的日期;計算一年前或一年後的日期;檢查閏年等。

接下來建立了 20 個基於任務的例項來學習 Java 8 的新特性。從最簡單建立當天的日期開始,然後建立時間及時區,接著模擬一個日期提醒應用中的任務——計算重要日期的到期天數,例如生日、紀念日、賬單日、保費到期日、信用卡過期日等。

示例 1、在 Java 8 中獲取今天的日期

Java 8 中的 LocalDate 用於表示當天日期。和 java.util.Date 不同,它只有日期,不包含時間。當你僅需要表示日期時就用這個類。

1LocalDate now = LocalDate.now();
2System.out.println(now);複製程式碼

結果是:

12018-06-20複製程式碼

上面的程式碼建立了當天的日期,不含時間資訊。打印出的日期格式非常友好,不像老的 Date 類打印出一堆沒有格式化的資訊。

示例 2、在 Java 8 中獲取年、月、日資訊

LocalDate 類提供了獲取年、月、日的快捷方法,其例項還包含很多其它的日期屬性。通過呼叫這些方法就可以很方便的得到需要的日期資訊,不用像以前一樣需要依賴 java.util.Calendar 類了

1LocalDate now = LocalDate.now();
2int year = now.getYear();
3int monthValue = now.getMonthValue();
4int dayOfMonth = now.getDayOfMonth();
5System.out.printf("year = %d,month = %d,day = %d",year,monthValue,dayOfMonth);複製程式碼

結果是:

1year = 2018,month = 6,day = 20複製程式碼

示例 3、在 Java 8 中處理特定日期

在第一個例子裡,我們通過靜態工廠方法 now() 非常容易地建立了當天日期,你還可以呼叫另一個有用的工廠方法LocalDate.of() 建立任意日期, 該方法需要傳入年、月、日做引數,返回對應的 LocalDate 例項。這個方法的好處是沒再犯老 API 的設計錯誤,比如年度起始於 1900,月份是從 0 開始等等。日期所見即所得,就像下面這個例子表示了 6 月 20 日,沒有任何隱藏機關。

1LocalDate date = LocalDate.of(2018,06,20);
2System.out.println(date);複製程式碼

可以看到建立的日期完全符合預期,與寫入的 2018 年 6 月 20 日完全一致。

示例 4、在 Java 8 中判斷兩個日期是否相等

現實生活中有一類時間處理就是判斷兩個日期是否相等。你常常會檢查今天是不是個特殊的日子,比如生日、紀念日或非交易日。這時就需要把指定的日期與某個特定日期做比較,例如判斷這一天是否是假期。下面這個例子會幫助你用 Java 8 的方式去解決,你肯定已經想到了,LocalDate 過載了 equal 方法,請看下面的例子:

1LocalDate now = LocalDate.now();
2LocalDate date = LocalDate.of(2018,20);
3if (date.equals(now)) {
4    System.out.println("同一天");
5}複製程式碼

這個例子中我們比較的兩個日期相同。注意,如果比較的日期是字元型的,需要先解析成日期物件再作判斷。

示例 5、在 Java 8 中檢查像生日這種週期性事件

Java 中另一個日期時間的處理就是檢查類似每月賬單、結婚紀念日、EMI日或保險繳費日這些週期性事件。如果你在電子商務網站工作,那麼一定會有一個模組用來在聖誕節、感恩節這種節日時向客戶傳送問候郵件。Java 中如何檢查這些節日或其它週期性事件呢?答案就是 MonthDay 類。這個類組合了月份和日,去掉了年,這意味著你可以用它判斷每年都會發生事件。和這個類相似的還有一個 YearMonth 類。這些類也都是不可變並且執行緒安全的值型別。下面我們通過 MonthDay 來檢查週期性事件:

1LocalDate now = LocalDate.now();
2LocalDate dateOfBirth = LocalDate.of(2018,20);
3MonthDay birthday = MonthDay.of(dateOfBirth.getMonth(),dateOfBirth.getDayOfMonth());
4MonthDay currentMonthDay = MonthDay.from(now);
5if (currentMonthDay.equals(birthday)) {
6    System.out.println("Happy Birthday");
7} else {
8    System.out.println("Sorry,today is not your birthday");
9}複製程式碼

結果:(注意:獲取當前時間可能與你看的時候不對,所以這個結果可能和你看的時候執行結果不一樣)

1Happy Birthday複製程式碼

只要當天的日期和生日匹配,無論是哪一年都會打印出祝賀資訊。你可以把程式整合進系統時鐘,看看生日時是否會受到提醒,或者寫一個單元測試來檢測程式碼是否執行正確。

示例 6、在 Java 8 中獲取當前時間

與 Java 8 獲取日期的例子很像,獲取時間使用的是 LocalTime 類,一個只有時間沒有日期的 LocalDate 近親。可以呼叫靜態工廠方法 now() 來獲取當前時間。預設的格式是 hh:mm:ss:nnn。

1LocalTime localTime = LocalTime.now();
2System.out.println(localTime);複製程式碼

結果:

113:35:56.155複製程式碼

可以看到當前時間就只包含時間資訊,沒有日期。

示例 7、如何在現有的時間上增加小時

通過增加小時、分、秒來計算將來的時間很常見。Java 8 除了不變型別和執行緒安全的好處之外,還提供了更好的plusHours() 方法替換 add(),並且是相容的。注意,這些方法返回一個全新的 LocalTime 例項,由於其不可變性,返回後一定要用變數賦值。

1LocalTime localTime = LocalTime.now();
2System.out.println(localTime);
3LocalTime localTime1 = localTime.plusHours(2);//增加2小時
4System.out.println(localTime1);複製程式碼

結果:

113:41:20.721
215:41:20.721複製程式碼

可以看到,新的時間在當前時間 13:41:20.721 的基礎上增加了 2 個小時。

示例 8、如何計算一週後的日期

和上個例子計算兩小時以後的時間類似,這個例子會計算一週後的日期。LocalDate 日期不包含時間資訊,它的 plus()方法用來增加天、周、月,ChronoUnit 類宣告瞭這些時間單位。由於 LocalDate 也是不變型別,返回後一定要用變數賦值。

1LocalDate now = LocalDate.now();
2LocalDate plusDate = now.plus(1,ChronoUnit.WEEKS);
3System.out.println(now);
4System.out.println(plusDate);複製程式碼

結果:

12018-06-20
22018-06-27複製程式碼

可以看到新日期離當天日期是 7 天,也就是一週。你可以用同樣的方法增加 1 個月、1 年、1 小時、1 分鐘甚至一個世紀,更多選項可以檢視 Java 8 API 中的 ChronoUnit 類。

示例 9、計算一年前或一年後的日期

繼續上面的例子,上個例子中我們通過 LocalDate 的 plus() 方法增加天數、週數或月數,這個例子我們利用 minus() 方法計算一年前的日期。

1LocalDate now = LocalDate.now();
2LocalDate minusDate = now.minus(1,ChronoUnit.YEARS);
3LocalDate plusDate1 = now.plus(1,ChronoUnit.YEARS);
4System.out.println(minusDate);
5System.out.println(plusDate1);複製程式碼

結果:

12017-06-20
22019-06-20複製程式碼

示例 10、使用 Java 8 的 Clock 時鐘類

Java 8 增加了一個 Clock 時鐘類用於獲取當時的時間戳,或當前時區下的日期時間資訊。以前用到System.currentTimeInMillis() 和 TimeZone.getDefault() 的地方都可用 Clock 替換。

1Clock clock = Clock.systemUTC();
2Clock clock1 = Clock.systemDefaultZone();
3System.out.println(clock);
4System.out.println(clock1);複製程式碼

結果:

1SystemClock[Z]
2SystemClock[Asia/Shanghai]複製程式碼

示例 11、如何用 Java 判斷日期是早於還是晚於另一個日期

另一個工作中常見的操作就是如何判斷給定的一個日期是大於某天還是小於某天?在 Java 8 中,LocalDate 類有兩類方法 isBefore() 和 isAfter() 用於比較日期。呼叫 isBefore() 方法時,如果給定日期小於當前日期則返回 true。

1 LocalDate tomorrow = LocalDate.of(2018,6,20);
2 if(tomorrow.isAfter(now)){
3     System.out.println("Tomorrow comes after today");
4 }
5 LocalDate yesterday = now.minus(1,ChronoUnit.DAYS);
6 if(yesterday.isBefore(now)){
7     System.out.println("Yesterday is day before today");
8 }複製程式碼

在 Java 8 中比較日期非常方便,不需要使用額外的 Calendar 類來做這些基礎工作了。

示例 12、在 Java 8 中處理時區

Java 8 不僅分離了日期和時間,也把時區分離出來了。現在有一系列單獨的類如 ZoneId 來處理特定時區,ZoneDateTime 類來表示某時區下的時間。這在 Java 8 以前都是 GregorianCalendar 類來做的。

1ZoneId america = ZoneId.of("America/New_York");
2LocalDateTime localtDateAndTime = LocalDateTime.now();
3ZonedDateTime dateAndTimeInNewYork  = ZonedDateTime.of(localtDateAndTime,america );
4System.out.println(dateAndTimeInNewYork);複製程式碼

示例 13、如何表示信用卡到期這類固定日期,答案就在 YearMonth

與 MonthDay 檢查重複事件的例子相似,YearMonth 是另一個組合類,用於表示信用卡到期日、FD 到期日、期貨期權到期日等。還可以用這個類得到 當月共有多少天,YearMonth 例項的 lengthOfMonth() 方法可以返回當月的天數,在判斷 2 月有 28 天還是 29 天時非常有用。

1YearMonth currentYearMonth = YearMonth.now();
2System.out.printf("Days in month year %s: %d%n",currentYearMonth,currentYearMonth.lengthOfMonth());
3YearMonth creditCardExpiry = YearMonth.of(2018,Month.FEBRUARY);
4System.out.printf("Your credit card expires on %s %n",creditCardExpiry);複製程式碼

結果:

1Days in month year 2018-06: 30
2Your credit card expires on 2018-02複製程式碼

示例 14、如何在 Java 8 中檢查閏年

LocalDate 類有一個很實用的方法 isLeapYear() 判斷該例項是否是一個閏年。

示例 15、計算兩個日期之間的天數和月數

有一個常見日期操作是計算兩個日期之間的天數、週數或月數。在 Java 8 中可以用 java.time.Period 類來做計算。下面這個例子中,我們計算了當天和將來某一天之間的月數。

1LocalDate date = LocalDate.of(2019,Month.MARCH,20);
2Period period = Period.between(now,date);
3System.out.println("離下個時間還有" + period.getMonths() + " 個月");複製程式碼

示例 16、包含時差資訊的日期和時間

在 Java 8 中,ZoneOffset 類用來表示時區,舉例來說印度與 GMT 或 UTC 標準時區相差 +05:30,可以通過ZoneOffset.of() 靜態方法來獲取對應的時區。一旦得到了時差就可以通過傳入 LocalDateTime 和 ZoneOffset 來建立一個 OffSetDateTime 物件。

1LocalDateTime datetime = LocalDateTime.of(2014,Month.JANUARY,14,19,30);
2ZoneOffset offset = ZoneOffset.of("+05:30");
3OffsetDateTime date = OffsetDateTime.of(datetime,offset);  
4System.out.println("Date and Time with timezone offset in Java : " + date);複製程式碼

示例 17、在 Java 8 中獲取當前的時間戳

如果你還記得 Java 8 以前是如何獲得當前時間戳,那麼現在你終於解脫了。Instant 類有一個靜態工廠方法 now() 會返回當前的時間戳,如下所示:

1Instant timestamp = Instant.now();
2System.out.println(timestamp);複製程式碼

結果:

12018-06-20T06:35:24.881Z複製程式碼

時間戳資訊裡同時包含了日期和時間,這和 java.util.Date 很像。實際上 Instant 類確實等同於 Java 8 之前的 Date類,你可以使用 Date 類和 Instant 類各自的轉換方法互相轉換,例如:Date.from(Instant) 將 Instant 轉換成java.util.Date,Date.toInstant() 則是將 Date 類轉換成 Instant 類。

示例 18、在 Java 8 中如何使用預定義的格式化工具去解析或格式化日期

在 Java 8 以前的世界裡,日期和時間的格式化非常詭異,唯一的幫助類 SimpleDateFormat 也是非執行緒安全的,而且用作區域性變數解析和格式化日期時顯得很笨重。幸好執行緒區域性變數能使它在多執行緒環境中變得可用,不過這都是過去時了。Java 8 引入了全新的日期時間格式工具,執行緒安全而且使用方便。它自帶了一些常用的內建格式化工具。

參見我上一篇文章: 《SimpleDateFormat 如何安全的使用?》

示例 19、如何在 Java 中使用自定義格式化工具解析日期

儘管內建格式化工具很好用,有時還是需要定義特定的日期格式。可以呼叫 DateTimeFormatter 的 ofPattern() 靜態方法並傳入任意格式返回其例項,格式中的字元和以前代表的一樣,M 代表月,m 代表分。如果格式不規範會丟擲 DateTimeParseException 異常,不過如果只是把 M 寫成 m 這種邏輯錯誤是不會拋異常的。

參見我上一篇文章: 《SimpleDateFormat 如何安全的使用?》

示例 20、在 Java 8 中如何把日期轉換成字串

上兩個主要是從字串解析日期。現在我們反過來,把 LocalDateTime 日期例項轉換成特定格式的字串。這是迄今為止 Java 日期轉字串最為簡單的方式了。下面的例子將返回一個代表日期的格式化字串。和前面類似,還是需要建立 DateTimeFormatter 例項並傳入格式,但這回呼叫的是 format() 方法,而非 parse() 方法。這個方法會把傳入的日期轉化成指定格式的字串。

1LocalDateTime arrivalDate  = LocalDateTime.now();
2try {
3    DateTimeFormatter format = DateTimeFormatter.ofPattern("MMMdd yyyy  hh:mm a");
4    String landing = arrivalDate.format(format);
5    System.out.printf("Arriving at :  %s %n",landing);
6}catch (DateTimeException ex) {
7    System.out.printf("%s can't be formatted!%n",arrivalDate);
8    ex.printStackTrace();
9}複製程式碼

Java 8 日期時間 API 的重點

通過這些例子,你肯定已經掌握了 Java 8 日期時間 API 的新知識點。現在來回顧一下這個優雅 API 的使用要點:

1)提供了 javax.time.ZoneId 獲取時區。

2)提供了 LocalDate 和 LocalTime 類。

3)Java 8 的所有日期和時間 API 都是不可變類並且執行緒安全,而現有的 Date 和 Calendar API 中的 java.util.Date 和SimpleDateFormat 是非執行緒安全的。

4)主包是 java.time,包含了表示日期、時間、時間間隔的一些類。裡面有兩個子包 java.time.format 用於格式化, java.time.temporal 用於更底層的操作。

5)時區代表了地球上某個區域內普遍使用的標準時間。每個時區都有一個代號,格式通常由區域/城市構成(Asia/Tokyo),在加上與格林威治或 UTC 的時差。例如:東京的時差是 +09:00。

6)OffsetDateTime 類實際上組合了 LocalDateTime 類和 ZoneOffset 類。用來表示包含和格林威治或 UTC 時差的完整日期(年、月、日)和時間(時、分、秒、納秒)資訊。

7)DateTimeFormatter 類用來格式化和解析時間。與 SimpleDateFormat 不同,這個類不可變並且執行緒安全,需要時可以給靜態常量賦值。DateTimeFormatter 類提供了大量的內建格式化工具,同時也允許你自定義。在轉換方面也提供了 parse() 將字串解析成日期,如果解析出錯會丟擲 DateTimeParseException。DateTimeFormatter 類同時還有format() 用來格式化日期,如果出錯會丟擲 DateTimeException異常。

8)再補充一點,日期格式“MMM d yyyy”和“MMM dd yyyy”有一些微妙的不同,第一個格式可以解析“Jan 2 2014”和“Jan 14 2014”,而第二個在解析“Jan 2 2014”就會拋異常,因為第二個格式裡要求日必須是兩位的。如果想修正,你必須在日期只有個位數時在前面補零,就是說“Jan 2 2014”應該寫成 “Jan 02 2014”。

推薦閱讀:

《深入理解 Java 記憶體模型》讀書筆記

面試-基礎篇

Spring Boot 2.0 遷移指南

SpringBoot使用Docker快速部署專案

為什麼選擇 Spring 作為 Java 框架?

SpringBoot RocketMQ 整合使用和監控

Spring Boot 面試的十個問題

使用 Spring Framework 時常犯的十大錯誤

SpringBoot Admin 使用指南

SpringBoot Kafka 整合使用

SpringBoot RabbitMQ 整合使用

Elasticsearch索引增量統計及定時郵件實現

Elasticsearch實戰 | 必要的時候,還得空間換時間!乾貨 |《從Lucene到Elasticsearch全文檢索實戰》拆解實踐

上篇好文:

JVM面試問題系列:JVM 配置常用引數和常用 GC 調優策略