《java 8 實戰》讀書筆記 -第十二章 新的日期和時間 API

1、LocalDate、LocalTime、Instant、Duration 以及 Period

1.使用 LocalDate 和 LocalTime

建立一個LocalDate對象並讀取其值java

LocalDate date = LocalDate.of(2014, 3, 18); //2014-03-18
int year = date.getYear(); //2014
Month month = date.getMonth();//MARCH 
int day = date.getDayOfMonth();//18 
DayOfWeek dow = date.getDayOfWeek();//TUESDAY
int len = date.lengthOfMonth();//31
boolean leap = date.isLeapYear();//false

你還能夠經過傳遞一個TemporalField參數給get方法拿到一樣的信息。TemporalField是一個接口,它定義瞭如何訪問temporal對象某個字段的值。ChronoField枚舉實現了這一接口,因此你能夠很方便地使用get方法獲得枚舉元素的值,以下所示。
使用TemporalField讀取LocalDate的值編程

int year = date.get(ChronoField.YEAR); 
int month = date.get(ChronoField.MONTH_OF_YEAR); 
int day = date.get(ChronoField.DAY_OF_MONTH);

相似地,一天中的時間,好比13:45:20,可使用LocalTime類表示。相似地,一天中的時間,好比13:45:20,可使用LocalTime類表示。安全

LocalTime time = LocalTime.of(13, 45, 20); 
int hour = time.getHour(); 
int minute = time.getMinute(); 
int second = time.getSecond();

LocalDate和LocalTime均可以經過解析表明它們的字符串建立。使用靜態方法parse,你能夠實現這一目的:app

LocalDate date = LocalDate.parse("2014-03-18"); 
LocalTime time = LocalTime.parse("13:45:20");
你能夠向parse方法傳遞一個DateTimeFormatter。它是替換老版java.util.DateFormat的推薦替代品。

2.合併日期和時間

這個複合類名叫LocalDateTime,是LocalDate和LocalTime的合體。它同時表示了日期和時間,但不帶有時區信息,你能夠直接建立,也能夠經過合併日期和時間對象構造,以下所示。
直接建立LocalDateTime對象,或者經過合併日期和時間的方式建立ui

// 2014-03-18T13:45:20 
LocalDateTime dt1 = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45, 20); 
LocalDateTime dt2 = LocalDateTime.of(date, time); 
LocalDateTime dt3 = date.atTime(13, 45, 20); 
LocalDateTime dt4 = date.atTime(time); 
LocalDateTime dt5 = time.atDate(date);

注意,經過它們各自的atTime或者atDate方法,向LocalDate傳遞一個時間對象,或者向LocalTime傳遞一個日期對象的方式,你能夠建立一個LocalDateTime對象。你也可使用toLocalDate或者toLocalTime方法,從LocalDateTime中提取LocalDate或者LocalTime組件:spa

LocalDate date1 = dt1.toLocalDate(); 
LocalTime time1 = dt1.toLocalTime();

3.機器的日期和時間格式

新的java.time.Instant類對時間建模的方式,基本上它是以Unix元年時間(傳統的設定爲UTC時區1970年1月1日午夜時分)開始所經歷的
秒數進行計算。你能夠經過向靜態工廠方法ofEpochSecond傳遞一個表明秒數的值建立一個該類的實例。靜態工廠方法ofEpochSecond還有一個加強的重載版本,它接收第二個以納秒爲單位的參數值,對傳入做爲秒數的參數進行調整。重載的版本會調整納秒參數,確保保存的納秒分片在0到999 999 999之間。這意味着下面這些對ofEpochSecond工廠方法的調用會返回幾乎一樣的Instant對象:線程

Instant.ofEpochSecond(3);
Instant.ofEpochSecond(3, 0);
Instant.ofEpochSecond(2, 1_000_000_000); 
Instant.ofEpochSecond(4, -1_000_000_000);
從Java7開始,你就能夠在你的Java代碼裏把長整型數字好比10000000000寫成一個更具可讀性10_000_000_000。

