三、全新的日期時間 API

3.0 前言

不論作什麼系統的開發,處理時間都是咱們沒法繞不過去的一道坎。java

在 Java 世界裏,java.util.Date 類應該是大多數人最先學習和使用的處理日期時間 API。這個類誕生於 Java 1.0 ,負責承載日期信息、日期之間的轉換、不一樣日期格式的顯示等多項功能,同時因爲該類的設計過於簡單粗暴,以致於其中的大部分方法在一年後發佈的 Java 1.1 中就廢棄了,並引入新的日曆類 Calendar 做爲部分功能的接班人,並明確了工做職責:git

  • 使用Calendar類實現日期和時間字段之間轉換;
  • 使用DateFormat類來格式化和分析日期字符串;
  • 而Date只用來承載日期和時間信息。

惋惜的是,Calendar 類使用起來很複雜,一樣沒有被大衆接受。github

幾個吐槽比較多的點: 一、Java的日期/時間類的定義並不一致,在 java.util 和 java.sql 的包中都有日期類,此外用於格式化和解析的類在 java.text 包中定義; 二、java.util.Date 同時包含日期和時間,而 java.sql.Date 僅包含日期,將其歸入 java.sql 包並不合理。另外這兩個類都有相同的名字,這自己就是一個很是糟糕的設計; 三、api 難用,舉一個簡單的例子,還有更多比這個還難用的sql

Date date = new Date(2016,1,1);
System.out.println(date);   // 輸出Thu Feb 01 00:00:00 CST 3916

輸出結果的 年爲3912,是2016+1900,而月,並非咱們給的月份參數,而是參數加1。api

實際應用中,咱們幾乎都在使用一個開源的時間/日期庫 Joda-Time,這個庫流行到什麼程度呢? Java 8 幾乎原封不動的引入了這個庫,這也是最受Java 開發者追捧的變化之一。下面就主要介紹一下來自Joda-Time 的新Java API,java.time包。網絡

  • 掃盲: 1s = 10^3ms(毫秒) = 10^6μs(微秒) = 10^9ns(納秒)

3.1 絕對時間

3.1.1 Instant

一個Instant對象表明時間軸上的一個點。原點(元年)被規定爲1970年1月1日0點0分0秒,UNIX/POSIX 時間一樣也是這樣的約定。從原點開始,天天按照86400秒進行計算,向前向後分別以納秒爲單位。框架

靜態方法 Instant.now() 會返回當前瞬間的時間點。學習

3.1.2 Duration

一個Duration對象表示兩個瞬間(Instant)之間的時間量,一個經常使用的場景是計算某方法的執行時間:設計

Instant from = Instant.now();
// do something
Thread.sleep(1000);
Duration duration = Duration.between(from, Instant.now());
System.out.println(duration.toMillis());

3.1.3 解析

Instant(Duration)對象內部以一個 long 型變量維護秒值,而納秒值由另外一個int 保存。其中納秒值是基於秒值的,因此納秒值的int 變量永遠不會大於999,999,999。code

/**
 * The number of seconds from the epoch of 1970-01-01T00:00:00Z.
 */
private final long seconds;

/**
 * The number of nanoseconds, later along the time-line, from the seconds field.
 * This is always positive, and never exceeds 999,999,999.
 */
private final int nanos;

咱們輸出一個 Instant 對象的值來看一下,

Instant now = Instant.now();
System.out.println(now.getEpochSecond());  // 輸出秒值 1460821922
System.out.println(now.getNano());   // 輸出納秒值 349000000

即當前時間時間軸上的時間點是從原點前進1460821922349000000納秒的這個時刻。

3.2 本地日期/時間

使用絕對時間是最清晰、最簡單不過的了。可是問題在於人,若是咱們能夠跟別人說:「1460821922 全體集合,別晚了」,是最能準備表達時間點的,惋惜沒有人理解的了。

3.2.1 LocalDate、LocalTime 和 LocalDateTime

這三個類聯繫很密切,能夠一塊兒介紹。LocalDate 表示一個日期,LocalTime 表示一天中的時間、LocalDateTime 包含了LocalDate 和 LocalTime 兩部分,表示一個日期和時間。它們不關聯任什麼時候區信息。例如2015-08-27(用戶端、兼職端上線日期)就是一個本地日期。因爲沒有時區信息,因此它沒法與時間軸上一個準確的瞬時點對應。

你可使用now 或 of 方法建立這三個類的實例,以日期爲例:

LocalDate today = LocalDate.now();
LocalDate online = LocalDate.of(2015, 8, 27);
online = LocalDate.of(2015, Month.AUGUST, 27);

注意的是,Unix 和 java.util.Date 中年份從1900開始,月份從0開始。與之不符合生活的使用方式不一樣,如今咱們可使用人類習慣的表達方式,數字幾就是幾月份,即1表示一月份。

星期的表達上也有相似的區別,java.util.Calendar 中,每週從週日開始,即週六值爲7, 週日值爲1。 如今咱們一樣遵循人類表達方式,即每週從週一開始。

LocalDate wuyi = LocalDate.of(2016, 5, 1);
System.out.println(wuyi.getDayOfWeek());   // 輸出 SUNDAY
System.out.println(wuyi.getDayOfWeek().getValue()); // 輸出 7

3.2.2 Period

Period 與 Duration 相似,它表示一段逝去的年、月和日。在Period 內部使用三個變量維護這段逝去的時間,

/**
 * The number of years.
 */
private final int years;

/**
 * The number of months.
 */
private final int months;

/**
 * The number of days.
 */
private final int days;

