Java8新特性--日期和時間API

如何正確處理時間

現實生活的世界裏,時間是不斷向前的,若是向前追溯時間的起點,多是宇宙出生時,又或是是宇宙出現以前, 
但確定是咱們目前沒法找到的,咱們不知道如今距離時間原點的精確距離。因此咱們要表示時間, 
就須要人爲定義一個原點。java

原點被規定爲,格林威治時間(GMT)1970年1月1日的午夜 爲起點,之於爲啥是GMT時間,大概是由於本初子午線在那的緣由吧。sql

Java中的時間

若是你跟你朋友說:「咱們 1484301456 一塊兒去吃飯,別遲到!」,而你朋友能立刻理解你說的時間,表示時間就會很簡單, 
只須要一個long值來表示原點的偏移量,這是個絕對時間,在世界範圍內都適用。但實際上咱們不能立刻理解這串數字, 
並且咱們須要不一樣的時間單位來表示時間的跨度,好比一個季度是3個月,一個月有30天等。 
你能夠跟朋友約好「明天這個時候再見面」,你朋友很容易理解明天的意思,但要是沒有’天’這個單位, 
他就須要在那串數字上加上86400(一天是86400秒)。數據庫

Java三次引入處理時間的API,JDK1.0中包含了一個Date類,但大多數方法在java1.1引入Calendear類以後被棄用了。 
它的實例都是可變的,並且它的API很難使用,好比月份是從0開始這種反人類的設置。api

java8引入的java.time API 已經糾正了以前的問題。它已經徹底實現了JSR310規範。安全

java8時間API介紹及使用

在新的時間API中,Instant表示一個精確的時間點,DurationPeriod表示兩個時間點之間的時間量。 
LocalDate表示日期,即xx年xx月xx日,即不包括時間也不帶時區。LocalTimeLocalDate相似, 
但只包含時間。LocalDateTime則包含日期和時間。ZoneDateTime表示一個帶時區的時間。 
DateTimeFormatter提供格式化和解析功能。下面詳細的介紹使用方法。markdown

Instant

Instant表示一個精確的時間,時間數軸就是由無數個時間點組成,數軸的原點就是上面提 
到的1970-1-1 00:00:00Instant由兩部分組成,一是從原點開始到指定時間點的秒數s, 
二是距離該秒數s的納秒數。ide

使用靜態方法Instant.now()能夠獲取當前的時間點,該方法默認使用的是UTC(協調世界時——由原子鐘提供)時間,可使用equeal 和 compareTo來比較兩個時間點的值。post

計算某段代碼執行時間可使用下面的方式:測試

Instant start = Instant.now(); doSomething(); Instant end = Instant.now(); Duration timeElapsed = Duration.between(start, end); long millis = timeElapsed.toMillis(); System.out.println("millis = " + millis);

Duration對象表示兩個時間點之間的距離,經過相似toMillis() toDays() getSeconds()等方法, 
獲得各類時間單位表示的Duration對象。若是確實須要使用納秒來作一些計算,能夠調用toNanos() 
得到一個long類型的值,該值表示距離原點的納秒值。大概300年的納秒值會致使long值溢出。spa

Duration內部使用一個long類型來保存秒鐘的值,使用一個int來保存納秒的值,與Instant相似, 
這個納秒保存的是距離該秒鐘的納秒值.

Instant與Duration均可以進行一些運算,來調整表示的時間,好比:plus() minus 方法, 
表示增長或減小一段時間,plusSeconds() minusSeconds() plusXxx()等表示增長或減小相應時間單位的一段時間。

Duration能夠進行multipliedBy()乘法和dividedBy()除法運算。negated()作取反運算,即1.2秒取反後爲-1.2秒。

很是重要的是,Instant 和 Duration類都是不可變的,他們的全部方法都返回一個新的實例。不可變類有不少優勢: 
不可變類使用起來不容易出錯,其本質上是線程安全的,對象能夠被自由的共享,而不用擔憂被某個方法修改。

LocalDate(本地日期)

上面介紹的Instant是一個絕對的準確時間點,是人類不容易理解的時間,如今介紹人類使用的時間。

LocalDate 表示像 2017-01-01這樣的日期。它包含有年份、月份、當月天數,它不不包含一天中的時間, 
以及時區信息。因爲上面的這些特色,因此LocalDate不能表示一個準確的時間點,即Instant。

有不少時間的計算是不須要時區的,並且有一些狀況下使用時區會致使一些問題,例如你在中國設置了一個 
2017-01-01 UT+8:00 的放假提醒,但以後你去了美國,到了2017-01-01 UT+8:00時間時你收到了提醒, 
可是此時美國還沒到放假的時間。

API的設計者推薦使用不帶時區的時間,除非真的但願表示絕對的時間點。

可使用靜態方法now()of()建立LocalDate。java.util.Date使用0做爲月份的開始,年份從1990年開始算起, 
而新的API中徹底是用生活中同樣的方式來表示年和月份。

