JAVA核心知識點--JDK1.8中的日期處理

目錄

在Java 8中如何處理日期和時間

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

示例 2、在Java 8中獲取年、月、日信息

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

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

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

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

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

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

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

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

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

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

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

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

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

示例 16、包含時差信息的日期和時間

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

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

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

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

Java 8日期時間API的重點


伴隨lambda表達式、streams以及一系列小優化,Java 8 推出了全新的日期時間API,在教程中我們將通過一些簡單的實例來學習如何使用新API。Java處理日期、日曆和時間的方式一直爲社區所詬病,將 java.util.Date設定爲可變類型,以及SimpleDateFormat的非線程安全使其應用非常受限。Java也意識到需要一個更好的 API來滿足社區中已經習慣了使用JodaTime API的人們。全新API的衆多好處之一就是,明確了日期時間概念,例如:瞬時(instant)、 長短(duration)、日期、時間、時區和週期。同時繼承了Joda庫按人類語言和計算機各自解析的時間處理方式。不同於老版本,新API基於ISO標準日曆系統,java.time包下的所有類都是不可變類型而且線程安全。下面是新版API中java.time包裏的一些關鍵類:

  • Instant:瞬時實例。

  • LocalDate:本地日期,不包含具體時間 例如:2014-01-14 可以用來記錄生日、紀念日、加盟日等。

  • LocalTime:本地時間,不包含日期。 LocalDateTime:組合了日期和時間,但不包含時差和時區信息。

  • ZonedDateTime:最完整的日期時間,包含時區和相對UTC或格林威治的時差。

新API還引入了ZoneOffSet和ZoneId類,使得解決時區問題更爲簡便。解析、格式化時間的DateTimeFormatter類也全部重新設 計。注意,這篇文章是我在一年前Java 8即將發佈時寫的,以下示例代碼中的時間都是那一年的,當運行這些例子時會返回你當前的時間。

在Java 8中如何處理日期和時間

常有人問我學習一個新庫的最好方式是什麼?我的答案是在實際項目中使用它。項目中有很多真正的需求驅使開發者去發掘並學習新庫。簡單得說就是任務驅動學習探 索。這對Java 8新日期時間API也不例外。我創建了20個基於任務的實例來學習Java 8的新特性。從最簡單創建當天的日期開始,然後創建時間及時區,接着模擬一個日期提醒應用中的任務——計算重要日期的到期天數,例如生日、紀念日、賬單 日、保費到期日、信用卡過期日等。

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

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

  1. LocalDate today = LocalDate.now();
  2. System.out.println("Today's Local date : " + today);

輸出:

Today's Local date : 2014-01-14

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

示例 2、在Java 8中獲取年、月、日信息

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

  1. LocalDate today = LocalDate.now();
  2. int year = today.getYear();
  3. int month = today.getMonthValue();
  4. int day = today.getDayOfMonth();
  5. System.out.printf("Year : %d  Month : %d  day : %d t %n", year, month, day);

輸出:

  1. Today's Local date : 2014-01-14
  2. Year : 2014  Month : 1  day : 14

看到了吧,在Java 8 中得到年、月、日信息是這麼簡單直觀,想用就用,沒什麼需要記的。對比看看以前Java是怎麼處理年月日信息的吧。

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

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

  1. LocalDate dateOfBirth = LocalDate.of(2010, 01, 14);
  2. System.out.println("Your Date of birth is : " + dateOfBirth);

輸出

Output : Your Date of birth is : 2010-01-14

可以看到創建的日期完全符合預期,與你寫入的2010年1月14日完全一致。

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

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

  1. LocalDate date1 = LocalDate.of(2014, 01, 14);
  2. if(date1.equals(today)){
  3.     System.out.printf("Today %s and date1 %s are same date %n", today, date1);
  4. }

輸出:

today 2014-01-14 and date1 2014-01-14 are same date

這個例子中我們比較的兩個日期相同。注意,如果比較的日期是字符型的,需要先解析成日期對象再作判斷。對比Java老的日期比較方式,你會感到清風拂面。

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

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

  1. LocalDate dateOfBirth = LocalDate.of(2010, 01, 14);
  2. MonthDay birthday = MonthDay.of(dateOfBirth.getMonth(), dateOfBirth.getDayOfMonth());
  3. MonthDay currentMonthDay = MonthDay.from(today);
  4. if(currentMonthDay.equals(birthday)){
  5.    System.out.println("Many Many happy returns of the day !!");
  6. }else{
  7.    System.out.println("Sorry, today is not your birthday");
  8. }