Instant類也支持靜態工廠方法now,它可以幫你獲取當前時刻的時間戳。它包含的是由秒及納秒所構成的數字。因此,它沒法處理那些咱們很是容易理解的時間單位。好比下面這段語句:設計

int day = Instant.now().get(ChronoField.DAY_OF_MONTH);

它會拋出下面這樣的異常:日誌

java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: 
 DayOfMonth

4.定義 Duration 或 Period

你能夠建立兩個LocalTimes對象、兩個LocalDateTimes對象,或者兩個Instant對象之間的duration( 持續期間),以下所示:code

Duration d1 = Duration.between(time1, time2); 
Duration d1 = Duration.between(dateTime1, dateTime2); 
Duration d2 = Duration.between(instant1, instant2);

若是你試圖在這兩類對象之間建立duration,會觸發一個DateTimeException異常。此外,因爲Duration類主要用於以秒和納秒衡量時間的長短,你不能僅向between方法傳遞一個LocalDate對象作參數。

若是你須要以年、月或者日的方式對多個時間單位建模,可使用Period類。使用該類的工廠方法between,你可使用獲得兩個LocalDate之間的時長,以下所示:

Period tenDays = Period.between(LocalDate.of(2014, 3, 8), 
 LocalDate.of(2014, 3, 18));

Duration和Period類都提供了不少很是方便的工廠類,直接建立對應的實例;再也不是隻能以兩個temporal對象的差值的方式來定義它們的對象。
建立Duration和Period對象

Duration threeMinutes = Duration.ofMinutes(3); 
Duration threeMinutes = Duration.of(3, ChronoUnit.MINUTES); 
Period tenDays = Period.ofDays(10); 
Period threeWeeks = Period.ofWeeks(3); 
Period twoYearsSixMonthsOneDay = Period.of(2, 6, 1);

日期/時間類中表示時間間隔的通用方法
圖片描述
圖片描述

Temporal接口的實現類:
HijrahDate, Instant, JapaneseDate, LocalDate, LocalDateTime, LocalTime, MinguoDate, OffsetDateTime, OffsetTime, ThaiBuddhistDate, Year, YearMonth, ZonedDateTime

2、操縱、解析和格式化日期

若是你已經有一個LocalDate對象,想要建立它的一個修改版,最直接也最簡單的方法是使用withAttribute方法。withAttribute方法會建立對象的一個副本,並按照須要修改它的屬性。注意,下面的這段代碼中全部的方法都返回一個修改了屬性的對象。它們都不會修改原來的對象!

LocalDate date1 = LocalDate.of(2014, 3, 18); 
LocalDate date2 = date1.withYear(2011); 
LocalDate date3 = date2.withDayOfMonth(25); 
LocalDate date4 = date3.with(ChronoField.MONTH_OF_YEAR, 9);

採用更通用的with方法能達到一樣的目的,它接受的第一個參數是一個TemporalField對象,格式相似上面代碼最後一行。更確切地說,使用get和with方法,咱們能夠將Temporal對象值的讀取和修改區分開。若是Temporal對象不支持請求訪問的字段,它會拋出一個UnsupportedTemporalTypeException異常。

以相對方式修改LocalDate對象的屬性:

LocalDate date1 = LocalDate.of(2014, 3, 18); 
LocalDate date2 = date1.plusWeeks(1); 
LocalDate date3 = date2.minusYears(3); 
LocalDate date4 = date3.plus(6, ChronoUnit.MONTHS);

像LocalDate、LocalTime、LocalDateTime以及Instant這樣表示時間點的日期時間類提供了大量通用的方法
圖片描述

1.使用 TemporalAdjuster

有的時候,你須要進行一些更加複雜的操做,好比,將日期調整到下個週日、下個工做日,或者是本月的最後一天。這時,你可使用重載版本的with方法,向其傳遞一個提供了更多定製化選擇的TemporalAdjuster對象,更加靈活地處理日期。對於最多見的用例,日期和時間API已經提供了大量預約義的TemporalAdjuster。你能夠經過TemporalAdjuster類的靜態工廠方法訪問它們。

