本系列文章經補充和完善,已修訂整理成書《Java編程的邏輯》,由機械工業出版社華章分社出版,於2018年1月上市熱銷,讀者好評如潮!各大網店和書店有售,歡迎購買,京東自營連接:http://item.jd.com/12299018.htmlhtml
本節繼續探討Java 8的新特性,主要是介紹Java 8對日期和時間API的加強,關於日期和時間,咱們在以前已經介紹過兩節了,32節介紹了Java 1.8之前的日期和時間API,主要的類是Date和Calendar,因爲它的設計有一些不足,業界普遍使用的是一個第三方的類庫Joda-Time,關於Joda-time,咱們在33節進行了介紹。Java 1.8學習了Joda-time,引入了一套新的API,位於包java.time下,本節,咱們就來簡要介紹這套新的API。java
咱們先從日期和時間的表示開始。git
表示日期和時間github
基本概念編程
咱們在32節介紹過日期和時間的幾個基本概念,這裏簡要回顧下。swift
Java 8中表示日期和時間的類有多個,主要的有:安全
類比較多,但概念更爲清晰了,下面咱們逐個來看下。微信
Instant函數
Instant表示時刻,獲取當前時刻,代碼爲:學習
Instant now = Instant.now();
能夠根據Epoch Time (紀元時)建立Instant,好比,另外一種獲取當前時刻的代碼能夠爲:
Instant now = Instant.ofEpochMilli(System.currentTimeMillis());
咱們知道,Date也表示時刻,Instant和Date能夠經過紀元時相互轉換,好比,轉換Date爲Instant,代碼爲:
public static Instant toInstant(Date date) { return Instant.ofEpochMilli(date.getTime()); }
轉換Instant爲Date,代碼爲:
public static Date toDate(Instant instant) { return new Date(instant.toEpochMilli()); }
Instant有不少基於時刻的比較和計算方法,大多比較直觀,咱們就不列舉了。
LocalDateTime
LocalDateTime表示與時區無關的日期和時間信息,獲取系統默認時區的當前日期和時間,代碼爲:
LocalDateTime ldt = LocalDateTime.now();
還能夠直接用年月日等信息構建LocalDateTime,好比,表示2017年7月11日20點45分5秒,代碼能夠爲:
LocalDateTime ldt = LocalDateTime.of(2017, 7, 11, 20, 45, 5);
LocalDateTime有不少方法,能夠獲取年月日時分秒等日曆信息,好比:
public int getYear() public int getMonthValue() public int getDayOfMonth() public int getHour() public int getMinute() public int getSecond()
還能夠獲取星期幾等信息,好比:
public DayOfWeek getDayOfWeek()
DayOfWeek是一個枚舉,有七個取值,從DayOfWeek.MONDAY到DayOfWeek.SUNDAY。
LocalDateTime不能直接轉爲時刻Instant,轉換須要一個參數ZoneOffset,ZoneOffset表示相對於格林尼治的時區差,北京是+08:00,好比,轉換一個LocalDateTime爲北京的時刻,方法爲:
public static Instant toBeijingInstant(LocalDateTime ldt) { return ldt.toInstant(ZoneOffset.of("+08:00")); }
給定一個時刻,使用不一樣時區解讀,日曆信息是不一樣的,Instant有方法根據時區返回一個ZonedDateTime:
public ZonedDateTime atZone(ZoneId zone)
默認時區是ZoneId.systemDefault(),能夠這樣構建ZoneId:
//北京時區 ZoneId bjZone = ZoneId.of("GMT+08:00")
ZoneOffset是ZoneId的子類,能夠根據時區差構造。
LocalDate/LocalTime
能夠認爲,LocalDateTime由兩部分組成,一部分是日期LocalDate,另外一部分是時間LocalTime,它們的用法也很直觀,好比:
//表示2017年7月11日 LocalDate ld = LocalDate.of(2017, 7, 11); //當前時刻按系統默認時區解讀的日期 LocalDate now = LocalDate.now(); //表示21點10分34秒 LocalTime lt = LocalTime.of(21, 10, 34); //當前時刻按系統默認時區解讀的時間 LocalTime time = LocalTime.now();
LocalDateTime由LocalDate和LocalTime構成,LocalDate加上時間能夠構成LocalDateTime,LocalTime加上日期能夠構成LocalDateTime,好比:
LocalDateTime ldt = LocalDateTime.of(2017, 7, 11, 20, 45, 5); LocalDate ld = ldt.toLocalDate(); //2017-07-11 LocalTime lt = ldt.toLocalTime(); // 20:45:05 //LocalDate加上時間,結果爲2017-07-11 21:18:39 LocalDateTime ldt2 = ld.atTime(21, 18, 39); //LocalTime加上日期,結果爲2016-03-24 20:45:05 LocalDateTime ldt3 = lt.atDate(LocalDate.of(2016, 3, 24));
ZonedDateTime
ZonedDateTime表示特定時區的日期和時間,獲取系統默認時區的當前日期和時間,代碼爲:
ZonedDateTime zdt = ZonedDateTime.now();
LocalDateTime.now()也是獲取默認時區的當前日期和時間,有什麼區別呢?LocalDateTime內部不會記錄時區信息,只會單純記錄年月日時分秒等信息,而ZonedDateTime除了記錄日曆信息,還會記錄時區,它的其餘大部分構建方法都須要顯式傳遞時區,好比:
//根據Instant和時區構建ZonedDateTime public static ZonedDateTime ofInstant(Instant instant, ZoneId zone) //根據LocalDate, LocalTime和ZoneId構造 public static ZonedDateTime of(LocalDate date, LocalTime time, ZoneId zone)
ZonedDateTime能夠直接轉換爲Instant,好比:
ZonedDateTime ldt = ZonedDateTime.now();
Instant now = ldt.toInstant();
格式化/解析字符串
Java 8中,主要的格式化類是java.time.format.DateTimeFormatter,它是線程安全的,看個例子:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); LocalDateTime ldt = LocalDateTime.of(2016,8,18,14,20,45); System.out.println(formatter.format(ldt));
輸出爲:
2016-08-18 14:20:45
將字符串轉化爲日期和時間對象,能夠使用對應類的parse方法,好比:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); String str = "2016-08-18 14:20:45"; LocalDateTime ldt = LocalDateTime.parse(str, formatter);
設置和修改時間
修改時期和時間有兩種方式,一種是直接設置絕對值,另外一種是在現有值的基礎上進行相對增減操做,Java 8的大部分類都支持這兩種方式,另外,與Joda-Time同樣,Java 8的大部分類都是不可變類,修改操做是經過建立並返回新對象來實現的,原對象自己不會變。
咱們來看一些例子。
調整時間爲下午3點20
代碼示例爲:
LocalDateTime ldt = LocalDateTime.now();
ldt = ldt.withHour(15).withMinute(20).withSecond(0).withNano(0);
還能夠爲:
LocalDateTime ldt = LocalDateTime.now();
ldt = ldt.toLocalDate().atTime(15, 20);
三小時五分鐘後
示例代碼爲:
LocalDateTime ldt = LocalDateTime.now();
ldt = ldt.plusHours(3).plusMinutes(5);
LocalDateTime有不少plusXXX和minusXXX方法,用於相對增長和減小時間。
今天0點
能夠爲:
LocalDateTime ldt = LocalDateTime.now();
ldt = ldt.with(ChronoField.MILLI_OF_DAY, 0);
ChronoField是一個枚舉,裏面定義了不少表示日曆的字段,MILLI_OF_DAY表示在一天中的毫秒數,值從0到(24 * 60 * 60 * 1,000) - 1。
還能夠爲:
LocalDateTime ldt = LocalDateTime.of(LocalDate.now(), LocalTime.MIN);
LocalTime.MIN表示"00:00"
也能夠爲:
LocalDateTime ldt = LocalDate.now().atTime(0, 0);
下週二上午10點整
能夠爲:
LocalDateTime ldt = LocalDateTime.now(); ldt = ldt.plusWeeks(1).with(ChronoField.DAY_OF_WEEK, 2) .with(ChronoField.MILLI_OF_DAY, 0).withHour(10);
下一個週二上午10點整
上面下週二指定是下週,若是是下一個週二呢?這與當前是周幾有關,若是當前是週一,則下一個週二就是明天,而其餘狀況則是下週,代碼能夠爲:
LocalDate ld = LocalDate.now(); if(!ld.getDayOfWeek().equals(DayOfWeek.MONDAY)){ ld = ld.plusWeeks(1); } LocalDateTime ldt = ld.with(ChronoField.DAY_OF_WEEK, 2).atTime(10, 0);
針對這種複雜一點的調整,Java 8有一個專門的接口TemporalAdjuster,這是一個函數式接口,定義爲:
public interface TemporalAdjuster { Temporal adjustInto(Temporal temporal); }
Temporal是一個接口,表示日期或時間對象,Instant,LocalDateTime,LocalDate等都實現了它,這個接口就是對日期或時間進行調整,還有一個專門的類TemporalAdjusters,裏面提供了不少TemporalAdjuster的實現,好比,針對下一個周幾的調整,方法是:
public static TemporalAdjuster next(DayOfWeek dayOfWeek)
針對上面的例子,代碼能夠爲:
LocalDate ld = LocalDate.now();
LocalDateTime ldt = ld.with(TemporalAdjusters.next(DayOfWeek.TUESDAY)).atTime(10, 0);
這個next方法是怎麼實現的呢?看代碼:
public static TemporalAdjuster next(DayOfWeek dayOfWeek) { int dowValue = dayOfWeek.getValue(); return (temporal) -> { int calDow = temporal.get(DAY_OF_WEEK); int daysDiff = calDow - dowValue; return temporal.plus(daysDiff >= 0 ? 7 - daysDiff : -daysDiff, DAYS); }; }
它內部封裝了一些條件判斷和具體調整,提供了更爲易用的接口。
TemporalAdjusters中還有不少方法,部分方法以下:
public static TemporalAdjuster firstDayOfMonth() public static TemporalAdjuster lastDayOfMonth() public static TemporalAdjuster firstInMonth(DayOfWeek dayOfWeek) public static TemporalAdjuster lastInMonth(DayOfWeek dayOfWeek) public static TemporalAdjuster previous(DayOfWeek dayOfWeek) public static TemporalAdjuster nextOrSame(DayOfWeek dayOfWeek)
這些方法的含義比較直觀,就不解釋了,它們主要是封裝了日期和時間調整的一些基本操做,更爲易用。
明天最後一刻
代碼能夠爲:
LocalDateTime ldt = LocalDateTime.of(LocalDate.now().plusDays(1), LocalTime.MAX);
或者爲:
LocalDateTime ldt = LocalTime.MAX.atDate(LocalDate.now().plusDays(1));
本月最後一天最後一刻
代碼能夠爲:
LocalDateTime ldt = LocalDate.now()
.with(TemporalAdjusters.lastDayOfMonth())
.atTime(LocalTime.MAX);
lastDayOfMonth()是怎麼實現的呢?看代碼:
public static TemporalAdjuster lastDayOfMonth() { return (temporal) -> temporal.with(DAY_OF_MONTH, temporal.range(DAY_OF_MONTH).getMaximum()); }
這裏使用了range方法,從它的返回值能夠獲取對應日曆單位的最大最小值,展開來,本月最後一天最後一刻的代碼還能夠爲:
long maxDayOfMonth = LocalDate.now().range(ChronoField.DAY_OF_MONTH).getMaximum(); LocalDateTime ldt = LocalDate.now() .withDayOfMonth((int)maxDayOfMonth) .atTime(LocalTime.MAX);
下個月第一個週一的下午5點整
代碼能夠爲:
LocalDateTime ldt = LocalDate.now() .plusMonths(1) .with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY)) .atTime(17, 0);
時間段的計算
Java 8中表示時間段的類主要有兩個,Period和Duration,Period表示日期之間的差,用年月日表示,不能表示時間,Duration表示時間差,用時分秒錶等表示,也能夠用天表示,一天嚴格等於24小時,不能用年月表示,下面看一些例子。
計算兩個日期之間的差
看個Period的例子:
LocalDate ld1 = LocalDate.of(2016, 3, 24); LocalDate ld2 = LocalDate.of(2017, 7, 12); Period period = Period.between(ld1, ld2); System.out.println(period.getYears() + "年" + period.getMonths() + "月" + period.getDays() + "天");
輸出爲:
1年3月18天
根據生日計算年齡
示例代碼能夠爲:
LocalDate born = LocalDate.of(1990,06,20); int year = Period.between(born, LocalDate.now()).getYears();
計算遲到分鐘數
假定早上9點是上班時間,過了9點算遲到,遲到要統計遲到的分鐘數,怎麼計算呢?看代碼:
long lateMinutes = Duration.between( LocalTime.of(9,0), LocalTime.now()).toMinutes();
與Date/Calendar對象的轉換
Java 8的日期和時間API沒有提供與老的Date/Calendar相互轉換的方法,但在實際中,咱們多是須要的,前面介紹了,Date能夠與Instant經過毫秒數相互轉換,對於其餘類型,也能夠經過毫秒數/Instant相互轉換。
好比,將LocalDateTime按默認時區轉換爲Date,代碼能夠爲:
public static Date toDate(LocalDateTime ldt){ return new Date(ldt.atZone(ZoneId.systemDefault()) .toInstant().toEpochMilli()); }
將ZonedDateTime轉換爲Calendar,代碼能夠爲:
public static Calendar toCalendar(ZonedDateTime zdt) { TimeZone tz = TimeZone.getTimeZone(zdt.getZone()); Calendar calendar = Calendar.getInstance(tz); calendar.setTimeInMillis(zdt.toInstant().toEpochMilli()); return calendar; }
Calendar保持了ZonedDateTime的時區信息。
將Date按默認時區轉爲LocalDateTime,代碼能夠爲:
public static LocalDateTime toLocalDateTime(Date date) { return LocalDateTime.ofInstant( Instant.ofEpochMilli(date.getTime()), ZoneId.systemDefault()); }
將Calendar轉爲ZonedDateTime,代碼能夠爲:
public static ZonedDateTime toZonedDateTime(Calendar calendar) { ZonedDateTime zdt = ZonedDateTime.ofInstant( Instant.ofEpochMilli(calendar.getTimeInMillis()), calendar.getTimeZone().toZoneId()); return zdt; }
小結
本節簡要介紹了Java 8中的日期和時間API,相比之前版本的Date和Calendar,它引入了更多的類,但概念更爲清晰了,更爲強大和易用了,Java 8學習了Joda-Time的不少概念和實現,與咱們以前介紹的Joda-Time很像。
從91節討論Lambda表達式到本節,關於Java 8的主要內容,咱們就介紹完了。
同時,關於整個Java編程的基礎部分,經過共95節的內容,咱們也基本探討完了,下一節是本系列文章的最後一篇,咱們對所有95節內容進行簡要梳理。
(與其餘章節同樣,本節全部代碼位於 https://github.com/swiftma/program-logic,位於包shuo.laoma.java8.c95下)
----------------
未完待續,查看最新文章,敬請關注微信公衆號「老馬說編程」(掃描下方二維碼),從入門到高級,深刻淺出,老馬和你一塊兒探索Java編程及計算機技術的本質。用心原創,保留全部版權。