JDK1.8-日期使用

1. 概述

??JDK1.8中對日期的改動是特別大的,基本上是引入了一套全新的API 。因為由于原來老舊的日期API一直被人詬病,比如java.util.Date,java.util.Calendar等,并且原來所有的日期類都是可變且線程不安全的,導致許多人要么自己手動封裝,要么轉(zhuǎn)去使用Joda Time等這類優(yōu)秀的第三方工具包。所以,在JDK1.8中,JDK官方在Joda Time等優(yōu)秀工具包基礎(chǔ)上,重新提供了一份相當不錯的日期API。并且,在JDK1.8中,java.time包中的類是不可變且線程安全的。

??JAVA8中的日期API是JSR-310的實現(xiàn),并且是工作在ISO-8601日歷系統(tǒng)基礎(chǔ)上的,但我們也可以在非ISO的日歷上。
??JDK8的日期API大致分為以下幾個包:

  1. java.time包:JDK8中的基礎(chǔ)包,所有常用的基礎(chǔ)類都是這個包的一部分,如LocalDateLocalTime,LocalDateTime等等,所有這些類都是不可變且線程安全的;
  2. java.time.chrono包:這個包為非ISO的日歷系統(tǒng)定義了一些API,我們可以在借助這個包中的一些類擴展我們自己的日歷系統(tǒng);
  3. java.time.format包:這個包很明顯了,格式化和解析日期時間對象,一般java.time包中的類都差不多能滿足我們的需求了,如果有需要,可以調(diào)用這個包下的類自定義解析方式;
  4. java.time.temporal包:這個包很有意思,封裝了一些獲取某個特定日期和時間的接口,比如某月的第一天或最后一天,并且這些方法都是屬于特別好認的方法。
  5. java.time.zone包:這個包就是時區(qū)相關(guān)的類了。

下面對ISO-8601簡單描述一下,參考自百度百科:

  1. ISO-8601: 國際標準化組織制定的日期和時間的表示方法,全稱為《數(shù)據(jù)存儲和交換形式·信息交換·日期和時間的表示方法》,簡稱為ISO-8601。
  2. 日的表示:小時、分和秒都用2位數(shù)表示,對UTC時間最后加一個大寫字母Z,其他時區(qū)用實際時間加時差表示。如UTC時間下午2點30分5秒表示為14:30:05Z或143005Z,當時的北京時間表示為22:30:05+08:00或223005+0800,也可以簡化成223005+08。
  3. 日期和時間的組合表示:合并表示時,要在時間前面加一大寫字母T,如要表示北京時間2004年5月3日下午5點30分8秒,可以寫成2004-05-03T17:30:08+08:00或20040503T173008+08。

??在JDK1.8中,我們經(jīng)常使用的大約有如下幾個類:LocalDate, LocalTime, LocalDateTime, DateTimeFormatter等,所以我們主要看一下這幾個類的相關(guān)方法。

2. 日期API

2.1 LocalDate

java.time.LocalDate這個類,是用來表示日期的,也僅包含日期,用起來十分方便,方法也十分簡單。我們來看一些小例子:

public static void testDate() {
    // 1. 獲取當前日期(年月日) -----打印輸出-----2018-01-29
    LocalDate localDate = LocalDate.now();
    System.out.println(localDate.toString());
    // 2. 根據(jù)年月日構(gòu)建Date ----打印輸出-----2018-01-30
    LocalDate localDate1 = LocalDate.of(2018, 01, 30);
    // 3. 字符串轉(zhuǎn)換日期,默認按照yyyy-MM-dd格式,也可以自定義格式 -----打印輸出-----2018-01-30
    LocalDate localDate2 = LocalDate.parse("2018-01-30");
    // 4. 獲取本月第一天 -----打印輸出-----2018-01-01
    LocalDate firstDayOfMonth = localDate.with(TemporalAdjusters.firstDayOfMonth());
    // 5. 獲取本月第二天  -----打印輸出-----2018-01-02
    LocalDate secondDayOfMonth = localDate.withDayOfMonth(2);
    // 6. 獲取本月最后一天 -----打印輸出-----2018-01-31
    LocalDate lastDayOfMonth = localDate.with(TemporalAdjusters.lastDayOfMonth());
    // 7. 明天 -----打印輸出----- 2018-01-30
    LocalDate tomorrowDay = localDate.plusDays(1L);
    // 8. 昨天 -----打印輸出----- 2018-01-28
    LocalDate yesterday = localDate.minusDays(1L);
    // 9. 獲取本年第12天 -----打印輸出----- 2018-04-30
    LocalDate day = localDate.withDayOfYear(120);
    // 10. 計算兩個日期間的天數(shù)
    long days = localDate.until(localDate1, ChronoUnit.DAYS);
    System.out.println(days);
    // 11. 計算兩個日期間的周數(shù)
    long weeks = localDate.until(localDate1, ChronoUnit.WEEKS);
    System.out.println(weeks);
}

