從Java8開始,java.time包提供了新的API,主要涉及到的類型:
- 本地的日期和時間:LocalDateTime,LocalDate,LocalTime;
- 帶時區的日期和時間:ZonedDateTime;
- 時刻:Instant;
- 時區:ZoneId,ZoneOffset;
- 時間間隔:Duration;
以及一套新的用於取代SimpleDateFormat的格式化類型DateTimeFormatter
和舊的API相比,新API嚴格區分了時刻、本地日期、本地時間和帶時區的日期時間,並且,對日期和時間進行運算更加方便。
此外,新API修正了舊API不合理的常量設計:
Month的範圍用1~12表示1月到12月;
Week的範圍用1~7表示週一到週日。
最後,新API的類型幾乎全部是不變類型(和String類似),可以放心使用不必擔心被修改。
LocalDateTime
我們首先來看最常用的LocalDateTime,它表示一個本地日期和時間:
import java.time.*;
public class Demo02 {
public static void main(String[] args) {
LocalDate d=LocalDate.now();// 當前日期
LocalTime t=LocalTime.now();// 當前時間
LocalDateTime dt=LocalDateTime.now();// 當前日期和時間
System.out.println(d);
System.out.println(t);
System.out.println(dt);
}
}
本地日期和時間通過now()獲取到的總是以當前默認時區返回的,和舊API不同,LocalDateTime、LocalDate和LocalTime默認嚴格按照ISO 8601規定的日期和時間格式進行打印。
上述代碼其實有一個小問題,在獲取3個類型的時候,由於執行一行代碼總會消耗一點時間,因此,3個類型的日期和時間很可能對不上(時間的毫秒數基本上不同)。爲了保證獲取到同一時刻的日期和時間,可以改寫如下:
LocalDateTime dt = LocalDateTime.now(); // 當前日期和時間
LocalDate d = dt.toLocalDate(); // 轉換到當前日期
LocalTime t = dt.toLocalTime(); // 轉換到當前時間
反過來,通過指定的日期和時間創建LocalDateTime可以通過of()方法:
// 指定日期和時間:
LocalDate d2 = LocalDate.of(2019, 11, 30); // 2019-11-30, 注意11=11月
LocalTime t2 = LocalTime.of(15, 16, 17); // 15:16:17
LocalDateTime dt2 = LocalDateTime.of(2019, 11, 30, 15, 16, 17);
LocalDateTime dt3 = LocalDateTime.of(d2, t2);
因爲嚴格按照ISO 8601的格式,因此,將字符串轉換爲LocalDateTime就可以傳入標準格式:
LocalDateTime dt = LocalDateTime.parse("2019-11-19T15:16:17");
LocalDate d = LocalDate.parse("2019-11-19");
LocalTime t = LocalTime.parse("15:16:17");
注意ISO 8601規定的日期和時間分隔符是T。標準格式如下:
- 日期:yyyy-MM-dd
- 時間:HH:mm:ss
- 帶毫秒的時間:HH:mm:ss.SSS
- 日期和時間:yyyy-MM-dd’T’HH:mm:ss
- 帶毫秒的日期和時間:yyyy-MM-dd’T’HH:mm:ss.SSS
DateTimeFormatter
如果要自定義輸出的格式,或者要把一個非ISO 8601格式的字符串解析成LocalDateTime,可以使用新的DateTimeFormatter:
import java.time.*;
import java.time.format.DateTimeFormatter;
public class Demo02 {
public static void main(String[] args) {
// 自定義格式化
DateTimeFormatter dtf=DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
System.out.println(dtf.format(LocalDateTime.now()));
// 自定義格式解析
LocalDateTime dt2=LocalDateTime.parse("2020/03/04 20:57:33",dtf);
System.out.println(dt2);
}
}
LocalDateTime提供了對日期和時間進行加減的非常簡單的鏈式調用:
import java.time.*;
import java.time.format.DateTimeFormatter;
public class Demo02 {
public static void main(String[] args) {
LocalDateTime dt = LocalDateTime.of(2019, 10, 26, 20, 30, 59);
System.out.println(dt);
// 加5天減3小時:
LocalDateTime dt2 = dt.plusDays(5).minusHours(3);
System.out.println(dt2); // 2019-10-31T17:30:59
// 減1月:
LocalDateTime dt3 = dt2.minusMonths(1);
System.out.println(dt3); // 2019-09-30T17:30:59
}
}
注意到月份加減會自動調整日期,例如從2019-10-31減去1個月得到的結果是2019-09-30,因爲9月沒有31日。
對日期和時間進行調整則使用withXxx()方法,例如:withHour(15)會把10:11:12變爲15:11:12:
- 調整年:withYear()
- 調整月:withMonth()
- 調整日:withDayOfMonth()
- 調整時:withHour()
- 調整分:withMinute()
- 調整秒:withSecond()
示例代碼如下:
import java.time.*;
import java.time.format.DateTimeFormatter;
public class Demo02 {
public static void main(String[] args) {
LocalDateTime dt = LocalDateTime.of(2019, 10, 26, 20, 30, 59);
System.out.println(dt);
// 日期變爲31日:
LocalDateTime dt2 = dt.withDayOfMonth(31);
System.out.println(dt2); // 2019-10-31T20:30:59
// 月份變爲9:
LocalDateTime dt3 = dt2.withMonth(9);
System.out.println(dt3); // 2019-09-30T20:30:59
}
}
同樣注意到調整月份時,會相應地調整日期,即把2019-10-31的月份調整爲9時,日期也自動變爲30。
實際上,LocalDateTime還有一個通用的with()方法允許我們做更復雜的運算。例如:
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
public class Demo02 {
public static void main(String[] args) {
// 本月第一天0:00時刻:
LocalDateTime firstDay = LocalDate.now().withDayOfMonth(1).atStartOfDay();
System.out.println(firstDay);
// 本月最後1天:
LocalDate lastDay = LocalDate.now().with(TemporalAdjusters.lastDayOfMonth());
System.out.println(lastDay);
// 下月第1天:
LocalDate nextMonthFirstDay = LocalDate.now().with(TemporalAdjusters.firstDayOfNextMonth());
System.out.println(nextMonthFirstDay);
// 本月第1個週一:
LocalDate firstWeekday = LocalDate.now().with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY));
System.out.println(firstWeekday);
}
}
對於計算某個月第1個週日這樣的問題,新的API可以輕鬆完成。
要判斷兩個LocalDateTime的先後,可以使用isBefore()、isAfter()方法,對於LocalDate和LocalTime類似:
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
public class Demo02 {
public static void main(String[] args) {
LocalDateTime now = LocalDateTime.now();
LocalDateTime target = LocalDateTime.of(2019, 11, 19, 8, 15, 0);
System.out.println(now.isBefore(target));
System.out.println(LocalDate.now().isBefore(LocalDate.of(2019, 11, 19)));
System.out.println(LocalTime.now().isAfter(LocalTime.parse("08:15:00")));
}
}
注意到LocalDateTime無法與時間戳進行轉換,因爲LocalDateTime沒有時區,無法確定某一時刻。後面我們要介紹的ZonedDateTime相當於LocalDateTime加時區的組合,它具有時區,可以與long表示的時間戳進行轉換。
Duration和Period
Duration表示兩個時刻之間的時間間隔。另一個類似的Period表示兩個日期之間的天數:
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
public class Demo02 {
public static void main(String[] args) {
LocalDateTime start = LocalDateTime.of(2019, 11, 19, 8, 15, 0);
LocalDateTime end = LocalDateTime.of(2020, 1, 9, 19, 25, 30);
Duration d = Duration.between(start, end);
System.out.println(d); // PT1235H10M30S
Period p = LocalDate.of(2019, 11, 19).until(LocalDate.of(2020, 1, 9));
System.out.println(p); // P1M21D
}
}
注意到兩個LocalDateTime之間的差值使用Duration表示,類似PT1235H10M30S,表示1235小時10分鐘30秒。而兩個LocalDate之間的差值用Period表示,類似P1M21D,表示1個月21天。
Duration和Period的表示方法也符合ISO 8601的格式,它以P…T…的形式表示,P…T之間表示日期間隔,T後面表示時間間隔。如果是PT…的格式表示僅有時間間隔。利用ofXxx()或者parse()方法也可以直接創建Duration:
Duration d1 = Duration.ofHours(10); // 10 hours
Duration d2 = Duration.parse("P1DT2H3M"); // 1 day, 2 hours, 3 minutes
有的童鞋可能發現Java 8引入的java.timeAPI。怎麼和一個開源的Joda Time很像?難道JDK也開始抄襲開源了?其實正是因爲開源的Joda Time設計很好,應用廣泛,所以JDK團隊邀請Joda Time的作者Stephen Colebourne共同設計了java.timeAPI。
小結
Java 8引入了新的日期和時間API,它們是不變類,默認按ISO 8601標準格式化和解析;
使用LocalDateTime可以非常方便地對日期和時間進行加減,或者調整日期和時間,它總是返回新對象;
使用isBefore()和isAfter()可以判斷日期和時間的先後;
使用Duration和Period可以表示兩個日期和時間的“區間間隔”。
謝謝觀看