本文深入探討了Java 8引入的全新日期時間API相較於傳統的Date和Calendar類的優勢及實際應用。鑒於Java 8新日期時間API在設計上的先進性和易用性,我們強烈建議開發者積極採納並替換掉陳舊的Date和Calendar類,轉而採用如LocalDate、LocalDateTime、Zon... ...
引言
在過去的Java版本中,日期和時間的處理主要依賴於java.util.Date
和java.util.Calendar
類,然而隨著業務系統的複雜以及技術層面的提升,這些傳統的日期時間類暴露出了若幹顯著的不足之處。隨著Java8
的發佈,其引入了一套全新的日期時間API,徹底改變了我們處理日期和時間的方式。
傳統的日期時間類
相比較Java8中新引入的java.time
包下的時間處理類,傳統的日期時間處理類在易用性,線程安全,不支持市時區等缺點。
- 設計複雜性:
Date
類的設計較為簡單,但它實際上混合了日期和時間信息,並且沒有提供直觀的方法來單獨操作日期或時間部分。Calendar
類雖然提供了更多靈活性,但由於其內部狀態和方法的複雜性,使得開發者在使用過程中容易出現錯誤和混淆,尤其是在進行日期時間計算和格式化時。比如:
Date currentDate = new Date();
// 輸出原始的日期時間,通常不是人類可讀格式 Fri Mar 08 03:13:47 CST 2024
System.out.println(currentDate);
// 要改變日期的某個部分,必須先將其轉換為 Calendar,然後設置
Calendar calendar = Calendar.getInstance();
calendar.setTime(currentDate);
// 修改日期的天數
calendar.add(Calendar.DAY_OF_MONTH, 1);
-
線程安全性:
Date
和Calendar
類,以及格式化日期的SimpleDateFormat類都不是線程安全的,這意味著在多線程環境下的併發訪問可能會導致數據不一致。-
Date
類內部維護了一個 long 類型的瞬時值,當調用如setTime()
方法來更新這個瞬時值時,不同的線程同時調用就會互相覆蓋彼此的值,造成數據不一致。 -
Calendar
類不僅包含了日期和時間信息,還有一系列內部狀態變數,如年、月、日、小時、分鐘、秒等。Calendar
類的方法通常會修改這些內部狀態,例如add()
、set()
等。在多線程環境下,若多個線程嘗試同時修改同一個Calendar
實例,也會導致不可預期的結果。 -
SimpleDateFormat
類在執行格式化和解析日期時間操作時,內部會維護一個Calendar
對象以及其他一些狀態變數。在format()
或parse()
方法執行過程中,這些狀態會被更新以完成格式轉換。並且SimpleDateFormat
中的方法並非原子操作,因此在多線程併發調用時,可能在一個線程還未完成整個操作時就被另一個線程打斷,導致錯誤的日期時間處理結果。
-
-
時區處理能力:
Date
類雖能表示時間戳,但它不直接關聯時區信息,難以進行時區相關的轉換。而Calendar
雖然支持時區,但操作過程相當複雜。 -
精度差異:
Date
類精度只到毫秒級。
Java8中日期時間類
Java8中引入的LocalDate
,LocalTime
,LocalDateTime
這幾個位於java.time
下的類剋服了上述傳統類別的局限性,提供了更強大、直觀和精準的日期時間處理能力,成為現代Java開發中處理日期時間首選的工具類。相比較傳統的日期時間類,具備以下顯著優勢:
-
功能豐富
java.time
包下的類如LocalDate
、LocalTime
、LocalDateTime
、ZonedDateTime
等都有明確的職責劃分,分別處理日期、時間、日期時間以及帶時區的日期時間,結構清晰,易於理解和使用。並且它們提供了一系列直觀、面向對象的API,如plusXxx()
、minusXxx()
、withXxx()
等方法,使日期時間操作變得簡單明瞭。 -
時區支持
除此之外,還支持時區,通過ZonedDateTime
和ZoneId
等類提供了對時區的更好支持,可以方便地進行時區轉換和處理。 -
線程安全
這些類都是不可變對象,線程安全,可以在多線程環境下安全使用,不會出現因併發操作導致的數據不一致問題。 -
更高的精度
支持納秒級精度,相比Date
類的毫秒精度更勝一籌。
java.time
下主要有如下一些關鍵類:
-
LocalDate
LocalDate
類表示一個不包含時間信息的日期,只包含年、月、日三個部分,且不關聯任何特定時區。 -
LocalTime
LocalTime
類表示一個不包含日期信息的具體時間,包含時、分、秒和納秒四個部分。 -
LocalDateTime
LocalDateTime
類結合了日期和時間,表示一個具體的日期和時間點,但是不包含時區信息。 -
ZonedDateTime
ZonedDateTime
類表示一個帶有時區的日期時間,它可以明確表示某一特定時區內的某一確切時間點。 -
Instant
Instant
類表示時間線上某一瞬時點,通常以Unix紀元(1970-01-01T00:00:00Z)以來的秒數和納秒數表示,它是全球通用的時間點表示。 -
Period
Period
類用於表示兩個日期之間的期間,包括年、月、日的數量。 -
Duration
Duration
類表示兩個時間點之間的時間差,包含秒和納秒的持續時間,主要用於表示時間間隔而非日曆單位。 -
DateTimeFormatter
DateTimeFormatter
類用於日期時間的格式化和解析,提供了標準化和自定義的日期時間格式化方式。 -
TemporalAdjusters
TemporalAdjusters
類提供了一系列實用方法,用於調整日期時間,例如獲取下一個工作日、月初、月末等。
這些類共同構成了一個強大、靈活且易於使用的日期時間處理體系,大大改善了Java在處理日期時間問題時的效率和準確性。接下來我們在使用上分別介紹這些類,以及使用他們的方式,感受他們的強大。
Java8中日期時間類使用
創建
NOW方法獲取當前 時刻、日期、時間
LocalTime localTime = LocalTime.now();
System.out.println("localTime:"+localTime);
LocalDate localDate = LocalDate.now();
System.out.println("localDate:"+localDate);
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println("localDateTime:"+localDateTime);
輸出為:
localTime:15:28:45.241181
localDate:2024-03-11
localDateTime:2024-03-11T15:28:45.260655
針對LocalTime
,LocalDateTime
獲取當前時刻預設會帶有毫秒,如果不需要毫秒的話,可以通過設置納秒為0 保留秒 1秒 = 十億納秒
。例如:
LocalTime localTime = LocalTime.now().withNano(0);
System.out.println("localTime:"+localTime);
LocalDateTime localDateTime = LocalDateTime.now().withNano(0);
System.out.println("localDateTime:"+localDateTime);
輸出為:
localTime:15:32:31
localDateTime:2024-03-11T15:32:31
而對於LocalDateTime
獲取當前日期,預設toString
會帶有T
分隔日期和時間,在項目中,可以通過全局序列化,進行統一的時間格式輸出為 yyyy-MM-dd HH:mm:ss
。但是一般不建議這麼乾,畢竟改變全局序列化配置,建議不使用toString
,可以使用DateTimeFormatter
進行自定義轉換。
of()方法指定年、月、日、時刻創建
// of方法直接傳遞對應的年、月、日
LocalDate localDate = LocalDate.of(2024, 3, 11);
System.out.println("localDate:"+localDate);
localDate = LocalDate.of(2024, Month.MARCH, 11);
System.out.println("localDate:"+localDate);
localDate = LocalDate.ofYearDay(2024, 71);
System.out.println("localDate:"+localDate);
// 北京時間對應的時區
ZoneId chinaTimeZone = ZoneId.of("Asia/Shanghai");
// 創建一個 Instant,這裡使用當前時間的 InstantInstant instant = Instant.now();
localDate = LocalDate.ofInstant(instant, chinaTimeZone);
System.out.println("localDate:"+localDate);
// 使用ofEpochDay()方法,則EpochDay為從公元1970年1月1日(Unix紀元)開始的第多少天
localDate = LocalDate.ofEpochDay(LocalDate.now().toEpochDay());
System.out.println("localDate:"+localDate);
LocalTime localTime = LocalTime.of(1, 30);
System.out.println("localTime:"+localTime);
localTime = LocalTime.of(1, 30, 30);
System.out.println("localTime:"+localTime);
localTime = LocalTime.ofInstant(instant, chinaTimeZone);
System.out.println("localTime:"+localTime);
// 根據一天中的總秒數構建時間
localTime = LocalTime.ofSecondOfDay(localTime.toSecondOfDay());
System.out.println("localTime:"+localTime);
LocalDateTime localDateTime = LocalDateTime.of(2024, 3, 11, 1, 30, 30);
System.out.println("localDateTime:"+localDateTime);
localDateTime = LocalDateTime.of(2024, Month.MARCH, 11, 1, 30, 30);
System.out.println("localDateTime:"+localDateTime);
// 使用LocalDate和LocalTime組合構造
localDateTime = LocalDateTime.of(localDate, localTime);
System.out.println("localDateTime:"+localDateTime);
localDateTime = LocalDateTime.ofInstant(instant, chinaTimeZone);
System.out.println("localDateTime:"+localDateTime);
輸出為:
localDate:2024-03-11
localDate:2024-03-11
localDate:2024-03-11
localDate:2024-03-11
localDate:2024-03-11
localTime:01:30
localTime:01:30:30
localTime:16:41:37.893310
localTime:16:41:37
localDateTime:2024-03-11T01:30:30
localDateTime:2024-03-11T01:30:30
localDateTime:2024-03-11T16:41:37
localDateTime:2024-03-11T16:41:37.893310
from()方法轉換
from()方法
將TemporalAccessor
類型(如ZonedDateTime
)轉換為相對應的日期或者時間。TemporalAccessor
介面是一個用於讀取或寫入日期、時間或者日期時間的通用介面。
// 創建一個ZonedDateTime實例
ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
LocalTime localTime = LocalTime.from(zonedDateTime);
System.out.println("localTime:"+localTime);
LocalDate localDate = LocalDate.from(zonedDateTime);
System.out.println("localDate:"+localDate);
LocalDateTime localDateTime = LocalDateTime.from(zonedDateTime);
System.out.println("localDateTime:"+localDateTime);
輸出為:
localTime:17:18:27.942911
localDate:2024-03-11
localDateTime:2024-03-11T17:18:27.942911
parse()方法轉換
將字元串按照指定格式(可選)解析為對應的日期時間類。
LocalTime localTime = LocalTime.parse("17:25:30");
System.out.println("localTime:"+localTime);
localTime = LocalTime.parse("17:25:30", DateTimeFormatter.ofPattern("HH:mm:ss"));
System.out.println("localTime:"+localTime);
LocalDate localDate = LocalDate.parse("2024-03-11");
System.out.println("localDate:"+localDate);
localDate = LocalDate.parse("2024/03/11", DateTimeFormatter.ofPattern("yyyy/MM/dd"));
System.out.println("localDate:"+localDate);
LocalDateTime localDateTime = LocalDateTime.parse("2024-03-11T17:25:30");
System.out.println("localDateTime:"+localDateTime);
localDateTime = LocalDateTime.parse("2024/03/11 17:25:30", DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"));
System.out.println("localDateTime:"+localDateTime);
輸出為:
localTime:17:25:30
localTime:17:25:30
localDate:2024-03-11
localDate:2024-03-11
localDateTime:2024-03-11T17:25:30
localDateTime:2024-03-11T17:25:30
日期時間類的相互轉換
LocalTime、LocalDate、LocalDateTime 相互轉化
// LocalTime + LocalDate = LocalDateTime
LocalDateTime localDateTime = LocalTime.now().atDate(LocalDate.now());
System.out.println("localDateTime:"+localDateTime);
localDateTime = LocalDate.now().atTime(LocalTime.now());
System.out.println("localDateTime:"+localDateTime);
localDateTime = LocalDateTime.of(LocalDate.now(), LocalTime.now());
System.out.println("localDateTime:"+localDateTime);
// LocalDateTime 轉 LocalDate
LocalDate localDate = LocalDateTime.now().toLocalDate();
System.out.println("localDate:"+localDate);
// LocalDateTime 轉 LocalTime
LocalTime localTime = LocalDateTime.now().toLocalTime();
System.out.println("localTime:"+localTime);
// 獲取今日開始時間 2024-03-11T00:00
localDateTime = LocalDate.now().atStartOfDay();
System.out.println("localDateTime:"+localDateTime);
// 獲取今日開始時間 2024-03-11T00:00
LocalDateTime startDateTime = LocalDateTime.of(LocalDate.now(), LocalTime.MIN);
System.out.println("startDateTime:"+ startDateTime);
// 獲取今日結束時間 2024-03-11T23:59:59.999999999
LocalDateTime endDateTime = LocalDateTime.of(LocalDate.now(), LocalTime.MAX);
System.out.println("endDateTime:"+ endDateTime);
輸出為:
localDateTime:2024-03-11T18:04:22.348539
localDateTime:2024-03-11T18:04:22.370562
localDateTime:2024-03-11T18:04:22.370768
localDate:2024-03-11
localTime:18:04:22.371062
localDateTime:2024-03-11T00:00
startDateTime:2024-03-11T00:00
endDateTime:2024-03-11T23:59:59.999999999
String 與 LocalTime、LocalDate、LocalDateTime 相互轉化
主要使用format
和 parse
進行轉換,使用方法基本相同。使用 DateTimeFormatter.ofPattern()
定義時間格式,再進行轉換。
DateTimeFormatter
線程安全。
// LocalTime 轉 String 自定義輸出格式,例如:**時**分**秒 該轉化的 00 不會被省略
String localTimeStr = LocalTime.now().format(DateTimeFormatter.ofPattern("HH時mm分ss秒"));
System.out.println("localTimeStr:"+localTimeStr);
String localDateStr = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
System.out.println("localDateStr:"+localDateStr);
// LocalDateTime 轉 String
String localDateTimeStr = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println("localDateTimeStr:"+localDateTimeStr);
// String 轉 LocalDateTime
LocalDateTime localDateTime = LocalDateTime.parse("2023-04-14 15:59:40", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println("localDateTime:"+localDateTime);
輸出結果:
localTimeStr:19時02分58秒
localDateStr:2024-03-11
localDateTimeStr:2024-03-11 19:02:58
localDateTime:2023-04-14T15:59:40
Date 與 LocalDate、LocalDateTime 相互轉化
// Date 轉 LocalDateTime
Date currentDate = new Date();
// 轉換為Instant
Instant instant = currentDate.toInstant();
// 通過zoneId設置時區(這裡使用系統時區),轉換為帶帶時區的 ZoneDateTime
ZonedDateTime zonedDateTime = instant.atZone(ZoneId.systemDefault());
// 然後通過ZonedDateTime轉換為LocalDateTime
LocalDateTime localDateTime = zonedDateTime.toLocalDateTime();
System.out.println("localDateTime:"+localDateTime);
// LocalDateTime 轉 Date,同理也是通過ZonedDateTime轉換為Date
Date localDateTimeToDate = Date.from(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant());
System.out.println(localDateTimeToDate);
// Date轉LocalDate 同理 LocalDateTime轉換
LocalDate localDate = currentDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
System.out.println("localDate:"+localDate);
// LocalDate 轉 Date 需要先將 LocalDate 轉 LocalDateTime
Date localDateToDate = Date.from(LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant());
這裡介紹一下ZoneId。java.time.ZoneId
是Java 8中java.time
包中用於表示時區的類。時區是地球上的地理位置,用於確定在該位置觀察太陽升落以及規定當地居民生活和商業活動時間的標準時間。ZoneId
使用IANA時區資料庫提供的時區標識符,這個標識符是唯一的,這些標識符通常是地區/城市對,例如“Asia/Shanghai”代表中國上海所在的時區,America/New_York
代表美國紐約城市。
其實例獲取有兩種方式:
ZoneId.systemDefault()
:獲取系統預設的時區ID。ZoneId.of(String zoneId)
:根據提供的時區ID字元串獲取ZoneId實例。至於zoneId的值,可以查看源碼。可以通過ZoneId.getAvailableZoneIds()
查看獲取。
Long 與 LocalDate、LocalDateTime 相互轉化
時間戳轉換。
long timeMillis = System.currentTimeMillis();
// 時間戳(Long) 轉 LocalDateTime
LocalDateTime localDateTime = Instant.ofEpochMilli(timeMillis).atZone(ZoneOffset.ofHours(8)).toLocalDateTime();
System.out.println("localDateTime:"+localDateTime);
localDateTime = Instant.ofEpochMilli(timeMillis).atZone(ZoneId.systemDefault()).toLocalDateTime();
System.out.println("localDateTime:"+localDateTime);
// LocalDateTime 轉 時間戳(Long) 秒級
long localDateTimeToSecond = LocalDateTime.now().toEpochSecond(ZoneOffset.ofHours(8));
System.out.println("localDateTimeToSecond:"+ localDateTimeToSecond);
// LocalDateTime 轉 時間戳(Long) 毫秒級
long localDateTimeToMilliSecond = LocalDateTime.now().toInstant(ZoneOffset.ofHours(8)).toEpochMilli();
System.out.println("localDateTimeToMilliSecond:"+ localDateTimeToMilliSecond);
// 時間戳(Long) 轉 LocalDate
LocalDate localDate = Instant.ofEpochMilli(timeMillis).atZone(ZoneOffset.ofHours(8)).toLocalDate();
System.out.println("localDate:"+ localDate);
// LocalDate 轉 時間戳(Long) 秒級
long localDateToSecond = LocalDate.now().atStartOfDay().toEpochSecond(ZoneOffset.ofHours(8));
System.out.println("localDateToSecond:"+ localDateToSecond);
// LocalDate 轉 時間戳(Long) 毫秒級
long localDateToMilliSecond = LocalDate.now().atStartOfDay().toInstant(ZoneOffset.ofHours(8)).toEpochMilli();
System.out.println("localDateToMilliSecond:"+ localDateToMilliSecond);
輸出結果為:
localDateTime:2024-03-11T19:37:02.335
localDateTime:2024-03-11T19:37:02.335
localDateTimeToSecond:1710157022
localDateTimeToMilliSecond:1710157022365
localDate:2024-03-11
localDateToSecond:1710086400
localDateToMilliSecond:1710086400000
java.time.ZoneOffset
是Java8中java.time
包內用來表示時區偏移量的類,它表示的是格林尼治標準時間或協調世界時間(UTC)基礎上的固定偏移量。每一個時區都可以通過一個或多個偏移量來表示,比如“+02:00”表示比UTC時間快兩個小時的時區偏移。
其實例創建有如下方式:
ZoneOffset.ofHours(int hours)
:根據小時數創建偏移量,例如ZoneOffset.ofHours(2)
表示比UTC早2小時的時區。ZoneOffset.ofHoursMinutes(int hours, int minutes)
:根據小時數和分鐘數創建偏移量。ZoneOffset.ofHoursMinutesSeconds(int hours, int minutes, int seconds)
:根據小時、分鐘和秒數創建偏移量。ZoneOffset.ofTotalSeconds(int totalSeconds)
:根據相對於UTC的總秒數創建偏移量。ZoneOffset.of(String offsetId)
:根據偏移量ID(如 "+02:00")創建實例。
日期時間類的操作
日期時間的增減
java.time
包中日期時間類(如 LocalDateTime
、LocalDate
和 LocalTime
)可以通過plusXxx()
和 minusXxx()
方法,用於對日期時間對象進行加減操作,以增加或減少指定的時間或日期單位。
1、LocalDateTime 加減:
plusHours(int hours)
,plusMinutes(int minutes)
,plusSeconds(int seconds)
:分別用於向LocalDateTime
對象添加指定的小時數、分鐘數和秒數。plus(1, ChronoUnit.XXX)
:這裡的ChronoUnit
參數可以是HOURS
、MINUTES
、SECONDS
等,也可以是YEARS
、MONTHS
、DAYS
、WEEKS
等,用於嚮日期時間對象添加指定單位的數量。plus(Duration duration)
:使用Duration
對象來增加時間,Duration
可以包含秒和納秒的精度。plus(Period period)
:使用Period
對象來增加日期,Period
可以表示年、月、日的數量。- 與
plusXxx()
方法相對應,minusXxx()
方法用於從日期時間對象中減少指定的單位。例如minusHours(int hours)
、minusMinutes(int minutes)
、minusSeconds(int seconds)
等方法用於減少小時、分鐘、秒數。
// LocalDateTime 加減
LocalDateTime localDateTime = LocalDateTime.now();
// 以下為增加時、分、秒
LocalDateTime plusLocalDateTime = localDateTime.plusHours(1).plusMinutes(1).plusSeconds(1);
System.out.println("plusLocalDateTime:"+plusLocalDateTime);
plusLocalDateTime = localDateTime.plus(1, ChronoUnit.HOURS).plus(1, ChronoUnit.MINUTES).plus(1, ChronoUnit.SECONDS);
System.out.println("plusLocalDateTime:"+plusLocalDateTime);
plusLocalDateTime = localDateTime.plus(Duration.ofHours(1)).plus(Duration.of(1, ChronoUnit.MINUTES)).plus(Duration.of(1, ChronoUnit.SECONDS));
System.out.println("plusLocalDateTime:"+plusLocalDateTime);
// 以下為增加年、月、日
plusLocalDateTime = localDateTime.plusYears(1).plusMonths(1).plusWeeks(1).plusDays(1);
System.out.println("plusLocalDateTime:"+plusLocalDateTime);
plusLocalDateTime = localDateTime.plus(1, ChronoUnit.YEARS).plus(1, ChronoUnit.MONTHS).plus(1, ChronoUnit.WEEKS).plus(1, ChronoUnit.DAYS);
System.out.println("plusLocalDateTime:"+plusLocalDateTime);
plusLocalDateTime = localDateTime.plus(Duration.of(1, ChronoUnit.YEARS)).plus(Duration.of(1, ChronoUnit.MONTHS)).plus(Duration.of(1, ChronoUnit.WEEKS)).plus(Duration.ofDays(1));
System.out.println("plusLocalDateTime:"+plusLocalDateTime);
plusLocalDateTime = localDateTime.plus(Period.ofYears(1)).plus(Period.ofMonths(1)).plus(Period.ofWeeks(1)).plus(Period.ofDays(1));
System.out.println("plusLocalDateTime:"+plusLocalDateTime);
// 以下為減少時、分、秒
LocalDateTime minusLocalDateTime = localDateTime.minusHours(1).minusMinutes(1).minusSeconds(1);
System.out.println("minusLocalDateTime:"+minusLocalDateTime);
minusLocalDateTime = localDateTime.minus(1, ChronoUnit.HOURS).minus(1, ChronoUnit.MINUTES).minus(1, ChronoUnit.SECONDS);
System.out.println("minusLocalDateTime:"+minusLocalDateTime);
minusLocalDateTime = localDateTime.minus(Duration.ofHours(1)).minus(Duration.of(1, ChronoUnit.MINUTES)).minus(Duration.of(1, ChronoUnit.SECONDS));
System.out.println("minusLocalDateTime:"+minusLocalDateTime);
// 以下為減少年、月、日
minusLocalDateTime = localDateTime.minusYears(1).minusMonths(1).minusWeeks(1).minusDays(1);
System.out.println("minusLocalDateTime:"+minusLocalDateTime);
minusLocalDateTime = localDateTime.minus(1, ChronoUnit.YEARS).minus(1, ChronoUnit.MONTHS).minus(1, ChronoUnit.WEEKS).minus(1, ChronoUnit.DAYS);
System.out.println("minusLocalDateTime:"+minusLocalDateTime);
minusLocalDateTime = localDateTime.minus(Duration.of(1, ChronoUnit.YEARS)).minus(Duration.of(1, ChronoUnit.MONTHS)).minus(Duration.of(1, ChronoUnit.WEEKS)).minus(Duration.ofDays(1));
System.out.println("minusLocalDateTime:"+minusLocalDateTime);
minusLocalDateTime = localDateTime.minus(Period.ofYears(1)).minus(Period.ofMonths(1)).minus(Period.ofWeeks(1)).minus(Period.ofDays(1));
System.out.println("plusLocalDateTime:"+minusLocalDateTime);
2、LocalDate 加減:
- 同樣的,
plusYears(int years)
,plusMonths(int months)
,plusDays(int days)
分別用於增加年、月、日。 plus(1, ChronoUnit.XXX)
和plus(Duration/Period duration/period)
方法在此同樣適用,用於增加指定的日期單位。- 與
plusXxx()
方法相對應,minusXxx()
方法用於從日期時間對象中減少指定的單位。例如minusYears(int years)
、minusMonths(int months)
、minusWeeks(int weeks)
、minusDays(int days)
等方法用於減少年、月、周、天數。
// LocalDate 加減
LocalDate localDate = LocalDate.now();
LocalDate plusLocalDate = localDate.plusYears(1).plusMonths(1).plusWeeks(1).plusDays(1);
System.out.println("plusLocalDate:"+plusLocalDate);
plusLocalDate = localDate.plus(1, ChronoUnit.YEARS).plus(1, ChronoUnit.MONTHS).plus(1, ChronoUnit.WEEKS).plus(1, ChronoUnit.DAYS);
System.out.println("plusLocalDate:"+plusLocalDate);
plusLocalDate = localDate.plus(Duration.of(1, ChronoUnit.YEARS)).plus(Duration.of(1, ChronoUnit.MONTHS)).plus(Duration.of(1, ChronoUnit.WEEKS)).plus(Duration.ofDays(1));
System.out.println("plusLocalDate:"+plusLocalDate);
plusLocalDate = localDate.plus(Period.ofYears(1)).plus(Period.ofMonths(1)).plus(Period.ofWeeks(1)).plus(Period.ofDays(1));
System.out.println("plusLocalDate:"+plusLocalDate);
LocalDate minusLocalDate = localDate.minusYears(1).minusMonths(1).minusWeeks(1).minusDays(1);
System.out.println("minusLocalDate:"+minusLocalDate);
minusLocalDate = localDate.minus(1, ChronoUnit.YEARS).minus(1, ChronoUnit.MONTHS).minus(1, ChronoUnit.WEEKS).minus(1, ChronoUnit.DAYS);
System.out.println("minusLocalDate:"+minusLocalDate);
minusLocalDate = localDate.minus(Duration.of(1, ChronoUnit.YEARS)).minus(Duration.of(1, ChronoUnit.MONTHS)).minus(Duration.of(1, ChronoUnit.WEEKS)).minus(Duration.ofDays(1));
System.out.println("minusLocalDate:"+minusLocalDate);
minusLocalDate = localDate.minus(Period.ofYears(1)).minus(Period.ofMonths(1)).minus(Period.ofWeeks(1)).minus(Period.ofDays(1));
System.out.println("minusLocalDate:"+minusLocalDate);
3、LocalTime 加減:
plusHours(int hours)
,plusMinutes(int minutes)
,plusSeconds(int seconds)
:分別用於向LocalTime
對象添加指定的小時數、分鐘數和秒數。- 同樣支持
plus(1, ChronoUnit.XXX)
和plus(Duration duration)
方法,用於增加時間單位。 - 與
plusXxx()
方法相對應,minusXxx()
方法用於從日期時間對象中減少指定的單位。minus(1, ChronoUnit.XXX)
,minus(Duration duration)
,minus(Period period)
方法也分別用於減少指定的日期或時間單位。例如minusHours(int hours)
、minusMinutes(int minutes)
、minusSeconds(int seconds)
等方法用於減少小時、分鐘、秒數。
// LocalTime 加減
LocalTime localTime = LocalTime.now();
LocalTime plusLocalTime = localTime.plusHours(1).plusMinutes(1).plusSeconds(1);
System.out.println("plusLocalTime:"+plusLocalTime);
plusLocalTime = localTime.plus(1, ChronoUnit.HOURS).plus(1, ChronoUnit.MINUTES).plus(1, ChronoUnit.SECONDS);
System.out.println("plusLocalTime:"+plusLocalTime);
plusLocalTime = localTime.plus(Duration.ofHours(1)).plus(Duration.of(1, ChronoUnit.MINUTES)).plus(Duration.of(1, ChronoUnit.SECONDS));
System.out.println("plusLocalTime:"+plusLocalTime);
LocalTime minusLocalTime = localTime.minusHours(1).minusMinutes(1).minusSeconds(1);
System.out.println("minusLocalTime:"+minusLocalTime);
minusLocalTime = localTime.minus(1, ChronoUnit.HOURS).minus(1, ChronoUnit.MINUTES).minus(1, ChronoUnit.SECONDS);
System.out.println("minusLocalDateTime:"+minusLocalTime);
minusLocalTime = localTime.minus(Duration.ofHours(1)).minus(Duration.of(1, ChronoUnit.MINUTES)).minus(Duration.of(1, ChronoUnit.SECONDS));
System.out.println("minusLocalDateTime:"+minusLocalTime);
日期時間修改指定值
LocalDate
、LocalTime
、LocalDateTime
、ZonedDateTime
可以通過相對應的withXxx()
方法修改指定的值。
1、LocalDate:
LocalDate.withYear(int year)
:修改年份欄位。LocalDate.withMonth(int month)
:修改月份欄位(註意月份是從1開始計數的)。LocalDate.withDayOfMonth(int dayOfMonth)
:修改日期欄位。
LocalDate localDate = LocalDate.of(2024, 3, 12);
LocalDate newDate = localDate.withYear(2025).withMonth(4).with(ChronoField.DAY_OF_MONTH, 13);
System.out.println("newDate:"+newDate);
2、LocalTime:
LocalTime.withHour(int hour)
:修改小時欄位。LocalTime.withMinute(int minute)
:修改分鐘欄位。LocalTime.withSecond(int second)
:修改秒欄位。LocalTime.withNano(int nanoOfSecond)
:修改納秒欄位。
LocalTime localTime = LocalTime.of(17, 25, 30);
LocalTime newTime = localTime.withHour(18).withMinute(26).with(ChronoField.SECOND_OF_MINUTE, 31);
System.out.println("newTime:"+newTime);
3、LocalDateTime:
LocalDateTime.withYear(int year)
LocalDateTime.withMonth(int month)
LocalDateTime.withDayOfMonth(int dayOfMonth)
LocalDateTime.withHour(int hour)
LocalDateTime.withMinute(int minute)
LocalDateTime.withSecond(int second)
LocalDateTime.withNano(int nanoOfSecond)
LocalDateTime localDateTime = LocalDateTime.of(2024, 3, 12, 17, 25, 30);
LocalDateTime newDateTime = localDateTime.withYear(2025).withMonth(4).with(ChronoField.DAY_OF_MONTH, 13).withHour(18).withMinute(26).with(ChronoField.SECOND_OF_MINUTE, 31);
System.out.println("newDateTime:"+ newDateTime);
4、ZonedDateTime:
- 除了上述的日期和時間欄位外,還有時區相關的
withZoneSameInstant(ZoneId zone)
方法,可以改變時區的同時保持同一瞬間不變。
ZonedDateTime zonedDateTime = ZonedDateTime.of(2024, 3, 12, 17, 25, 30, 0, ZoneId.of("Europe/London"));
ZonedDateTime newZonedDateTime = zonedDateTime.withZoneSameInstant(ZoneId.of("America/New_York"));
System.out.println("newZonedDateTime:"+ newZonedDateTime);
除此之外,調整日期時間還可以通過TemporalAdjusters
,TemporalAdjuster
是一個函數式介面,用於根據給定的規則調整日期時間對象。Java8的 java.time.temporal
包中預定義了一系列常用的 TemporalAdjuster
實現,例如獲取下一個工作日、月初、月末等。
LocalDate date = LocalDate.of(2024, 3, 11);
// 下一個工作日
LocalDate nextWorkingDay = date.with(TemporalAdjusters.next(DayOfWeek.MONDAY)); // 如果11號不是周一,則返回下一個周一的日期
// 下一個月的第一天
LocalDate firstDayNextMonth = date.with(TemporalAdjusters.firstDayOfMonth()); // 返回4月1日
// 當月的最後一個工作日
LocalDate lastWorkingDay = date.with(TemporalAdjusters.lastInMonth(DayOfWeek.FRIDAY)); // 返回3月最後一個周五的日期
// 自定義 TemporalAdjuster
TemporalAdjuster adjuster = temporal -> {
return temporal.plusDays(10).with(TemporalAdjusters.lastDayOfMonth());
};
LocalDate tenthDayNextMonthEnd = date.with(adjuster); // 返回4月最後一個日期,前提是先加10天
日期時間的比較
在Java8及其以後版本的日期時間API中,isBefore()
和 isAfter()
方法是 java.time
包中的 LocalDate
、LocalTime
、LocalDateTime
、ZonedDateTime
等日期時間類所共有的方法,用於比較兩個日期時間對象的先後順序。
isBefore():
- 此方法用於判斷當前對象是否早於另一個日期時間對象。
- 如果當前對象的時間點在參數對象之前,則返回
true
;否則返回false
。
LocalDate date1 = LocalDate.of(2024, 3, 11);
LocalDate date2 = LocalDate.of(2024, 3, 12);
boolean isEarlier = date1.isBefore(date2); // 返回 true,因為 date1 在 date2 之前
isAfter():
- 此方法用於判斷當前對象是否晚於另一個日期時間對象。
- 如果當前對象的時間點在參數對象之後,則返回
true
;否則返回false
。
LocalDateTime time1 = LocalDateTime.of(2024, 3, 11, 10, 0);
LocalDateTime time2 = LocalDateTime.of(2024, 3, 11, 9, 0);
boolean isLater = time1.isAfter(time2); // 返回 true,因為 time1 在 time2 之後
compareTo()
在Java 8的 java.time
包中,大部分日期時間類如 LocalDate
、LocalTime
、LocalDateTime
、ZonedDateTime
都實現了 Comparable
介面,從而可以直接使用 compareTo()
方法進行比較。compareTo()
方法用於比較兩個日期時間對象的先後順序,返回值含義如下:
- 如果當前對象早於(時間點在前)參數對象,返回負數。
- 如果當前對象等於參數對象,返回0。
- 如果當前對象晚於(時間點在後)參數對象,返回正數。
LocalDate date1 = LocalDate.of(2024, 3, 11);
LocalDate date2 = LocalDate.of(2024, 3, 12);
int comparisonResult = date1.compareTo(date2);
if (comparisonResult < 0) {
System.out.println("date1 is before date2");
} else if (comparisonResult > 0) {
System.out.println("date1 is after date2");
} else {
System.out.println("date1 is equal to date2");
}
LocalDateTime dateTime1 = LocalDateTime.of(2024, 3, 11, 10, 30);
LocalDateTime dateTime2 = LocalDateTime.of(2024, 3, 11, 11, 00);
int timeComparisonResult = dateTime1.compareTo(dateTime2);
其他操作
在Java8的 java.time
包中,各個日期時間類如 LocalDate
、LocalTime
、LocalDateTime
提供了一系列 get
方法,用於獲取特定欄位的值。
獲取日期中的特定欄位:
LocalDate date = LocalDate.of(2024, 3, 11);
int dayOfMonth = date.getDayOfMonth(); // 獲取當月的第幾天,此處返回11
int monthValue = date.getMonthValue(); // 獲取月份值,此處返回3
Month month = date.getMonth(); // 獲取Month枚舉,此處返回March
int year = date.getYear(); // 獲取年份,此處返回2024
對於時間部分,類似地可以獲取小時、分鐘、秒和納秒:
LocalTime time = LocalTime.of(19, 30, 45);
int hour = time.getHour(); // 獲取小時數,此處返回10
int minute = time.getMinute(); // 獲取分鐘數,此處返回30
int second = time.getSecond(); // 獲取秒數,此處返回45
int nano = time.getNano(); // 獲取納秒數
在SpringBoot中使用
SpringBoot
預設集成了Jackson
作為JSON
處理庫,Jackson
已經能自動處理 LocalDate
、LocalTime
和 LocalDateTime
類型。
如果需要使用自定義日期時間格式,我們有兩種方式:
手動更改全局配置: 如果需要自定義日期格式,可以通過 ObjectMapper
的配置類來註冊自定義的日期格式化器:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</dependency>
@Configuration
public class JacksonConfig {
@Bean
public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() {
return builder -> {
builder.simpleDateFormat("yyyy-MM-dd");
// 使用Java 8時間API的日期格式器
builder.dateFormat(new StdDateFormat().withColonInTimeZone(true));
// 註冊LocalDateTime的序列化和反序列化模塊
builder.modules(new JavaTimeModule());
};
}
}
手動綁定格式化配置
SpringBoot支持自動綁定HTTP請求參數到控制器方法參數中,包括 LocalDate
、LocalTime
和 LocalDateTime
類型。客戶端需發送符合日期格式的字元串,Spring Boot會自動轉換成相應類型。
@PostMapping("/events")
public ResponseEntity<Event> createEvent(@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate date,
@RequestParam @DateTimeFormat(pattern = "HH:mm:ss") LocalTime startTime,
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime timestamp) {
// ...
}
或者請求或者響應VO中:
public static class ResponseVO{
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate date;
@DateTimeFormat(pattern = "HH:mm:ss")
private LocalTime startTime;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime timestamp;
}
Mybatis中使用
在MyBatis中查詢MySQL資料庫時,使用Java 8的 java.time.LocalDate
、java.time.LocalTime
和 java.time.LocalDateTime
類型。
- 資料庫表結構: 在MySQL資料庫中,通常需要使用適合的日期時間類型來存儲這些Java 8的日期時間對象。例如:
LocalDate
對應MySQL
的DATE
類型。LocalTime
對應MySQL
的TIME
類型。LocalDateTime
對應MySQL
的DATETIME
或TIMESTAMP
類型。
CREATE TABLE `test_date`(
`id` BIGINT ( 20 ) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增主鍵',
test_local_date DATE ,
test_local_time TIME,
test_local_date_time DATETIME,
PRIMARY KEY ( `id` )
)
ENGINE = INNODB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT = '日期時間測試';
- 實體類映射: 在Java實體類中,對應欄位應聲明為
LocalDate
、LocalTime
或LocalDateTime
類型。
@Data
public class TestDate implements Serializable {
/**
* 自增主鍵
*/
private Long id;
private LocalDate testLocalDate;
private LocalTime testLocalTime;
private LocalDateTime testLocalDateTime;
private static final long serialVersionUID = 1L;
}
3.MyBatis配置:
- 自動類型轉換:如果你使用的是較新的MyBatis版本(>=3.4.5),MyBatis已經內置了對Java 8日期時間類型的處理。這意味著在執行SQL查詢時,MyBatis會自動將資料庫中的日期時間欄位轉換為相應的Java8類型。
@Test
public void testInsertDate(){
TestDate testDate = new TestDate();
testDate.setTestLocalDate(LocalDate.of(2024, 3, 12));
testDate.setTestLocalTime(LocalTime.of(20,10,30));
testDate.setTestLocalDateTime(LocalDateTime.of(2024, 3, 12,20,10,30,0));
testDateMapper.insert(testDate);
}
@Test
public void testQueryDate(){
TestDate testDate = testDateMapper.selectByPrimaryKey(1L);
System.out.println("testLocalDate:"+testDate.getTestLocalDate());
System.out.println("testLocalTime:"+testDate.getTestLocalTime());
System.out.println("testLocalDateTime:"+testDate.getTestLocalDateTime());
}
- 自定義TypeHandler:如果MyBatis版本較低或者需要自定義日期時間格式,你可能需要自定義
TypeHandler
來處理LocalDate
、LocalTime
和LocalDateTime
與資料庫欄位間的轉換。關於Mybatis使用自定義TypeHandler,請非同步:玩轉Mybatis:自定義TypeHandler,輕鬆應對Mysql的JSON類型操作
結論
綜上所述,本文深入探討了Java 8引入的全新日期時間API相較於傳統的Date和Calendar類的優勢及實際應用。鑒於Java 8新日期時間API在設計上的先進性和易用性,我們強烈建議開發者積極採納並替換掉陳舊的Date和Calendar類,轉而採用如LocalDate、LocalDateTime、ZonedDateTime等現代日期時間類。
Java 8新日期時間API提供了更為清晰、直觀的操作介面,支持不可變對象設計模式,增強了類型安全性,並具備豐富的日期時間運算、解析與格式化功能,顯著提高了代碼質量與可讀性。此外,新API對日期時間單位的精確度控制、時區管理以及與其他日期時間規範的相容性等方面均表現出卓越的表現力和靈活性,使得開發者在處理各類複雜日期時間邏輯時能夠更加得心應手,提升開發效率。
因此,無論是處於對代碼現代化改造的需求,還是出於提高開發效率和程式穩定性的考量,遷移到Java 8的新日期時間API無疑是明智之舉。通過充分利用這些強大且功能完備的工具,開發者將在日期時間處理領域實現飛躍,為項目的長期維護和發展打下堅實基礎。
本文已收錄於我的個人博客:碼農Academy的博客,專註分享Java技術乾貨,包括Java基礎、Spring Boot、Spring Cloud、Mysql、Redis、Elasticsearch、中間件、架構設計、面試題、程式員攻略等