??上面我只列舉了幾個常用的方法,但實際上LocalDate還有相當多的有意思的方法,基本上我們工作上遇到的與日期有關(guān)的操作,都可以通過LocadDate來實現(xiàn)。如果大家在遇到相關(guān)問題的時候,可以自行查看API去了解和使用。

2.2 LocalTime

同樣,和LocalDate相對應(yīng)的另一個類就是LocalTime,這個類恰好和LocalDate相反,它表示的全是時間,不包含日期。例子如下:

public static void testTime() {
    // 1. 獲取當前時間,包含毫秒數(shù) -----打印輸出----- 21:03:26.315
    LocalTime localTime = LocalTime.now();
    // 2. 構(gòu)建時間 -----打印輸出----- 12:15:30
    LocalTime localTime1 = LocalTime.of(12, 15, 30);
    // 3. 獲取當前時間,不包含毫秒數(shù) -----打印輸出----- 21:01:56
    LocalTime localTime2 = localTime.withNano(0);
    // 4. 字符串轉(zhuǎn)為時間,還可以有其他格式,比如12:15, 12:15:23.233
    // -----打印輸出----- 12:15:30
    LocalTime localTime3 = LocalTime.parse("12:15:30");
}
2.3 LocalDateTime

LocalDateTime就和原先的java.util.Date很像了,既包含日期,又包含時間,它經(jīng)常和DateTimeFormatter一起使用。

public static void testDateTime() {
    // 1. 獲取當前年月日 時分秒 -----打印輸出----- 2018-01-29T21:23:26.774
    LocalDateTime localDateTime = LocalDateTime.now();
    // 2. 通過LocalDate和LocalTime構(gòu)建 ----- 打印輸出----- 2018-01-29T21:24:41.738
    LocalDateTime localDateTime1 = LocalDateTime.of(LocalDate.now(), LocalTime.now());
    // 3. 構(gòu)建年月日 時分秒 -----打印輸出----- 2018-01-29T19:23:13
    LocalDateTime localDateTime2 = LocalDateTime.of(2018, 01, 29, 19, 23, 13);
    // 4. 格式化當前時間 ----打印輸出----- 2018/01/29
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
    System.out.println(formatter.format(localDateTime2));
}

使用LocalDateTime的with開頭的方法可以設(shè)置相應(yīng)的時間,小時,分鐘等,比如:

// 設(shè)置分鐘數(shù)
LocalDateTime localDateTime = LocalDateTime.now().withMinute(23);

需要注意的有兩點:

  1. LocalDateTime默認的格式是 2018-01-29T21:23:26.774 這種格式,這可能與我們經(jīng)常使用的格式不太符合,所以我們可以指定格式。
  2. DateTimeFormatter本身提供了許多靜態(tài)格式化常量,我們可以參考使用,如果不能滿足我們的需求的話,我們可以自定義;

LocalDateTime的toString方法:

@Override
public String toString() {
    return date.toString() + 'T' + time.toString();
}
  1. LocalDate,LocalTimeLocalDateTime這三個類基本上處理了大部分的日期,時間。而與這三個類經(jīng)常結(jié)合使用的還有如下幾個類:Year,Month,YearMonth,MonthDay,DayOfWeek。
  2. 并且,我們在LocalDate相關(guān)類的操作也可以通過Year,Month等來實現(xiàn)。
Year year =Year.now();
System.out.println(year.getValue()); // 2018
2.4 TemporalAdjusters

該類是一個計算用的類,提供了各種各樣的計算方法。比如某個月的第一天,某個月的最后一天,某一年的第一天,某一年的第幾天等各種計算方法。該類內(nèi)部實現(xiàn)基本上全都是通過JDK8的Lambda表達式來實現(xiàn)的。隨便舉一些例子,都很簡單。

LocalDate localDate = LocalDate.now();
        
