Java8新特性探索之新日期時間庫
一、為什麼引入新的日期時間庫
Java對日期,日曆及時間的處理一直以來都飽受詬病,尤其是它決定將java.util.Date
定義為可修改的以及將SimpleDateFormat
實現成非執行緒安全的。
關於這個新的時間日期庫的最大的優點就在於它定義清楚了時間日期相關的一些概念,比方說,瞬時時間(Instant),持續時間(duration),日期(date),時間(time),時區(time-zone)以及時間段(Period)。同時它也借鑑了Joda
庫的一些優點,比如將人和機器對時間日期的理解區分開的。Java 8仍然延用了ISO的日曆體系,並且與它的前輩們不同,java.time
包中的類是不可變且執行緒安全的。
二、如何使用Java8的新日期和時間
首先認識下Java8新日期和時間的一些關鍵類:
類名 | 說明 |
---|---|
Instant | 代表時間戳 |
LocalDate | 不包含具體時間的日期 |
LocalTime | 不包含日期的時間 |
LocalDateTime | 包含了日期及時間,沒有時區資訊 |
ZonedDateTime | 包含時區的完整的日期時間,偏移量是以UTC/格林威治時間為基準的 |
DateTimeFormatter | 日期解析和格式化工具類 |
接下來從幾個示例中認識Java8新日期和時間的特別之處,很強大
在Java8中獲取當前日期
Java 8中有一個叫LocalDate的類,它能用來表示今天的日期。這個類與java.util.Date略有不同,因為它只包含日期,沒有時間。因此,如果你只需要表示日期而不包含時間,就可以使用它。
LocalDate today = LocalDate.now();
System.out.println("Today is : " + today);
輸出結果:
Today is : 2020-12-13
從輸出結果中可以看到,日期是格式化完了後再輸出來的,不像之前的Date類那樣,打印出來的資料都是未經格式化的,不便於閱讀。
在Java8中獲取當前的年月日
LocalDate類中提供了一些很方便的方法可以用於提取出年月日以及其它的日期屬性。使用這些方法,你可以獲取到任何你所需要的日期屬性,而不再需要使用java.util.Calendar這樣的類
LocalDate today = LocalDate.now();
int year = today.getYear();
int month = today.getMonthValue();
int day = today.getDayOfMonth();
System.out.printf("Year : %d , Month : %d , day : %d \t %n", year, month, day);
輸出結果:
Year : 2020 , Month : 12 , day : 13
在Java8中獲取某個特定的日期
使用工廠方法LocalDate.of(),則可以創建出任意一個日期,它接受年月日的引數,然後返回一個等價的LocalDate例項。關於這個方法還有一個好訊息就是它沒有再犯之前API中的錯,比方說,年只能從1900年開始,月必須從0開始,等等。這裡的日期你寫什麼就是什麼,比如說,下面這個例子中它代表的就是1月14日,沒有什麼隱藏邏輯
LocalDate today = LocalDate.of(2020, 12, 13);
System.out.println("Today is : " + today);
輸出結果:
Today is : 2020-12-13
在Java8中檢查兩個日期是否相等
LocalDate重寫了equals方法來進行日期的比較
LocalDate today = LocalDate.now();
LocalDate date = LocalDate.of(2014, 01, 14);
if(date.equals(today)){
System.out.printf("Today %s and date %s are same date %n", today, date);
}
輸出結果:
Today 2020-12-13 and date 2020-12-13 are same date
在Java8中檢查重複事件
使用MonthDay類。這個類由月日組合,不包含年資訊,也就是說你可以用它來代表每年重複出現的一些日子。當然也有一些別的組合,比如說YearMonth類。它和新的時間日期庫中的其它類一樣也都是不可變且執行緒安全的,並且它還是一個值類(value class)
LocalDate today = LocalDate.now();
LocalDate dateOfBirth = LocalDate.of(2020, 12, 13);
MonthDay birthday = MonthDay.of(dateOfBirth.getMonth(), dateOfBirth.getDayOfMonth());
MonthDay currentMonthDay = MonthDay.from(today);
if (currentMonthDay.equals(birthday)) {
System.out.println("Oh, today is your birthday");
} else {
System.out.println("Sorry, today is not your birthday");
}
輸出結果:
Oh, today is your birthday
在Java8中獲取當前時間
使用LocalTime的類,它是沒有日期的時間,與LocalDate是近親。這裡你也可以用靜態工廠方法now()來獲取當前時間。預設的格式是hh:mm:ss:nnn,這裡的nnn是納秒
LocalTime time = LocalTime.now();
System.out.println("local time now : " + time);
輸出結果:
local time now : 13:44:48.255
在Java8中增加小時數
Java 8不僅提供了不可變且執行緒安全的類,它還提供了一些更方便的方法譬如plusHours()來替換原來的add()方法。順便說一下,這些方法返回的是一個新的LocalTime例項的引用,因為LocalTime是不可變的,可別忘了儲存好這個新的引用。
LocalTime time = LocalTime.now();
LocalTime newTime = time.plusHours(2);
System.out.println("Time after 2 hours : " + newTime);
輸出結果:
Time after 2 hours : 15:47:00.787
在Java8中獲取1周後的日期
LocalDate是用來表示無時間的日期的,它有一個plus()方法可以用來增加日,星期,或者月,ChronoUnit則用來表示這個時間單位。由於LocalDate也是不可變的,因此任何修改操作都會返回一個新的例項,因此別忘了儲存起來。
LocalDate today = LocalDate.now();
LocalDate nextWeek = today.plus(1, ChronoUnit.WEEKS);
System.out.println("Today is : " + today);
System.out.println("Date after 1 week : " + nextWeek);
輸出結果:
Today is : 2020-12-13
Date after 1 week : 2020-12-20
在Java8中獲取一年前後的日期
使用LocalDate的plus()方法來給日期增加日,周或者月,現在我們來學習下如何用minus()方法來找出一年前的那天。
LocalDate today = LocalDate.now();
LocalDate previousYear = today.minus(1, ChronoUnit.YEARS);
System.out.println("Date before 1 year : " + previousYear);
LocalDate nextYear = today.plus(1, ChronoUnit.YEARS);
System.out.println("Date after 1 year : " + nextYear);
輸出結果:
Date before 1 year : 2019-12-13
Date after 1 year : 2021-12-13
在Java8中使用時鐘
Java 8中自帶了一個Clock類,你可以用它來獲取某個時區下當前的瞬時時間,日期或者時間。可以用Clock來替代System.currentTimeInMillis()與 TimeZone.getDefault()方法,如果你需要對不同時區的日期進行處理的話這是相當方便的。
// Returns the current time based on your system clock and set to UTC.
Clock clock1 = Clock.systemUTC();
System.out.println("Clock : " + LocalDate.now(clock1));
// Returns time based on system clock zone Clock defaultClock =
Clock clock2 = Clock.systemDefaultZone();
System.out.println("Clock : " + LocalDate.now(clock2));
輸出結果:
Clock : 2020-12-13
Clock : 2020-12-13
在Java8中判斷一個日期在某個日期的前後
在Java 8中,LocalDate類有一個isBefore()和isAfter()方法可以用來比較兩個日期。如果呼叫方法的那個日期比給定的日期要早的話,isBefore()方法會返回true。
LocalDate today = LocalDate.now();
LocalDate tomorrow = LocalDate.of(2020, 12, 14);
if (tomorrow.isAfter(today)) {
System.out.println("Tomorrow comes after today");
}
LocalDate yesterday = today.minus(1, ChronoUnit.DAYS);
if (yesterday.isBefore(today)) {
System.out.println("Yesterday is day before today");
}
輸出結果:
Tomorrow comes after today
Yesterday is day before today
在Java8中處理不同的時區
Java 8不僅將日期和時間進行了分離,同時還有時區。現在已經有好幾組與時區相關的類了,比如ZonId代表的是某個特定的時區,而ZonedDateTime代表的是帶時區的時間。
LocalDateTime localDateTime = LocalDateTime.now();
ZonedDateTime dateAndTimeInNewYork = ZonedDateTime.of(localDateTime, ZoneId.of("America/New_York"));
System.out.println("Current date and time in a particular timezone : " + dateAndTimeInNewYork);
輸出結果:
Current date and time in a particular timezone : 2020-12-13T14:40:44.664-05:00[America/New_York]
在Java8中表示固定的日期
YearMonth又是另一個組合,它代表的是像信用卡還款日,定期存款到期日,options到期日這類的日期。你可以用這個類來找出那個月有多少天,lengthOfMonth()這個方法返回的是這個YearMonth例項有多少天,這對於檢查2月到底是28天還是29天可是非常有用的。
YearMonth currentYearMonth = YearMonth.now();
System.out.printf("Days in month year %s: %d%n", currentYearMonth, currentYearMonth.lengthOfMonth());
YearMonth creditCardExpiry = YearMonth.of(2020, Month.FEBRUARY);
System.out.printf("Your credit card expires on %s %n", creditCardExpiry);
輸出結果:
Days in month year 2020-12: 31
Your credit card expires on 2020-02
在Java8中檢查閏年
LocalDate類有一個isLeapYear()的方法能夠返回當前LocalDate對應的那年是否是閏年。
LocalDate today = LocalDate.now();
if (today.isLeapYear()) {
System.out.println("This year is Leap year");
} else {
System.out.println("2020 is not a Leap year");
}
輸出結果:
This year is Leap year
在Java8中判斷兩個日期之間包含多少天/月
一個常見的任務就是計算兩個給定的日期之間包含多少天,多少周或者多少年。你可以用java.time.Period類來完成這個功能。
LocalDate today = LocalDate.now();
LocalDate java8Release = LocalDate.of(2021, Month.JANUARY, 13);
Period periodToNextJavaRelease = Period.between(today, java8Release);
System.out.println("Months left between today and Java 8 release : " + periodToNextJavaRelease.getMonths());
輸出結果:
Months left between today and Java 8 release : 1
在Java8中獲取當前時間戳
Instant類有一個靜態的工廠方法now()可以返回當前時間戳
Instant timestamp = Instant.now();
System.out.println("instant : " + timestamp);
輸出結果:
instant : 2020-12-13T07:30:55.877Z
在Java8中使用預定義的格式器來對日期進行解析/格式化
在Java 8之前,時間日期的格式化可是個技術活,我們的好夥伴SimpleDateFormat並不是執行緒安全的,而如果用作本地變數來格式化的話又顯得有些笨重。多虧了執行緒本地變數,這使得它在多執行緒環境下也算有了用武之地。這次它引入了一個全新的執行緒安全的日期與時間格式器。它還自帶了一些預定義好的格式器,包含了常用的日期格式。
String date = "20201213";
LocalDate formatted = LocalDate.parse(date, DateTimeFormatter.BASIC_ISO_DATE);
System.out.printf("Date generated from String %s is %s %n", date, formatted);
輸出結果:
Date generated from String 20201213 is 2020-12-13
在Java8中使用自定義的格式器來解析日期
在DateTimeFormatter的ofPattern靜態方法()傳入任何的模式,它會返回一個例項,這個模式的字面量與前例中是相同的。比如說M還是代表月,而m仍是分。無效的模式會丟擲DateTimeParseException異常
String goodFriday = "12 13 2020";
try {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM dd yyyy");
LocalDate holiday = LocalDate.parse(goodFriday, formatter);
System.out.printf("Successfully parsed String %s, date is %s%n", goodFriday, holiday);
} catch (DateTimeParseException ex) {
ex.printStackTrace();
}
輸出結果:
Successfully parsed String 12 13 2020, date is 2020-12-13
在Java8中對日期進行格式化,轉換成字串
使用DateTimeFormatter類的例項,呼叫它的format()方法。這個方法會返回一個代表當前日期的字串,對應的模式就是傳入的DateTimeFormatter例項中所定義好的。
LocalDateTime localDateTime = LocalDateTime.now();
DateTimeFormatter format = DateTimeFormatter.ofPattern("MMM dd yyyy hh:mm a");
String landing = localDateTime.format(format);
System.out.printf("Arriving at : %s %n", landing);
輸出結果:
Arriving at : 十二月 13 2020 04:13 下午
三、總結
Java 8中新的時間與日期API中的所有類都是不可變且執行緒安全的,這與之前的 Date
與Calendar API
中的恰好相反,那裡面像java.util.Date
以及SimpleDateFormat
這些關鍵的類都不是執行緒安全的。新的時間與日期API中很重要的一點是它定義清楚了基本的時間與日期的概念,比方說,瞬時時間,持續時間,日期,時間,時區以及時間段。它們都是基於ISO日曆體系的。 Java8新的API能勝任任何與時間日期相關的任務。