使用預約義的TemporalAdjuster

import static java.time.temporal.TemporalAdjusters.*; 
LocalDate date1 = LocalDate.of(2014, 3, 18); 
LocalDate date2 = date1.with(nextOrSame(DayOfWeek.SUNDAY)); //2014-03-23
LocalDate date3 = date2.with(lastDayOfMonth());//2014-03-31

TemporalAdjuster類中的工廠方法
圖片描述

TemporalAdjuster接口

@FunctionalInterface 
public interface TemporalAdjuster { 
 Temporal adjustInto(Temporal temporal); 
}

若是你想要使用Lambda表達式定義TemporalAdjuster對象,推薦使用TemporalAdjusters類的靜態工廠方法ofDateAdjuster,它接受一個UnaryOperator<LocalDate>

3.打印輸出及解析日期時間對象

新的java.time.format包就是特別爲這個目的而設計的。這個包中,最重要的類是DateTimeFormatter。建立格式器最簡單的方法是經過它的靜態工廠方法以及常量。像BASIC_ISO_DATE和 ISO_LOCAL_DATE 這樣的常量是 DateTimeFormatter 類的預約義實例。

LocalDate date = LocalDate.of(2014, 3, 18);
String s1 = date.format(DateTimeFormatter.BASIC_ISO_DATE); //20140318
String s2 = date.format(DateTimeFormatter.ISO_LOCAL_DATE);//2014-03-18

你可使用工廠方法parse達到重創該日期對象的目的:

LocalDate date1 = LocalDate.parse("20140318", 
 DateTimeFormatter.BASIC_ISO_DATE); 
LocalDate date2 = LocalDate.parse("2014-03-18", 
 DateTimeFormatter.ISO_LOCAL_DATE);

和老的java.util.DateFormat相比較,全部的DateTimeFormatter實例都是線程安全的。

按照某個模式建立DateTimeFormatter

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy"); 
LocalDate date1 = LocalDate.of(2014, 3, 18); 
String formattedDate = date1.format(formatter); 
LocalDate date2 = LocalDate.parse(formattedDate, formatter);

建立一個本地化的DateTimeFormatter

DateTimeFormatter italianFormatter = 
 DateTimeFormatter.ofPattern("d. MMMM yyyy", Locale.ITALIAN); 
LocalDate date1 = LocalDate.of(2014, 3, 18); 
String formattedDate = date.format(italianFormatter); // 18. marzo 2014 
LocalDate date2 = LocalDate.parse(formattedDate, italianFormatter);

DateTimeFormatterBuilder類還提供了更復雜的格式器,你能夠經過DateTimeFormatterBuilder本身編程實現咱們在上面代碼使用的italianFormatter,代碼清單以下。

DateTimeFormatter italianFormatter = new DateTimeFormatterBuilder() 
 .appendText(ChronoField.DAY_OF_MONTH) 
 .appendLiteral(". ") 
 .appendText(ChronoField.MONTH_OF_YEAR) 
 .appendLiteral(" ") 
 .appendText(ChronoField.YEAR) 
 .parseCaseInsensitive() 
 .toFormatter(Locale.ITALIAN);

3、處理不一樣的時區和曆法

新的java.time.ZoneId類是老版java.util.TimeZone的替代品。跟其餘日期和時間類同樣,ZoneId類也是沒法修改的。時區是按照必定的規則將區域劃分紅的標準時間相同的區間。在ZoneRules這個類中包含了40個這樣的實例。你能夠簡單地經過調用ZoneId的getRules()獲得指定時區的規則。每一個特定的ZoneId對象都由一個地區ID標識,好比:

ZoneId romeZone = ZoneId.of("Europe/Rome");

地區ID都爲「{區域}/{城市}」的格式,你能夠經過Java 8的新方法toZoneId將一個老的時區對象轉換爲ZoneId:

ZoneId zoneId = TimeZone.getDefault().toZoneId();

爲時間點添加時區信息