//獲取當前日期 LocalDate now = LocalDate.now(); //2017-01-01 LocalDate newYear = LocalDate.of(2017, 1, 1);

能夠經過一些方法對日期作一些運算。

//三天後 now.plusDays(3); //一週後 now.plusWeeks(1) //兩天前 now.minusDays(2) //增長一個月不會出現2017-02-31 而是會返回該月的最後一個有效日期,即2017-02-28 LocalDate.of(2017, 1, 31).plusMonths(1) LocalDate feb = LocalDate.of(2017, 2, 1); //withXxx()表示以該日期爲基礎,修改年、月、日字段,並返回一個新的日期 //2019-2-1 feb.withYear(2019); //2017-1-10 feb.withDayOfYear(10); //2017-2-10 feb.withDayOfMonth(10);

上面講過Duration表示的是Instant對應的時間段,LocalDate對應的表示時間段的是Period, 
Period內部使用三個int值分表表示年、月、日。 
Duration和Period都是TemporalAmount接口的實現,該接口表示時間量。

LocalDate 也能夠增長或減小一段時間:

//2019-02-01 feb.plus(Period.ofYears(2)); //2015-02-01 feb.minus(Period.ofYears(2);

使用until得到兩個日期之間的Period對象

//輸出P9D,表示相差9天 feb.until(LocalDate.of(2017, 2, 10));//輸出---> P9D

 

LocalDate提供了一些測試方法: 
isBefore isAfter比較兩個LocalDate,isLeapYear判斷是不是閏年。

LocalDate還提供了各類getXxx方法來返回所須要的數據,其中getDayOfWeek()返回DayOfWeek枚舉。 
DayOfWeek提供了plus minus來方便計算星期。

//SUNDAY
LocalDate.of(2017, 1, 1).getDayOfWeek(); //TUESDAY DayOfWeek.SUNDAY.plus(2);

除了LocalDate,Java8還提供了Year MonthDay YearMonth來表示部分日期,例如MonthDay能夠表示1月1日。

日期校訂器TemporalAdjuster

若是想找到某個月的第一個週五,或是某個月的最後一天,像這樣的日期就可使用TemporalAdjuster來進行日期調整。 
TemporalAdjusters提供一些靜態方法,返回經常使用的TemporalAdjuster

//2017-02-03的下一個星期五(包含當天) 2017-03-03 LocalDate.of(2017, 2, 3).with(TemporalAdjusters.nextOrSame(DayOfWeek.FRIDAY)); //2017-02-03的下一個星期五(不包含當天) 2017-02-10 LocalDate.of(2017, 2, 3).with(TemporalAdjusters.next(DayOfWeek.FRIDAY)); //2月中的第3個星期五 2017-02-17 LocalDate.of(2017, 2, 3).with(TemporalAdjusters.dayOfWeekInMonth(3, DayOfWeek.FRIDAY)); //2月中的最後一個星期五 2017-02-24 LocalDate.of(2017, 2, 3).with(TemporalAdjusters.lastInMonth(DayOfWeek.FRIDAY)); //下個月的第一天 LocalDate.of(2017, 2, 3).with(TemporalAdjusters.firstDayOfNextMonth());

 

這是上面例子對應的當月日曆

LocalTime(本地時間)

LocalTime表示一天中的某個時間,例如18:00:00。LocaTime與LocalDate相似,他們也有類似的API。

須要注意的是:LocalTime自己不關心是AM仍是PM,而是格式化程序來負責這個事情。

LocalDateTime(本地日期時間)

LocalDateTime表示一個日期和時間,它適合用來存儲肯定時區的某個時間點。不適合跨時區的問題。

若須要處理跨時區的時間,須要使用ZonedDateTime.

ZonedDateTime(帶時區的時間)

時區(Time Zone)是地球上的區域使用同一個時間定義。1884年在華盛頓召開國際經度會議時, 
爲了克服時間上的混亂,規定將全球劃分爲24個時區。 
因爲實用上經常1個國家,或1個省份同時跨着2個或更多時區,爲了照顧到行政上的方便, 
常將1個國家或1個省份劃在一塊兒。因此時區並不嚴格按南北直線來劃分,而是按天然條件來劃分。

Java使用ZoneId來標識不一樣的時區.

//得到全部可用的時區  size=590 ZoneId.getAvailableZoneIds(); //獲取默認ZoneId對象 ZoneId defZoneId = ZoneId.systemDefault(); //獲取指定時區的ZoneId對象 ZoneId shanghaiZoneId = ZoneId.of("Asia/Shanghai"); //ZoneId.SHORT_IDS返回一個Map<String, String> 是時區的簡稱與全稱的映射。下面能夠獲得字符串 Asia/Shanghai String shanghai = ZoneId.SHORT_IDS.get("CTT");

我在測試的時候一共有590個時區可用,但要知道,這個時區的個數不是固定的。

IANA(Internet Assigned Numbers Authority,因特網撥號管理局)維護着一份全球全部已知的時區數據庫, 
每一年會更新幾回,主要處理夏令時規則的改變。Java使用了IANA的數據庫。