輸出:

Many Many happy returns of the day !!

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

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

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

  1. LocalTime time = LocalTime.now();
  2. System.out.println("local time now : " + time);

輸出:

local time now : 16:33:33.369  // in hour, minutes, seconds, nano seconds

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

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

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

  1. LocalTime time = LocalTime.now();
  2. LocalTime newTime = time.plusHours(2); // adding two hours
  3. System.out.println("Time after 2 hours : " +  newTime);

輸出:

Time after 2 hours : 18:33:33.369

可以看到,新的時間在當前時間16:33:33.369的基礎上增加了2個小時。和舊版Java的增減時間的處理方式對比一下,看看哪種更好。

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

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

  1. LocalDate nextWeek = today.plus(1, ChronoUnit.WEEKS);
  2. System.out.println("Today is : " + today);
  3. System.out.println("Date after 1 week : " + nextWeek);

輸出:

  1. Today is : 2014-01-14
  2. Date after 1 week : 2014-01-21

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

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

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

  1. LocalDate previousYear = today.minus(1, ChronoUnit.YEARS);
  2. System.out.println("Date before 1 year : " + previousYear);
  3. LocalDate nextYear = today.plus(1, YEARS);
  4. System.out.println("Date after 1 year : " + nextYear);

輸出:

  1. Date before 1 year : 2013-01-14
  2. Date after 1 year : 2015-01-14

例子結果得到了兩個日期,一個2013年、一個2015年、分別是2014年的前一年和後一年。

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

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

  1. // Returns the current time based on your system clock and set to UTC.
  2. Clock clock = Clock.systemUTC();
  3. System.out.println("Clock : " + clock);
  4. // Returns time based on system clock zone
  5. Clock defaultClock = Clock.systemDefaultZone();
  6. System.out.println("Clock : " + clock);

輸出:

  1. Clock : SystemClock[Z]
  2. Clock : SystemClock[Z]