// 1. 本月第一天
LocalDate firstDayOfMonth = localDate.with(TemporalAdjusters.firstDayOfMonth());
// 2. 本月最后一天
LocalDate lastDayOfMonth = localDate.with(TemporalAdjusters.lastDayOfMonth());
// 3. 本年第一天
LocalDate firstDayOfYear = localDate.with(TemporalAdjusters.firstDayOfYear());
// 4. 下個月第一天
LocalDate firstDayOfNextMonth = localDate.with(TemporalAdjusters.firstDayOfNextMonth());
// 5. 本年度最后一天
LocalDate lastDayOfYear = localDate.with(TemporalAdjusters.lastDayOfYear());

System.out.println(firstDayOfMonth);
System.out.println(lastDayOfMonth);
System.out.println(firstDayOfYear);
System.out.println(firstDayOfNextMonth);
System.out.println(lastDayOfYear);

打印輸出:

2018-01-01
2018-01-31
2018-01-01
2018-02-01
2018-12-31
2.5 Period和Duration

??Period是基于ISO-8601標準的日期系統(tǒng),用于計算兩個日期間的年,月,日的差值。比如'2年,3個月,4天';而Duration和Period很像,但Duration計算的是兩個日期間的秒,納秒的值,是一種更為精確的計算方式;而ISO-8601系統(tǒng)是當今世界大部分地區(qū)采用的現(xiàn)代日歷的陽歷系統(tǒng)。

LocalDate localDate = LocalDate.now();
LocalDate localDate1 = LocalDate.of(2018, 3, 28);
Period period = Period.between(localDate, localDate1);
System.out.println(period.getDays());
System.out.println(period.getMonths());

當然如果我們看一下Period的between方法實現(xiàn),就知道底層是通過LocalDate.until方法來實現(xiàn)的。同樣,Period的between方法也就等同于LocalDate.until方法。
但這里有一個問題,就是Period的getDays方法返回的不是兩個日期間總的天數(shù),有點像月計算后剩余的天數(shù),但也不完全是,所以不太清楚這個類實際的意義??磦€例子:

LocalDate localDate1 = LocalDate.of(2018, 01, 30);
LocalDate localDate2 = LocalDate.of(2018, 03, 01);
Period period = Period.between(localDate1, localDate2);
System.out.println(period.getDays());

打?。?code>1
我們把第一行代碼換一下值,再看一下:

LocalDate localDate1 = LocalDate.of(2018, 01, 29);

結(jié)果還是打印1。再換:

LocalDate localDate1 = LocalDate.of(2018, 01, 28);
LocalDate localDate1 = LocalDate.of(2018, 01, 27);

上面兩個分別打印1,2。

所以說,在沒搞清楚上面的方法之前,還是不要貿(mào)然使用,而如果要計算兩個日期間的總的天數(shù),可以用如下方法來計算:

System.out.println(localDate1.until(localDate2, ChronoUnit.DAYS));
System.out.println(ChronoUnit.DAYS.between(localDate1, localDate2));

而對于Duration來說,就更簡單了,計算兩個時間之間的秒數(shù),納秒數(shù):

LocalTime localTime1 = LocalTime.of(12, 12, 12);
LocalTime localTime2 = LocalTime.of(12, 13, 27);
Duration duration = Duration.between(localTime1, localTime2);
System.out.println(duration.getSeconds());  // 75

但有一點需要注意,Duration中必須要支持秒數(shù),如果沒有的話,將會報錯,如:

LocalDate localDate1 = LocalDate.of(2018, 02, 28);
LocalDate localDate2 = LocalDate.of(2018, 02, 27);
Duration duration = Duration.between(localDate1, localDate2);
System.out.println(duration.getSeconds());

將會直接提示:

Exception in thread "main" java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Seconds
    at java.time.LocalDate.until(LocalDate.java:1614)
    at java.time.Duration.between(Duration.java:475)
    at com.jdk8.DateTest.testPeriod(DateTest.java:40)
    at com.jdk8.DateTest.main(DateTest.java:23)
2.6 ChronoUnit

??這個是個枚舉類型,實現(xiàn)功能類型Period和Duration,但如果我們看它的底層實現(xiàn),就可以看到它基本是基于Duration來實現(xiàn)的。這個枚舉很簡單,我們直接看一些例子就可以了:

LocalDateTime oldDate = LocalDateTime.of(2017, Month.AUGUST, 31, 10, 20, 55);
LocalDateTime newDate = LocalDateTime.of(2018, Month.NOVEMBER, 9, 10, 21, 56);