因此,Period的 getYears、getMonths、getDays 方法實際獲取的值是兩個日期之間時段的一部分。

LocalDate wuyi = LocalDate.of(2016, 5, 1);
LocalDate shiyi = LocalDate.of(2016, 10, 1);
Period period = Period.between(wuyi, shiyi);

System.out.println(period.getYears());  // 輸出 0
System.out.println(period.getMonths()); // 輸出 5
System.out.println(period.getDays());   // 輸出 0

要獲取兩個日期(只能是LocalDate)之間的天數,通常不會經過Period.between獲取,由於這個方法返回的是幾年幾月幾天,意義並不大。而是使用 until 方法, Period的between其實也是用的until 方法。

LocalDate wuyi = LocalDate.of(2016, 5, 1);
LocalDate shiyi = LocalDate.of(2016, 10, 1);
System.out.println(wuyi.until(shiyi, ChronoUnit.DAYS)); // 輸出 153。即5.1到10.1有153天

3.3 時間調節器

時間調節器的工做已經基本被Qrartz、Spring Schedule這些框架給排擠的沒有用武之地了。舉例說一下,瞭解就好,有興趣能夠看TemporalAdjusters 的api。

例1:計算2016年1月第一個週二

LocalDate firstTuesday = LocalDate.of(2016, 1, 1).with(TemporalAdjusters.nextOrSame(DayOfWeek.TUESDAY));

例二:計算父親節和母親節 父親節:每一年六月的第三個星期日 母親節:每一年五月的第二個星期日

LocalDate faDay = LocalDate.of(2016, 6, 1).with(TemporalAdjusters.dayOfWeekInMonth(3, DayOfWeek.SUNDAY));
System.out.println(faDay);  // 2016-06-19

LocalDate maDay = LocalDate.of(2016, 5, 1).with(TemporalAdjusters.dayOfWeekInMonth(2, DayOfWeek.SUNDAY));
System.out.println(maDay); // 2016-05-08

3.4 有時區的時間

絕大多數應用場景下是不須要時區的,而且考慮時區會大大增長複雜性,好比處理夏令時。因此咱們不須要也並不推薦使用時區時間。一樣這裏僅做簡單介紹。

3.4.1 ZoneId、 ZoneDateTime

帶時區時間的類是ZoneDateTime,還有一個重要的類是時區標識ZoneId類,咱們能夠直接經過ZoneDateTime.of(..省略年月日時分秒., ZoneId) 方法建立一個ZoneDateTime對象,也能夠經過 LocalDateTime.now().atZone(ZoneId) 將一個本地類型轉爲帶時區的類型。

3.4.2 OffsetDateTime

這個表示帶有偏移量(基於UTC計算)的時間,可是沒有時區規則。該類專門用於一些不須要時區規則的應用程序,好比某些網絡協議。

3.五、格式化和解析

在Java 8 以前的版本,格式化時間咱們會使用 SimpleDateFormat, Java 8 中與之對應的是 DateTimeFormatter,該類提供三種方式格式化日期時間

  • 預約義的標準格式 (好比 BA 認證用到的GMT格式即 RFC_1123_DATE_TIME 標準格式,詳見API)
  • 語言環境相關的格式(沒用,略)
  • 自定義格式(通常不會定義太多,咱們只用到 yyyy-MM-dd HH:mm:ss)

貼一段代碼簡單看一下,更多使用方法能夠參考 redscarf-common 的 DateUtils 類。

LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME;

// 格式化
String formatNow = formatter.format(now);
System.out.println(formatNow);  // 輸出 2016-04-18T23:43:37.154

// 解析
LocalDateTime newNow = LocalDateTime.parse(formatNow, formatter);
System.out.println(newNow);    // 輸出 2016-04-18T23:43:37.154

3.6 與遺留類互操做

列舉幾個典型的轉換。若是用到其它類型間轉換能夠參考,而後本身嘗試

/*
 * java.time.Instant 和 java.util.Date 互轉
 */
Instant instant = Instant.now();
Date date = Date.from(instant);
instant = date.toInstant();

/*
 * java.time.Instant 和 java.sql.Timestamp 互轉
 */
Timestamp timestamp = Timestamp.from(instant);
instant = timestamp.toInstant();

/*
 * java.time.LocalDateTime 和 java.sql.Timestamp 互轉
 */
LocalDateTime localDateTime = LocalDateTime.now();
timestamp = Timestamp.valueOf(localDateTime);
localDateTime = timestamp.toLocalDateTime();

3.7 總結

3.7.1 格式對比

一張圖片總結一下各類類型的格式:

<img src="https://raw.githubusercontent.com/miaoxg/static/master/datetime.png" width = "500" /> ## 3.7.2 一點強調 全部類型都是不可變的,無論什麼操做,總會返回一個新的實例,原實例不變

3.7.3 一個記憶的小技巧

java.time 包的 API 提供了大量相關的方法,這些方法通常有一致的方法前綴:

of:靜態工廠方法。 parse:靜態工廠方法,關注於解析。 get:獲取某些東西的值。 is:檢查某些東西的是不是true。 with:不可變的setter等價物。 plus:加一些量到某個對象。 minus:從某個對象減去一些量。 to:轉換到另外一個類型。 at:把這個對象與另外一個對象組合起來,例如: date.atTime(time)。

3.7.4 最後的總結

這一部份內容難度很小,雖然寫的內容挺多,可是也沒作什麼真正講解說明,更多的是體系和 API 的介紹。目的是明確幾個重要類的概念和做用,讓咱們在開發中用的更順手。

相關文章
相關標籤/搜索