還可以針對clock時鐘做比較,像下面這個例子:

  1. public class MyClass {
  2.     private Clock clock;  // dependency inject
  3.     ...
  4.     public void process(LocalDate eventDate) {
  5.       if (eventDate.isBefore(LocalDate.now(clock)) {
  6.         ...
  7.       }
  8.     }
  9. }

這種方式在不同時區下處理日期時會非常管用。

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

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

  1. LocalDate tomorrow = LocalDate.of(2014, 1, 15);
  2. if(tommorow.isAfter(today)){
  3.     System.out.println("Tomorrow comes after today");
  4. }
  5. LocalDate yesterday = today.minus(1, DAYS);
  6. if(yesterday.isBefore(today)){
  7.     System.out.println("Yesterday is day before today");
  8. }

輸出:

  1. Tomorrow comes after today
  2. Yesterday is day before today

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

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

Java 8不僅分離了日期和時間,也把時區分離出來了。現在有一系列單獨的類如ZoneId來處理特定時區,ZoneDateTime類來表示某時區下的時間。這在Java 8以前都是 GregorianCalendar類來做的。下面這個例子展示瞭如何把本時區的時間轉換成另一個時區的時間。

  1. // Date and time with timezone in Java 8
  2. ZoneId america = ZoneId.of("America/New_York");
  3. LocalDateTime localtDateAndTime = LocalDateTime.now();
  4. ZonedDateTime dateAndTimeInNewYork  = ZonedDateTime.of(localtDateAndTime, america );
  5. System.out.println("Current date and time in a particular timezone : " + dateAndTimeInNewYork);

輸出:

Current date and time in a particular timezone : 2014-01-14T16:33:33.373-05:00[America/New_York]

和以前使用GMT的方式轉換本地時間對比一下。注意,在Java 8以前,一定要牢牢記住時區的名稱,不然就會拋出下面的異常:

  1. Exception in thread "main" java.time.zone.ZoneRulesException: Unknown time-zone ID: ASIA/Tokyo
  2.         at java.time.zone.ZoneRulesProvider.getProvider(ZoneRulesProvider.java:272)
  3.         at java.time.zone.ZoneRulesProvider.getRules(ZoneRulesProvider.java:227)
  4.         at java.time.ZoneRegion.ofId(ZoneRegion.java:120)
  5.         at java.time.ZoneId.of(ZoneId.java:403)
  6.         at java.time.ZoneId.of(ZoneId.java:351)

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

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

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

輸出:

  1. Days in month year 2014-01: 31
  2. Your credit card expires on 2018-02

根據上述數據,你可以提醒客戶信用卡快要到期了,個人認爲這個類非常有用。

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

LocalDate類有一個很實用的方法isLeapYear()判斷該實例是否是一個閏年,如果你還是想重新發明輪子,這有一個代碼示例,純Java邏輯編寫的判斷閏年的程序。

  1. if(today.isLeapYear()){
  2.    System.out.println("This year is Leap year");
  3. }else {
  4.     System.out.println("2014 is not a Leap year");
  5. }

輸出:

2014 is not a Leap year

你可以多寫幾個日期來驗證是否是閏年,最好是寫JUnit單元測試做判斷。

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

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

  1. LocalDate java8Release = LocalDate.of(2014, Month.MARCH, 14);
  2. Period periodToNextJavaRelease = Period.between(today, java8Release);
  3. System.out.println("Months left between today and Java 8 release : "
  4.                                    + periodToNextJavaRelease.getMonths() );

輸出:

Months left between today and Java 8 release : 2

從上面可以看到現在是一月,Java 8的發佈日期是3月,中間相隔兩個月。

示例 16、包含時差信息的日期和時間

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

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

輸出:

Date and Time with timezone offset in Java : 2014-01-14T19:30+05:30

現在的時間信息裏已經包含了時區信息了。注意:OffSetDateTime是對計算機友好的,ZoneDateTime則對人更友好。

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

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

  1. Instant timestamp = Instant.now();
  2. System.out.println("What is value of this instant " + timestamp);

輸出:

What is value of this instant 2014-01-14T08:33:33.379Z

時間戳信息裏同時包含了日期和時間,這和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引入了全新的日期時間格式工具,線程安全而且使用方便。它自帶了一些常用的內置格式化工具。下面這個例子使用了BASIC_ISO_DATE格式化工具將2014年1月14日格式化成20140114。

  1. String dayAfterTommorrow = "20140116";
  2. LocalDate formatted = LocalDate.parse(dayAfterTommorrow,
  3.                         DateTimeFormatter.BASIC_ISO_DATE);
  4. System.out.printf("Date generated from String %s is %s %n",
  5.                     dayAfterTommorrow, formatted);

輸出:

Date generated from String 20140116 is 2014-01-16

很明顯的看出得到的日期和給出的日期是同一天,但是格式不同。

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

上 個例子使用了Java內置的格式化工具去解析日期字符串。儘管內置格式化工具很好用,有時還是需要定義特定的日期格式,下面這個例子展示瞭如何創建自定義 日期格式化工具。例子中的日期格式是“MMM dd yyyy”。可以調用DateTimeFormatter的 ofPattern()靜態方法並傳入任意格式返回其實例,格式中的字符和以前代表的一樣,M 代表月,m代表分。如果格式不規範會拋出 DateTimeParseException異常,不過如果只是把M寫成m這種邏輯錯誤是不會拋異常的。

  1. String goodFriday = "Apr 18 2014";
  2. try {
  3.     DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMM dd yyyy");
  4.     LocalDate holiday = LocalDate.parse(goodFriday, formatter);
  5.     System.out.printf("Successfully parsed String %s, date is %s%n", goodFriday, holiday);
  6. } catch (DateTimeParseException ex) {
  7.     System.out.printf("%s is not parsable!%n", goodFriday);
  8.     ex.printStackTrace();
  9. }

輸出:

Successfully parsed String Apr 18 2014, date is 2014-04-18

日期值與傳入的字符串是匹配的,只是格式不同而已。

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

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

  1. LocalDateTime arrivalDate  = LocalDateTime.now();
  2. try {
  3.     DateTimeFormatter format = DateTimeFormatter.ofPattern("MMM dd 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. }

輸出:

Arriving atJan 14 2014  04:33 PM

當前時間被指定的“MMM dd yyyy hh:mm a”格式格式化,格式包含3個代表月的字符串,時間後面帶有AM和PM標記。

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 8的全新日期時間API就介紹到這了。這些簡單的例子對幫助理解新API非常有用。由於這些例子都基於真實任務,你在做Java日期編程時不用再東張西望了。我們學會了如何創建並操作日期實例,學習了純日期、以及包含時間信息和時差信息的日期、學會了怎樣計算兩個日期的間隔,這些在計算當天與某個特定日期間隔的例子中都有所展示。 我們還學到了在Java 8中如何線程安全地解析和格式化日期,不用再使用蹩腳的線程局部變量技巧,也不用依賴Joda Time第三方庫。新API可以作爲處理日期時間操作的標準。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章