1. 程式人生 > >【深度思考】JDK8中日期型別該如何使用?

【深度思考】JDK8中日期型別該如何使用?

在JDK8之前,處理日期時間,我們主要使用3個類,`Date`、`SimpleDateFormat`和`Calendar`。 這3個類在使用時都或多或少的存在一些問題,比如`SimpleDateFormat`不是執行緒安全的, 比如`Date`和`Calendar`獲取到的月份是0到11,而不是現實生活中的1到12,關於這一點,《阿里巴巴Java開發手冊》中也有提及,因為很容易犯錯: ![](https://images.zwwhnly.com/picture/2020/06/snipaste_20200610_155757.png) 不過,JDK8推出了全新的日期時間處理類解決了這些問題,比如`Instant`、`LocalDate`、`LocalTime`、`LocalDateTime`、`DateTimeFormatter`,在《阿里巴巴Java開發手冊》中也推薦使用`Instant`、 `LocalDateTime`、`DateTimeFormatter`: ![](https://images.zwwhnly.com/picture/2020/06/snipaste_20200610_160150.png) 但我發現好多專案中其實並沒有使用這些類,使用的還是之前的`Date`、`SimpleDateFormat`和`Calendar`,所以本篇部落格就講解下JDK8新推出的日期時間類,主要是下面幾個: 1. Instant 2. LocalDate 3. LocalTime 4. LocalDateTime 5. DateTimeFormatter ## 1. Instant ### 1.1 獲取當前時間 既然`Instant`可以代替`Date`類,那它肯定可以獲取當前時間: ```java Instant instant = Instant.now(); System.out.println(instant); ``` 輸出結果: > 2020-06-10T08:22:13.759Z 細心的你會發現,這個時間比北京時間少了8個小時,如果要輸出北京時間,可以加上預設時區: ```java System.out.println(instant.atZone(ZoneId.systemDefault())); ``` 輸出結果: > 2020-06-10T16:22:13.759+08:00[Asia/Shanghai] ### 1.2 獲取時間戳 ```java Instant instant = Instant.now(); // 當前時間戳:單位為秒 System.out.println(instant.getEpochSecond()); // 當前時間戳:單位為毫秒 System.out.println(instant.toEpochMilli()); ``` 輸出結果: > 1591777752 > > 1591777752613 當然,也可以通過`System.currentTimeMillis()`獲取當前毫秒數。 ### 1.3 將long轉換為Instant 1)根據秒數時間戳轉換: ```java Instant instant = Instant.now(); System.out.println(instant); long epochSecond = instant.getEpochSecond(); System.out.println(Instant.ofEpochSecond(epochSecond)); System.out.println(Instant.ofEpochSecond(epochSecond, instant.getNano())); ``` 輸出結果: > 2020-06-10T08:40:54.046Z > > 2020-06-10T08:40:54Z > > 2020-06-10T08:40:54.046Z 2)根據毫秒數時間戳轉換: ```java Instant instant = Instant.now(); System.out.println(instant); long epochMilli = instant.toEpochMilli(); System.out.println(Instant.ofEpochMilli(epochMilli)); ``` 輸出結果: > 2020-06-10T08:43:25.607Z > > 2020-06-10T08:43:25.607Z ### 1.4 將String轉換為Instant ```java String text = "2020-06-10T08:46:55.967Z"; Instant parseInstant = Instant.parse(text); System.out.println("秒時間戳:" + parseInstant.getEpochSecond()); System.out.println("豪秒時間戳:" + parseInstant.toEpochMilli()); System.out.println("納秒:" + parseInstant.getNano()); ``` 輸出結果: > 秒時間戳:1591778815 > > 豪秒時間戳:1591778815967 > > 納秒:967000000 如果字串格式不對,比如修改成`2020-06-10T08:46:55.967`,就會丟擲`java.time.format.DateTimeParseException`異常,如下圖所示: ![](https://images.zwwhnly.com/picture/2020/06/snipaste_20200610_165330.png) ## 2. LocalDate ### 2.1 獲取當前日期 使用`LocalDate`獲取當前日期非常簡單,如下所示: ```java LocalDate today = LocalDate.now(); System.out.println("today: " + today); ``` 輸出結果: > today: 2020-06-10 不用任何格式化,輸出結果就非常友好,如果使用`Date`,輸出這樣的格式,還得配合`SimpleDateFormat`指定`yyyy-MM-dd`進行格式化,一不小心還會出個bug,比如去年年底很火的1個bug,我當時還是截了圖的: ![](https://images.zwwhnly.com/picture/2020/06/snipaste_20200610_171414.png) 這2個好友是2019/12/31關注我的,但我2020年1月2號檢視時,卻顯示成了2020/12/31,為啥呢?格式化日期時格式寫錯了,應該是`yyyy/MM/dd`,卻寫成了`YYYY/MM/dd`,剛好那周跨年,就顯示成下一年,也就是2020年了,當時好幾個博主寫過文章解析原因,我這裡就不做過多解釋了。 > 劃重點:都說到這了,給大家安利下我新註冊的公眾號「申城異鄉人」,歡迎大家關注,更多原創文章等著你哦,哈哈。 ### 2.2 獲取年月日 ```java LocalDate today = LocalDate.now(); int year = today.getYear(); int month = today.getMonthValue(); int day = today.getDayOfMonth(); System.out.println("year: " + year); System.out.println("month: " + month); System.out.println("day: " + day); ``` 輸出結果: > year: 2020 > > month: 6 > > day: 10 獲取月份終於返回1到12了,不像`java.util.Calendar`獲取月份返回的是0到11,獲取完還得加1。 ![](https://images.zwwhnly.com/picture/2020/06/snipaste_20200608_202729.png) ### 2.3 指定日期 ```java LocalDate specifiedDate = LocalDate.of(2020, 6, 1); System.out.println("specifiedDate: " + specifiedDate); ``` 輸出結果: > specifiedDate: 2020-06-01 如果確定月份,推薦使用另一個過載方法,使用列舉指定月份: ```java LocalDate specifiedDate = LocalDate.of(2020, Month.JUNE, 1); ``` ### 2.4 比較日期是否相等 ```java LocalDate localDate1 = LocalDate.now(); LocalDate localDate2 = LocalDate.of(2020, 6, 10); if (localDate1.equals(localDate2)) { System.out.println("localDate1 equals localDate2"); } ``` 輸出結果: > localDate1 equals localDate2 ### 2.5 獲取日期是本週/本月/本年的第幾天 ```java LocalDate today = LocalDate.now(); System.out.println("Today:" + today); System.out.println("Today is:" + today.getDayOfWeek()); System.out.println("今天是本週的第" + today.getDayOfWeek().getValue() + "天"); System.out.println("今天是本月的第" + today.getDayOfMonth() + "天"); System.out.println("今天是本年的第" + today.getDayOfYear() + "天"); ``` 輸出結果: > Today:2020-06-11 > > Today is:THURSDAY > > 今天是本週的第4天 > > 今天是本月的第11天 > > 今天是本年的第163天 ### 2.6 判斷是否為閏年 ```java LocalDate today = LocalDate.now(); System.out.println(today.getYear() + " is leap year:" + today.isLeapYear()); ``` 輸出結果: > 2020 is leap year:true ## 3. LocalTime ### 3.1 獲取時分秒 如果使用`java.util.Date`,那程式碼是下面這樣的: ```java Date date = new Date(); int hour = date.getHours(); int minute = date.getMinutes(); int second = date.getSeconds(); System.out.println("hour: " + hour); System.out.println("minute: " + minute); System.out.println("second: " + second); ``` 輸出結果: ![](https://images.zwwhnly.com/picture/2020/06/snipaste_20200608_210444.png) **注意事項:這幾個方法已經過期了,因此強烈不建議在專案中使用:** ![](https://images.zwwhnly.com/picture/2020/06/snipaste_20200608_210636.png) 如果使用`java.util.Calendar`,那程式碼是下面這樣的: ```java Calendar calendar = Calendar.getInstance(); // 12小時制 int hourOf12 = calendar.get(Calendar.HOUR); // 24小時制 int hourOf24 = calendar.get(Calendar.HOUR_OF_DAY); int minute = calendar.get(Calendar.MINUTE); int second = calendar.get(Calendar.SECOND); int milliSecond = calendar.get(Calendar.MILLISECOND); System.out.println("hourOf12: " + hourOf12); System.out.println("hourOf24: " + hourOf24); System.out.println("minute: " + minute); System.out.println("second: " + second); System.out.println("milliSecond: " + milliSecond); ``` 輸出結果: ![](https://images.zwwhnly.com/picture/2020/06/snipaste_20200608_204129.png) **注意事項:**獲取小時時,有2個選項,1個返回12小時制的小時數,1個返回24小時制的小時數,因為現在是晚上8點,所以`calendar.get(Calendar.HOUR)`返回8,而`calendar.get(Calendar.HOUR_OF_DAY)`返回20。 如果使用`java.time.LocalTime`,那程式碼是下面這樣的: ```java LocalTime localTime = LocalTime.now(); System.out.println("localTime:" + localTime); int hour = localTime.getHour(); int minute = localTime.getMinute(); int second = localTime.getSecond(); System.out.println("hour: " + hour); System.out.println("minute: " + minute); System.out.println("second: " + second); ``` 輸出結果: ![](https://images.zwwhnly.com/picture/2020/06/snipaste_20200608_211125.png) 可以看出,LocalTime只有時間沒有日期。 ## 4. LocalDateTime ### 4.1 獲取當前時間 ```java LocalDateTime localDateTime = LocalDateTime.now(); System.out.println("localDateTime:" + localDateTime); ``` 輸出結果: > localDateTime: 2020-06-11T11:03:21.376 ### 4.2 獲取年月日時分秒 ```java LocalDateTime localDateTime = LocalDateTime.now(); System.out.println("localDateTime: " + localDateTime); System.out.println("year: " + localDateTime.getYear()); System.out.println("month: " + localDateTime.getMonthValue()); System.out.println("day: " + localDateTime.getDayOfMonth()); System.out.println("hour: " + localDateTime.getHour()); System.out.println("minute: " + localDateTime.getMinute()); System.out.println("second: " + localDateTime.getSecond()); ``` 輸出結果: ![](https://images.zwwhnly.com/picture/2020/06/snipaste_20200611_115748.png) ### 4.3 增加天數/小時 ```java LocalDateTime localDateTime = LocalDateTime.now(); System.out.println("localDateTime: " + localDateTime); LocalDateTime tomorrow = localDateTime.plusDays(1); System.out.println("tomorrow: " + tomorrow); LocalDateTime nextHour = localDateTime.plusHours(1); System.out.println("nextHour: " + nextHour); ``` 輸出結果: > localDateTime: 2020-06-11T11:13:44.979 > > tomorrow: 2020-06-12T11:13:44.979 > > nextHour: 2020-06-11T12:13:44.979 `LocalDateTime`還提供了新增年、周、分鐘、秒這些方法,這裡就不一一列舉了: ![](https://images.zwwhnly.com/picture/2020/06/snipaste_20200611_111631.png) ### 4.4 減少天數/小時 ```java LocalDateTime localDateTime = LocalDateTime.now(); System.out.println("localDateTime: " + localDateTime); LocalDateTime yesterday = localDateTime.minusDays(1); System.out.println("yesterday: " + yesterday); LocalDateTime lastHour = localDateTime.minusHours(1); System.out.println("lastHour: " + lastHour); ``` 輸出結果: > localDateTime: 2020-06-11T11:20:38.896 > > yesterday: 2020-06-10T11:20:38.896 > > lastHour: 2020-06-11T10:20:38.896 類似的,`LocalDateTime`還提供了減少年、周、分鐘、秒這些方法,這裡就不一一列舉了: ![](https://images.zwwhnly.com/picture/2020/06/snipaste_20200611_112257.png) ### 4.5 獲取時間是本週/本年的第幾天 ```java LocalDateTime localDateTime = LocalDateTime.now(); System.out.println("localDateTime: " + localDateTime); System.out.println("DayOfWeek: " + localDateTime.getDayOfWeek().getValue()); System.out.println("DayOfYear: " + localDateTime.getDayOfYear()); ``` 輸出結果: > localDateTime: 2020-06-11T11:32:31.731 > > DayOfWeek: 4 > > DayOfYear: 163 ## 5. DateTimeFormatter JDK8中推出了`java.time.format.DateTimeFormatter`來處理日期格式化問題,《阿里巴巴Java開發手冊》中也是建議使用`DateTimeFormatter`代替`SimpleDateFormat`。 ### 5.1 格式化LocalDate ```java LocalDate localDate = LocalDate.now(); System.out.println("ISO_DATE: " + localDate.format(DateTimeFormatter.ISO_DATE)); System.out.println("BASIC_ISO_DATE: " + localDate.format(DateTimeFormatter.BASIC_ISO_DATE)); System.out.println("ISO_WEEK_DATE: " + localDate.format(DateTimeFormatter.ISO_WEEK_DATE)); System.out.println("ISO_ORDINAL_DATE: " + localDate.format(DateTimeFormatter.ISO_ORDINAL_DATE)); ``` 輸出結果: ![](https://images.zwwhnly.com/picture/2020/06/snipaste_20200611_115406.png) 如果提供的格式無法滿足你的需求,你還可以像以前一樣自定義格式: ```java LocalDate localDate = LocalDate.now(); System.out.println("yyyy/MM/dd: " + localDate.format(DateTimeFormatter.ofPattern("yyyy/MM/dd"))); ``` 輸出結果: > yyyy/MM/dd: 2020/06/11 ### 5.2 格式化LocalTime ```java LocalTime localTime = LocalTime.now(); System.out.println(localTime); System.out.println("ISO_TIME: " + localTime.format(DateTimeFormatter.ISO_TIME)); System.out.println("HH:mm:ss: " + localTime.format(DateTimeFormatter.ofPattern("HH:mm:ss"))); ``` 輸出結果: > 14:28:35.230 > > ISO_TIME: 14:28:35.23 > > HH:mm:ss: 14:28:35 ### 5.3 格式化LocalDateTime ```java LocalDateTime localDateTime = LocalDateTime.now(); System.out.println(localDateTime); System.out.println("ISO_DATE_TIME: " + localDateTime.format(DateTimeFormatter.ISO_DATE_TIME)); System.out.println("ISO_DATE: " + localDateTime.format(DateTimeFormatter.ISO_DATE)); ``` 輸出結果: > 2020-06-11T14:33:18.303 > > ISO_DATE_TIME: 2020-06-11T14:33:18.303 > > ISO_DATE: 2020-06-11 ## 6. 型別相互轉換 ### 6.1 Instant轉Date JDK8中,`Date`新增了`from()`方法,將`Instant`轉換為`Date`,程式碼如下所示: ```java Instant instant = Instant.now(); System.out.println(instant); Date dateFromInstant = Date.from(instant); System.out.println(dateFromInstant); ``` 輸出結果: > 2020-06-11T06:39:34.979Z > > Thu Jun 11 14:39:34 CST 2020 ### 6.2 Date轉Instant JDK8中,`Date`新增了`toInstant`方法,將`Date`轉換為`Instant`,程式碼如下所示: ```java Date date = new Date(); Instant dateToInstant = date.toInstant(); System.out.println(date); System.out.println(dateToInstant); ``` 輸出結果: > Thu Jun 11 14:46:12 CST 2020 > > 2020-06-11T06:46:12.112Z ### 6.3 Date轉LocalDateTime ```java Date date = new Date(); Instant instant = date.toInstant(); LocalDateTime localDateTimeOfInstant = LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); System.out.println(date); System.out.println(localDateTimeOfInstant); ``` 輸出結果: > Thu Jun 11 14:51:07 CST 2020 > > 2020-06-11T14:51:07.904 ### 6.4 Date轉LocalDate ```java Date date = new Date(); Instant instant = date.toInstant(); LocalDateTime localDateTimeOfInstant = LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); LocalDate localDate = localDateTimeOfInstant.toLocalDate(); System.out.println(date); System.out.println(localDate); ``` 輸出結果: > Thu Jun 11 14:59:38 CST 2020 > > 2020-06-11 可以看出,`Date`是先轉換為`Instant`,再轉換為`LocalDateTime`,然後通過`LocalDateTime`獲取`LocalDate`。 ### 6.5 Date轉LocalTime ```java Date date = new Date(); Instant instant = date.toInstant(); LocalDateTime localDateTimeOfInstant = LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); LocalTime toLocalTime = localDateTimeOfInstant.toLocalTime(); System.out.println(date); System.out.println(toLocalTime); ``` 輸出結果: > Thu Jun 11 15:06:14 CST 2020 > > 15:06:14.531 可以看出,`Date`是先轉換為`Instant`,再轉換為`LocalDateTime`,然後通過`LocalDateTime`獲取`LocalTime`。 ### 6.6 LocalDateTime轉Date ```java LocalDateTime localDateTime = LocalDateTime.now(); Instant toInstant = localDateTime.atZone(ZoneId.systemDefault()).toInstant(); Date dateFromInstant = Date.from(toInstant); System.out.println(localDateTime); System.out.println(dateFromInstant); ``` 輸出結果: > 2020-06-11T15:12:11.600 > > Thu Jun 11 15:12:11 CST 2020 ### 6.7 LocalDate轉Date ```java LocalDate today = LocalDate.now(); LocalDateTime localDateTime = localDate.atStartOfDay(); Instant toInstant = localDateTime.atZone(ZoneId.systemDefault()).toInstant(); Date dateFromLocalDate = Date.from(toInstant); System.out.println(dateFromLocalDate); ``` 輸出結果: > Thu Jun 11 00:00:00 CST 2020 ### 6.8 LocalTime轉Date ```java LocalDate localDate = LocalDate.now(); LocalTime localTime = LocalTime.now(); LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime); Instant instantFromLocalTime = localDateTime.atZone(ZoneId.systemDefault()).toInstant(); Date dateFromLocalTime = Date.from(instantFromLocalTime); System.out.println(dateFromLocalTime); ``` 輸出結果: > Thu Jun 11 15:24:18 CST 2020 ## 7. 總結 JDK8推出了全新的日期時間類,如`Instant`、`LocaleDate`、`LocalTime`、`LocalDateTime`、`DateTimeFormatter`,設計比之前更合理,也是執行緒安全的。 《阿里巴巴Java開發規範》中也推薦使用`Instant`代替`Date`,`LocalDateTime` 代替 `Calendar`,`DateTimeFormatter` 代替 `SimpleDateFormat`。 因此,如果條件允許,建議在專案中使用,沒有使用的,可以考慮升級下。