不論作什麼系統的開發,處理時間都是咱們沒法繞不過去的一道坎。java
在 Java 世界裏,java.util.Date 類應該是大多數人最先學習和使用的處理日期時間 API。這個類誕生於 Java 1.0 ,負責承載日期信息、日期之間的轉換、不一樣日期格式的顯示等多項功能,同時因爲該類的設計過於簡單粗暴,以致於其中的大部分方法在一年後發佈的 Java 1.1 中就廢棄了,並引入新的日曆類 Calendar 做爲部分功能的接班人,並明確了工做職責:git
惋惜的是,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包。網絡
一個Instant對象表明時間軸上的一個點。原點(元年)被規定爲1970年1月1日0點0分0秒,UNIX/POSIX 時間一樣也是這樣的約定。從原點開始,天天按照86400秒進行計算,向前向後分別以納秒爲單位。框架
靜態方法 Instant.now() 會返回當前瞬間的時間點。學習
一個Duration對象表示兩個瞬間(Instant)之間的時間量,一個經常使用的場景是計算某方法的執行時間:設計
Instant from = Instant.now(); // do something Thread.sleep(1000); Duration duration = Duration.between(from, Instant.now()); System.out.println(duration.toMillis());
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納秒的這個時刻。
使用絕對時間是最清晰、最簡單不過的了。可是問題在於人,若是咱們能夠跟別人說:「1460821922 全體集合,別晚了」,是最能準備表達時間點的,惋惜沒有人理解的了。
這三個類聯繫很密切,能夠一塊兒介紹。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
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天
時間調節器的工做已經基本被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
絕大多數應用場景下是不須要時區的,而且考慮時區會大大增長複雜性,好比處理夏令時。因此咱們不須要也並不推薦使用時區時間。一樣這裏僅做簡單介紹。
帶時區時間的類是ZoneDateTime,還有一個重要的類是時區標識ZoneId類,咱們能夠直接經過ZoneDateTime.of(..省略年月日時分秒., ZoneId) 方法建立一個ZoneDateTime對象,也能夠經過 LocalDateTime.now().atZone(ZoneId) 將一個本地類型轉爲帶時區的類型。
這個表示帶有偏移量(基於UTC計算)的時間,可是沒有時區規則。該類專門用於一些不須要時區規則的應用程序,好比某些網絡協議。
在Java 8 以前的版本,格式化時間咱們會使用 SimpleDateFormat, Java 8 中與之對應的是 DateTimeFormatter,該類提供三種方式格式化日期時間
貼一段代碼簡單看一下,更多使用方法能夠參考 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
列舉幾個典型的轉換。若是用到其它類型間轉換能夠參考,而後本身嘗試
/* * 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();
一張圖片總結一下各類類型的格式:
<img src="https://raw.githubusercontent.com/miaoxg/static/master/datetime.png" width = "500" /> ## 3.7.2 一點強調 全部類型都是不可變的,無論什麼操做,總會返回一個新的實例,原實例不變
java.time 包的 API 提供了大量相關的方法,這些方法通常有一致的方法前綴:
of:靜態工廠方法。 parse:靜態工廠方法,關注於解析。 get:獲取某些東西的值。 is:檢查某些東西的是不是true。 with:不可變的setter等價物。 plus:加一些量到某個對象。 minus:從某個對象減去一些量。 to:轉換到另外一個類型。 at:把這個對象與另外一個對象組合起來,例如: date.atTime(time)。
這一部份內容難度很小,雖然寫的內容挺多,可是也沒作什麼真正講解說明,更多的是體系和 API 的介紹。目的是明確幾個重要類的概念和做用,讓咱們在開發中用的更順手。