建立ZonedDateTime

//2017-01-20T17:35:20.885+08:00[Asia/Shanghai] ZonedDateTime.now(); //2017-01-01T12:00+08:00[Asia/Shanghai] ZonedDateTime.of(2017, 1, 1, 12, 0, 0, 0, ZoneId.of("Asia/Shanghai")); //使用一個準確的時間點來建立ZonedDateTime,下面這個代碼會獲得當前的UTC時間,會比北京時間早8個小時 ZonedDateTime.ofInstant(Instant.now(), ZoneId.of("UTC"));

LocalDateTime轉換爲ZonedDateTime

//atZone方法能夠將LocalDateTime轉換爲ZonedDateTime,下面的方法將時區設置爲UTC。 //假設如今的LocalDateTime是2017-01-20 17:55:00 轉換後的時間爲2017-01-20 17:55:00[UTC] LocalDateTime.now().atZone(ZoneId.of("UTC")); //使用靜態of方法建立zonedDateTime ZonedDateTime.of(LocalDateTime.now(), ZoneId.of("UTC"));

ZonedDateTime的一些方法

ZonedDateTime的許多方法與LocalDateTime、LocalDate、LocalTime相似,下面簡單介紹幾個方法的使用。

ZonedDateTime utcDateTime = ZonedDateTime.of(2017, 1, 1, 12, 0, 0, 0, ZoneId.of("UTC"));//2017-01-01T12:00Z[UTC] //withZoneSameLocal返回指定時區中的一個新ZonedDateTime,替換時區爲指定時區,表示相同的本地時間的該時區時間。 utcDateTime.withZoneSameLocal(ZoneId.of("Asia/Shanghai"));//2017-01-01T12:00+08:00[Asia/Shanghai] //withZoneSameInstant返回指定時區中的一個新ZonedDateTime,替換爲指定時區,表示相同時間點的該時區時間。 utcDateTime.withZoneSameInstant(ZoneId.of("Asia/Shanghai"));//2017-01-01T20:00+08:00[Asia/Shanghai] 

 

有一些國家和地區使用夏令時,處理起來須要注意,但在中國沒有該問題, 
須要注意的是使用plus()時要用Period對象表示的時間量,而不該該用Duration表示的時間量, 
Duration不能處理夏令時。

utcDateTime.plus(Duration.ofDays(7));//不能處理夏令時 utcDateTime.plus(Period.ofDays(7));//正確方式 

 

格式化和解析 DateTimeFormatter

DateTimeFormatter是不可變類,而SimpleDateFormat是非線程安全的,是一個常見的坑。

格式化

DateTimeFormatter使用了三種格式化方法來打印日期和時間

  • 預約義的標準格式

DateTimeFormatter預約義了一些格式,能夠直接調用format方法

//2017-01-01 DateTimeFormatter.ISO_LOCAL_DATE.format(LocalDate.of(2017, 1, 1)) //20170101 DateTimeFormatter.BASIC_ISO_DATE.format(LocalDate.of(2017, 1, 1)); //2017-01-01T09:10:00 DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(LocalDateTime.of(2017, 1, 1, 9, 10, 0)); 

 

語言環境相關的格式化風格

根據當前操做系統語言環境,有SHORET MEDIUM LONG FULL 四種不一樣的風格來格式化。 
能夠經過DateTimeFormatter的靜態方法ofLocalizedDate ofLocalizedTime ofLocalizedDateTime

//2017年1月1日 星期日 DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).format(LocalDate.of(2017, 1, 1)); //上午09時10分00秒 DateTimeFormatter.ofLocalizedTime(FormatStyle.LONG).format(LocalTime.of(9, 10, 0)); //2017-2-27 22:32:03 DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).format(LocalDateTime.now());

 

上面的方法都使用的是默認的語言環境,若是想改語言環境,須要使用withLocale方法來改變。

//Feb 27, 2017 10:34:36 PM DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withLocale(Locale.US).format(LocalDateTime.now());

 使用自定義模式格式化

//2017-02-27 22:48:52 DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now())

 

解析

//使用的ISO_LOCAL_DATE格式解析  2017-01-01 LocalDate.parse("2017-01-01"); //使用自定義格式解析 2017-01-01T08:08:08 LocalDateTime.parse("2017-01-01 08:08:08", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));

遺留代碼相互操做

Instant 相似於java.util.Date

ZonedDateTime相似於java.util.GregorianCalendar

//Date --> Instant Instant timestamp = new Date().toInstant(); //Instant --> Date Date.from(Instant.now()); //GregorianCalendar --> ZonedDateTime new GregorianCalendar().toZonedDateTime(); //ZonedDateTime --> GregorianCalendar GregorianCalendar.from(zonedDateTime); //2017-02-27T21:16:13.647 LocalDateTime.ofInstant(timestamp, ZoneId.of(ZoneId.SHORT_IDS.get("PST"))); //Calendar --> Instant //2017-02-28T05:16:13.656Z Calendar.getInstance().toInstant(); 
相關文章
相關標籤/搜索