LocalDate date = LocalDate.of(2014, Month.MARCH, 18); 
ZonedDateTime zdt1 = date.atStartOfDay(romeZone); 
LocalDateTime dateTime = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45); 
ZonedDateTime zdt2 = dateTime.atZone(romeZone); 
Instant instant = Instant.now(); 
ZonedDateTime zdt3 = instant.atZone(romeZone);

經過ZoneId,你還能夠將LocalDateTime轉換爲Instant:

LocalDateTime dateTime = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45); 
Instant instantFromDateTime = dateTime.toInstant(romeZone);

你也能夠經過反向的方式獲得LocalDateTime對象:

Instant instant = Instant.now(); 
LocalDateTime timeFromInstant = LocalDateTime.ofInstant(instant, romeZone);

1.利用和 UTC/格林尼治時間的固定誤差計算時區

ZoneOffset類,它是ZoneId的一個子類,表示的是當前時間和倫敦格林尼治子午線時間的差別:

ZoneOffset newYorkOffset = ZoneOffset.of("-05:00");

注意,使用這種方式定義的ZoneOffset並未考慮任何日光時的影響,因此在大多數狀況下,不推薦使用。ZoneOffset也是ZoneId。
你甚至還能夠建立這樣的OffsetDateTime,它使用ISO-8601的歷法系統,以相對於UTC/格林尼治時間的誤差方式表示日期時間。

LocalDateTime dateTime = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45); 
OffsetDateTime dateTimeInNewYork = OffsetDateTime.of(date, newYorkOffset)

2.使用別的日曆系統

Java 8中另外還提供了4種其餘的日曆系統。這些日曆系統中的每個都有一個對應的日誌類,分別是ThaiBuddhistDate、MinguoDate 、 JapaneseDate 以及HijrahDate 。全部這些類以及 LocalDate 都實現了ChronoLocalDate接口。
以下所示:

LocalDate date = LocalDate.of(2014, Month.MARCH, 18); 
JapaneseDate japaneseDate = JapaneseDate.from(date);

或者,你還能夠爲某個Locale顯式地建立日曆系統,接着建立該Locale對應的日期的實例。新的日期和時間API中,Chronology接口建模了一個日曆系統,使用它的靜態工廠方法ofLocale,能夠獲得它的一個實例,代碼以下:

Chronology japaneseChronology = Chronology.ofLocale(Locale.JAPAN); 
ChronoLocalDate now = japaneseChronology.dateNow();

日期及時間API的設計者建議咱們使用LocalDate,儘可能避免使用ChronoLocalDate,緣由是開發者在他們的代碼中可能會作一些假設,而這些假設在不一樣的日曆系統中,有可能不成立。好比,有人可能會作這樣的假設,即一個月天數不會超過31天,一年包括12個月,或者一年中包
含的月份數目是固定的。因爲這些緣由,咱們建議你儘可能在你的應用中使用LocalDate,包括存儲、操做、業務規則的解讀;不過若是你須要將程序的輸入或者輸出本地化,這時你應該使用ChronoLocalDate類。

伊斯蘭教日曆
在Java 8新添加的幾種日曆類型中,HijrahDate(伊斯蘭教日曆)是最複雜一個,由於它會發生各類變化。Hijrah日曆系統構建於農曆月份繼承之上。Java 8提供了多種方法判斷一個月份,好比新月,在世界的哪些地方可見,或者說它只能首先可見於沙特阿拉伯。withVariant方法能夠用於選擇指望的變化。爲了支持HijrahDate這一標準,Java 8中還包括了烏姆庫拉(Umm Al-Qura)變量。
下面這段代碼做爲一個例子說明了如何在ISO日曆中計算當前伊斯蘭年中齋月的起始和終止日期:

HijrahDate ramadanDate = 
 HijrahDate.now().with(ChronoField.DAY_OF_MONTH, 1)
 .with(ChronoField.MONTH_OF_YEAR, 9); 
System.out.println("Ramadan starts on " + 
 IsoChronology.INSTANCE.date(ramadanDate) + 
 " and ends on " + 
 IsoChronology.INSTANCE.date( 
 ramadanDate.with( 
 TemporalAdjusters.lastDayOfMonth())));