System.out.println(oldDate);
System.out.println(newDate);

// count between dates
long years = ChronoUnit.YEARS.between(oldDate, newDate);
long months = ChronoUnit.MONTHS.between(oldDate, newDate);
long weeks = ChronoUnit.WEEKS.between(oldDate, newDate);
long days = ChronoUnit.DAYS.between(oldDate, newDate);
long hours = ChronoUnit.HOURS.between(oldDate, newDate);
long minutes = ChronoUnit.MINUTES.between(oldDate, newDate);
long seconds = ChronoUnit.SECONDS.between(oldDate, newDate);
long milis = ChronoUnit.MILLIS.between(oldDate, newDate);
long nano = ChronoUnit.NANOS.between(oldDate, newDate);

System.out.println("\n--- Total --- ");
System.out.println(years + " years");
System.out.println(months + " months");
System.out.println(weeks + " weeks");
System.out.println(days + " days");
System.out.println(hours + " hours");
System.out.println(minutes + " minutes");
System.out.println(seconds + " seconds");
System.out.println(milis + " milis");
System.out.println(nano + " nano");

打印結(jié)果:

2017-08-31T10:20:55
2018-11-09T10:21:56

--- Total --- 
1 years
14 months
62 weeks
435 days
10440 hours
626401 minutes
37584061 seconds
37584061000 milis
37584061000000000 nano
2.7 Clock

??時鐘系統(tǒng),用于查找當前時刻。通過指定一個時區(qū),我們可以獲取到當前的時刻,日期,時間。所以可以使用一個時鐘來代替System.currenttimemillis()TimeZone.getDefault()。
在應(yīng)用程序的最佳實踐是將時鐘傳遞給任何需要當前即刻的方法:

public class MyBean {
    private Clock clock;  // dependency inject
    ...
    public void process(LocalDate eventDate) {
      if (eventDate.isBefore(LocalDate.now(clock)) {
        ...
      }
    }
}

我們可以簡單測試一下:

// 系統(tǒng)默認
Clock systemDefaultClock = Clock.systemDefaultZone();
System.out.println("Current DateTime with system default clock: " + LocalDateTime.now(systemDefaultClock));
System.out.println(systemDefaultClock.millis());

// 世界協(xié)調(diào)時UTC
Clock systemUTCClock = Clock.systemUTC();
System.out.println("Current DateTime with UTC clock: " + LocalDateTime.now(systemUTCClock));
System.out.println(systemUTCClock.millis());

//芝加哥
Clock clock = Clock.system(ZoneId.of(ZoneId.SHORT_IDS.get("CST")));
System.out.println("Current DateTime with CST clock: " + LocalDateTime.now(clock));
System.out.println(clock.millis());

打印:

Current DateTime with system default clock: 2018-02-02T16:26:07.665
1517559967665
Current DateTime with UTC clock: 2018-02-02T08:26:07.665
1517559967665
Current DateTime with CST clock: 2018-02-02T02:26:07.667
1517559967667

并且我們可以使用 millis 方法來代替 System.currenttimemillis()。
更多有關(guān)Clock方法實例可以參考下面鏈接(沒找到JDK8的,不過可以參考JDK9的):
https://www.concretepage.com/java/java-9/java-clock

2.8 其他

其中還有一些類沒有詳細說明,比如:

  1. Instant,表示的是時間戳,用于記錄某一時刻的更改(JDK8之前的Timestamp);
  2. ZoneId 時區(qū)標志,比如用于標志歐洲/巴黎;
  3. ZoneOffset 時區(qū)偏移量,與UTC的偏移量;
  4. ZonedDateTime 與時區(qū)有關(guān)的日歷系統(tǒng),比如2007-12 03t10:15:30+01歐洲/巴黎;
  5. OffsetDateTime 用于與UTC偏移的日期時間,如如2007-12 03t10:15:30+01:00。

3. 轉(zhuǎn)換

3.1 java.util.Date與LocalDate,LocalTime,LocalDateTime替換

將Date轉(zhuǎn)換為LocalDate,LocalTime,LocalDateTime可以借助于ZonedDateTime和Instant,實現(xiàn)如下:

Date date = new Date();
System.out.println("current date: " + date);

// Date -> LocalDateTime
LocalDateTime localDateTime = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
System.out.println("localDateTime by Instant: " + localDateTime);

// Date -> LocalDate
LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
System.out.println("localDate by Instant: " + localDate);
// Date -> LocalTime
LocalTime localTime = date.toInstant().atZone(ZoneId.systemDefault()).toLocalTime();
System.out.println("localTime by Instant: " + localTime);

//2. Date -> LocalDateTime
localDateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
System.out.println("localDateTime by ofInstant: " + localDateTime);

output:

current date: Fri Feb 02 16:43:13 CST 2018
localDateTime by Instant: 2018-02-02T16:43:13.073
localDateTime by ofInstant: 2018-02-02T16:43:13.073
localDate by Instant: 2018-02-02
localTime by Instant: 16:43:13.073

由于JDK8實現(xiàn)了向下兼容,所以Date里在JDK8版本引入了2個方法,from
toInstant,所以我們可以借助這兩個方法來實現(xiàn)LocalDateTime到Date的轉(zhuǎn)換。將LocalDateTime轉(zhuǎn)為Date如下:

LocalDateTime localDateTime = LocalDateTime.now();
System.out.println("localDateTime: " + localDateTime);

// LocalDateTime -> Date
Date date = Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
System.out.println("LocalDateTime -> current date: " + date);

// LocalDate -> Date,時間默認都是00
LocalDate localDate = LocalDate.now();
date = Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
System.out.println("LocalDate -> current date: " + date);

output:

localDateTime: 2018-02-02T16:55:52.464
LocalDateTime -> current date: Fri Feb 02 16:55:52 CST 2018
LocalDate -> current date: Fri Feb 02 00:00:00 CST 2018

而單獨的LocalTime轉(zhuǎn)為Date沒什么意義,所以如果LocalTime要轉(zhuǎn)為Date,一般借助于LocalDate和LocalDateTime來實現(xiàn)就可以了。

3.2 日期與字符串的轉(zhuǎn)換

??日期與字符串的轉(zhuǎn)換比較簡單。先說轉(zhuǎn)換成日期格式,通過LocalDate,LocalTime,LocalDateTime的parse方法和DateTimeFormatter來實現(xiàn):

LocalDate localDate = LocalDate.parse("2018-09-09", DateTimeFormatter.ofPattern("yyyy-MM-dd"));

LocalDateTime localDateTime = LocalDateTime.parse("2018-09-10 12:12:12", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));

而將日期轉(zhuǎn)為字符串,可以通過format方法和DateTimeFormatter來實現(xiàn):

String localDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
String localDateTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));

// 也可以通過DateTimeFormatter的format方法
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
localDateTime = dateTimeFormatter.format(LocalDateTime.now());

毫秒的話,是SSS,這個可能不經(jīng)常使用,不過也要注意下:yyyy-MM-dd HH:mm:ss.SSS

3.3 時間戳與LocalDateTime轉(zhuǎn)換

將時間戳轉(zhuǎn)為LocalDateTime,可以借助Instant(其實就是時間戳)來實現(xiàn):

public static LocalDateTime convertToDate(long timestamp) {
   // ofEpochSecond 以秒為單位, ofEpochMilli 以毫秒為單位
   // Instant.ofEpochSecond(timestamp);
   Instant instant = Instant.ofEpochMilli(timestamp);
   return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
}

而將LocalDateTime轉(zhuǎn)為時間戳,則同樣可以借助Instant來實現(xiàn):

public static long convertToTimestamp() {
   LocalDateTime localDateTime = LocalDateTime.now();
   return localDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
}

而至于字符串與時間戳的轉(zhuǎn)換,借助LocalDateTime來實現(xiàn)即可。

總結(jié)

  1. java.time包下其實還有一些其他的類與方法,如查詢是否是閏年,日期比較,獲取某一年的某一天等等,我們可以等用到的時候再去查看不遲。并且我們可以將我們常用的方法封裝成util包供我們在使用的時候直接調(diào)用。
  2. JDK8提供的這套API接口基本上封裝了我們平時常用的所有與日期時間的相關(guān)操作,所以如果升級了JDK8,在處理日期和時間的時候盡量多用新的API。
  3. JDK8中新的日期不但命名優(yōu)雅,通俗易懂,而且提供了向下兼容的能力,可以無縫連接。
  4. 如果用到了Period,注意它的getDays方法。

本文實例參考自:
Java 8 – Period and Duration examples
https://docs.oracle.com/javase/8/docs/api/
Java8 日期/時間(Date Time)API指南

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容