在Java 1.0中,對日期和時間的支持只能依賴java.util.Date類。這個類只能以毫秒的精度表示時間。這個類還有不少糟糕的問題,好比年份的起始選擇是1900年,月份的起始從0開始。這意味着你要想表示2018年8月22日,就必須建立下面這樣的Date實例:java
Date date = new Date (118,7,22);
Wed Aug 22 00:00:00 CST 2018程序員
甚至Date類的toString方法返回的字符串也容易誤人。如今這個返回值甚至還包含了JVM的默認時區CST,但這不表示Date類在任何方面支持時區。數據庫
Java 1.1中,Date類中的不少方法被廢棄了,取而代之的是java.util.Calendar類。可是Calendar也有相似的問題和設計缺陷,致使使用這些方法寫出的代碼很是容易出錯。好比月份依舊是從0開始計算,不過去掉了1900年開始計算年份這一設計。更糟的是同時存在Date和Calendar這兩個類,也增長了程序員的困惑。到底該使用哪個類呢?此外,有的特性只有在某一個類有提供,好比用於以語言無關方式格式化和解析日期或時間的DateFormat方法就只有在Date類裏有。編程
DateFormat也有它本身的問題,首先他不是縣城安全的。這意味着兩個縣城若是嘗試使用同一個formatter解析日期,你可能沒法獲得預期的結果。安全
最後,Date和Calendar類都是可變的。app
Java 8 在java.time包中整合了不少Joda-Time的特性,java.time包中提供了不少新的類能夠幫助你解決問題:LocalDate、LocalTime、Instant、Duration和Period。ide
LocalDate函數式編程
首先該類的實例是一個不可變對象,它只提供了簡單的日期,並不含當天的時間。另外,它也不附帶任什麼時候區相關的信息。 你能夠經過靜態工廠of建立一個LocalDate實例,LocalDate提供了不少方法來讀取經常使用的值,好比年月日星期幾等:函數
LocalDate date = LocalDate.of(2018,8,22); int year = date.getYear(); //2018 Month month = date.getMonth(); //AUGUST(7) int day = date.getDayOfMonth(); //22 DayOfWeek dow = date.getDayOfWeek(); //WEDNESDAY int len = date.lengthOfMonth(); //31 boolean leap = date.isLeapYear(); //false LocalDate today = LocalDate.now(); //2018-08-22
傳遞TemporalField參數給 date的get方法也能夠拿到一樣的信息,TemporalField是一個藉口,它定義瞭如何訪問temporal對象某個字段的值。ChronoField枚舉實現了這一藉口,因此你能夠很方便的使用get方法獲得枚舉元素的值:ui
int yearofGet = date.get(ChronoField.YEAR); //2018 int monthofGet = date.get(ChronoField.MONTH_OF_YEAR); //8 int dayofGet = date.get(ChronoField.DAY_OF_MONTH); //22
LocalTime
與LocalDate相似,LocalTime表示時間,你可使用of重載的兩個工廠方法建立LocalTime實例,第一個重載是小時和分鐘,第二個重載還接受秒。也提供了getter方法訪問這些變量的值:
LocalTime time = LocalTime.of(17,55,55); int hour = time.getHour();//17 int minute = time.getMinute();//55 int second = time.getSecond();//55
LocalDate和LocalTime都支持從字符串建立,使用靜態方法parse:
LocalDate dateofString = LocalDate.parse("2018-08-22");
LocalTime timeofString = LocalTime.parse("15:53:52");
若是格式不正確,沒法被解析成合法的LocalDate或LocalTime對象。parse方法會拋出一個繼承自RuntimeException的DateTimeParseException異常。
LocalDateTime
這個複合類是LocalDate何LocalTime的合體,同時表示了日期和時間。但不帶有時區信息,你能夠直接建立,也能夠經過合併日期和時間對象構造。
LocalDateTime dt1 = dateofString.atTime(timeofString); LocalDateTime dt2 = time.atDate(date); LocalDateTime dt3 = LocalDateTime.of(date,time); LocalDateTime dt4 = LocalDateTime.of(2018,8,22,17,55,55); //2018-08-22T17:55:55
也能夠將LocalDateTime拆分爲獨立的LocalDate 和LocalTime:
LocalDate localDate = dt1.toLocalDate();
LocalTime localTime = dt1.toLocalTime();
Instant
做爲人,咱們習慣於星期幾、幾號、幾點、幾分這樣的方式理解日期和時間,可是對於計算機而言並不容易理解。java.time.Instant類對時間的建模方式是 以Unix元年時間(UTC時區1970年1月1日午夜時分)開始所經歷的描述進行計算。
可使用靜態工廠方法ofEpochSecond傳遞一個代筆哦啊秒數的值建立一個該類的實例。這個方法還有一個重載版本,它接受第二個以納秒爲單位的參數值,對傳入做爲秒數的參數進行調整。重載的版本會調整納秒參數,確保保存的納秒分片在0到999999999之間,這意味着下面這些對ofEpochSecond工廠方法的調用返回幾乎一樣的Instant對象:
Instant instant = Instant.ofEpochSecond(3); Instant instant2 = Instant.ofEpochSecond(3, 0); Instant instant3 = Instant.ofEpochSecond(2, 1_000_000_000); Instant instant4 = Instant.ofEpochSecond(4, -1_000_000_000);
1970-01-01T00:00:03Z
Instant類也支持靜態工廠方法now,它可以幫你獲取當前時刻的時間戳。Instant的設計初衷是爲了便於機器使用,它所包含的是由秒及納秒所構成的數字。因此,它沒法處理那些咱們很是容易理解的時間單位。
定義Duration或Period
計算日期時間差使用這兩個類
Duration d1 = Duration.between(time1, time2); Duration d2 = Duration.between(datetime1, datetime2); Duration d3 = Duration.between(instant,instant2);
因爲LocalDateTime和Instant是爲不一樣的目的而設計的,一個是爲了人閱讀的,一個是爲了機器處理的,因此不能將兩者混用。此外,因爲Duration類主要用於以秒和納秒衡量時間的長短,因此不能向between方法傳遞LocalDate。
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 twoYearsSixMothsOneDay = Period.of(2,6,1);
Period計算時間差
LocalDate today = LocalDate.now(); System.out.println("Today : " + today); LocalDate birthDate = LocalDate.of(1991, 1, 11); System.out.println("BirthDate : " + birthDate); Period p = Period.between(birthDate, today); System.out.printf("年齡 : %d 年 %d 月 %d 日", p.getYears(), p.getMonths(), p.getDays());
Today : 2018-08-22
BirthDate : 1991-01-11
年齡 : 27 年 7 月 11 日
Duration計算相差秒數
Instant inst1 = Instant.now(); System.out.println("Inst1 : " + inst1); Instant inst2 = inst1.plus(Duration.ofSeconds(10)); System.out.println("Inst2 : " + inst2); System.out.println("Difference in milliseconds : " + Duration.between(inst1, inst2).toMillis()); System.out.println("Difference in seconds : " + Duration.between(inst1, inst2).getSeconds());
Difference in milliseconds : 10000
Difference in seconds : 10
到目前爲止,這些日期和時間都是不可修改的,這是爲了更好的支持函數式編程,確保線程安全。若是你想在LocalDate實例上增長3天,或者將日期解析和輸入 dd/MM/yyyy這種格式。 將在下一節講解。
操做、解析和格式化日期
對已存在的LocalDate對象,建立它的修改版,最簡單的方式是使用withAttribute方法。withAttribute方法會建立對象的一個副本,並按照須要修改它的屬性。如下全部的方法都返回了一個修改屬性的對象,他們不會影響原來的對象。
LocalDate dd = LocalDate.of(2018,8,23); //2018-08-23 LocalDate dd1 = dd.withYear(2017); //2017-08-23 LocalDate dd2 = dd.withDayOfMonth(22); //2018-08-22 LocalDate dd4 = dd.withMonth(10); //2018-10-23 LocalDate dd3 = dd.with(ChronoField.MONTH_OF_YEAR,9); //2018-09-23
除了withAttribute詳細的年月日,也能夠採用通用的with方法,第一個參數是TemporalField對象,第二個參數是修改的值。它也能夠操縱LocalDate對象:
LocalDate dd5 = dd.plusWeeks(1); //加一週 LocalDate dd6 = dd.minusYears(3); //減去三年 LocalDate dd7 = dd.plus(6,ChronoUnit.MONTHS); //加6月
plus和minus方法和上面的with相似,他們都聲明於Temporal接口中。像LocalDate、LocalTime、LocalDateTime以及Instant這些表示日期和時間的類提供了大量的通用方法:
一、from 靜態方法 依據傳入的Temporal對象建立是實例
二、now 靜態方法 依據系統時鐘建立Temporal對象
三、of 靜態方法 由Temporal對象的某個部分建立對象的實例
四、 parse 靜態方法 由字符串建立Temporal對象的實例
五、atOffset 將Temporal對象和某個時區偏移相結合
六、atZone 將Temporal 對象和某個時區相結合
七、format 使用某個指定的格式將Temporal對象轉換爲字符串(Instant類不提供此方法)
八、get 讀取Temporal對象的某一部分的值
九、minus 建立對象的一個副本,而後將當前的Temporal對象的值減去必定的時長建立該副本
十、plus 建立對象的一個副本,而後將當前Temporal對象的值加上必定的時長建立該副本
十一、with 以該對象爲模板,對某些狀態進行修改建立該對象的副本。
TemporalAdjuster
操縱更復雜的日期,好比將日期調整到下個週日、下個工做日,或者是本月的最後一天。這時可使用with的重載版本,向其傳遞一個提供了更多定製化選擇的TemporalAdjuster對象,更加靈活的處理日期。
import static java.time.temporal.TemporalAdjusters.*; LocalDate dd = LocalDate.of(2018,8,23); LocalDate dd1 = dd.with(dayOfWeekInMonth(2,DayOfWeek.FRIDAY)); //同一個月中,第二個星期五 2018-08-10 LocalDate dd2 = dd.with(firstDayOfMonth()); //當月的第一天 2018-08-01 LocalDate dd3 = dd.with(firstDayOfNextMonth()); //下月的第一天 2018-09-01 LocalDate dd4 = dd.with(firstDayOfNextYear()); //明年的第一天 2019-01-01 LocalDate dd5 = dd.with(firstDayOfYear()); //當年的第一天 2018-01-01 LocalDate dd6 = dd.with(firstInMonth(DayOfWeek.MONDAY)); //當月第一個星期一 2018-08-06 LocalDate dd7 = dd.with(lastDayOfMonth()); //當月的最後一天 2018-08-31 LocalDate dd8 = dd.with(lastDayOfYear()); //當年的最後一天 2018-12-31 LocalDate dd9 = dd.with(lastInMonth(DayOfWeek.SUNDAY)); //當月最後一個星期日 2018-08-26 LocalDate dd10 = dd.with(previous(DayOfWeek.MONDAY)); //將日期向前調整到第一個符合星期一 2018-08-20 LocalDate dd11 = dd.with(next(DayOfWeek.MONDAY)); //將日期向後調整到第一個符合星期一 2018-08-27 LocalDate dd12 = dd.with(previousOrSame(DayOfWeek.FRIDAY)); //將日期向前調整第一個符合星期五,若是該日期已經符合,直接返回該對象 2018-08-17 LocalDate dd13 = dd.with(nextOrSame(DayOfWeek.FRIDAY)); //將日期向後調整第一個符合星期五,若是該日期已經符合,直接返回該對象 2018-08-24
TemporalAdjuster能夠進行復雜的日期操做,若是沒有找到符合的預約義方法,能夠本身建立一個,TemporalAdjuster接口只聲明瞭一個方法因此他說一個函數式接口:
@FunctionalInterface public interface TemporalAdjuster { Temporal adjustInto(Temporal temporal); }
這意味着TemporalAdjuster接口的實現須要定義如何將一個Temporal對象轉換爲另外一個Temporal對象。好比設計一個NextWorkingDay類,實現計算下一個工做日,過濾掉週六和週日節假日。
public class NextWorkingDay implements TemporalAdjuster { @Override public Temporal adjustInto(Temporal temporal) { DayOfWeek dow = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK)); int dayToAdd = 1; //若是當前日期 星期一到星期五之間返回下一天 if(dow == DayOfWeek.FRIDAY) dayToAdd = 3; //若是當前日期 週六或週日 返回下週一 else if(dow == DayOfWeek.SATURDAY) dayToAdd = 2; return temporal.plus(dayToAdd, ChronoUnit.DAYS); } }
因爲TemporalAdjuster是函數式接口,因此你只能以Lambda表達式的方式向Adjuster接口傳遞行爲:
date.with(t->{
//上面一坨
})
爲了能夠重用,TemporalAdjuster對象推薦使用TemporalAdjusters類的靜態工廠方法ofDateAdjuster:
LocalDate dd = LocalDate.of(2018, 8, 23); TemporalAdjuster nextWorkingDay = TemporalAdjusters.ofDateAdjuster( temporal -> { DayOfWeek dow = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK)); int dayToAdd = 1; //若是當前日期 星期一到星期五之間返回下一天 if (dow == DayOfWeek.FRIDAY) dayToAdd = 3; //若是當前日期 週六或週日 返回下週一 else if (dow == DayOfWeek.SATURDAY) dayToAdd = 2; return temporal.plus(dayToAdd, ChronoUnit.DAYS); } ); LocalDate next = dd.with(nextWorkingDay);
打印輸出及解析日期
處理日期和時間對象時,格式化以及解析日期-時間對象是另外一個很是重要的功能。新的java.time.format包就是爲了這個目的而設計的。這個包中最重要的類是DateTimeFormatter,建立格式器最簡單的方式是經過他的靜態工廠方法及常量。
String s1 = next.format(DateTimeFormatter.BASIC_ISO_DATE); //20180824 String s2 = next.format(DateTimeFormatter.ISO_LOCAL_DATE); //2018-08-24
除了解析爲字符串外,還能夠經過解析表明日期或時間的字符串從新建立該日期對象。
LocalDate date1 = LocalDate.parse("20180901",DateTimeFormatter.BASIC_ISO_DATE);
LocalDate date2 = LocalDate.parse("2018-09-02",DateTimeFormatter.ISO_LOCAL_DATE);
與老的java.util.DateFormat想比較,全部的DateTimeFormatter實例都是線程安全的。DateTimeFormatter類還支持一個靜態工廠方法,它按照某個特定的模式建立格式器。
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy"); LocalDate now = LocalDate.now(); String formatterDate = now.format(formatter); LocalDate nowparse = LocalDate.parse(formatterDate,formatter);
ofPattern能夠按照指定的格式進行解析成字符串,而後又調用了parse方法的重載 將該格式的字符串轉換成了 LocalDate對象。
ofPattern也提供了重載版本,使用它能夠建立某個Locale的格式器:
DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyy年MMMMd號", Locale.CHINA); LocalDate chinaDate = LocalDate.parse("2018-08-21"); String formatterDate2 = chinaDate.format(formatter2); //2018年八月21號 LocalDate chinaDate2 = LocalDate.parse(formatterDate2,formatter2);
DateFormatterBuilder類還提供了更復雜的格式器和更強大的解析功能:
DateTimeFormatter chinaFormatter = new DateTimeFormatterBuilder().appendText(ChronoField.YEAR) .appendLiteral("年") .appendText(ChronoField.MONTH_OF_YEAR) .appendText(ChronoField.DAY_OF_MONTH) .appendLiteral("號") .parseCaseInsensitive().toFormatter(Locale.CHINA);
處理不一樣的時區和曆法
以前所看到的日期和時間種類都不包含時區信息。時區的處理是新版日期和時間API新增長的重要功能,新的 java.time.ZoneId 類是老版 java.util.TimeZone 的替代品。
時區是按照必定的規則將區域劃分紅的標準時間相同的區間。在ZoneRules這個類中包含了40個這樣的實例。你可使用ZoneId的getRules()獲得指定時區的規則。每一個特定的ZoneId對象都由一個地區標識:
ZoneId romeZone = ZoneId.of("Europe/Rome"); //格式 歐洲/羅馬
地區ID都爲 「{區域}/{城市}」的格式,這些地區集合的設定都由英特網編號分配機構(IANA)的時區數據庫提供。你能夠經過java 8的新方法toZoneId將一個老的時區對象轉換爲ZoneId
ZoneId zoneId = TimeZone.getDefault().toZoneId();
一旦獲得一個ZoneId對象,就能夠將它與LocalDate、LocalDateTIme或者是Instant對象整合起來,構造爲一個ZonedDateTime實例,它表明了相對於指定時區的時間點,
LocalDate date = LocalDate.of(2018,8,22); ZonedDateTime zdt1 = date.atStartOfDay(romeZone); LocalDateTime dateTime = LocalDateTime.of(2018,8,23,13,48,00); ZonedDateTime zdt2 = dateTime.atZone(romeZone); Instant instant = Instant.now(); ZonedDateTime zdt3 = instant.atZone(romeZone);
ZonedDateTime = LocalDateTime(LocalDate + LocalTime) + ZoneId
經過ZoneId,你還能夠將LocalDateTime轉換爲Instant
LocalDateTime dateTime = LocalDateTime.of(2018,8,23,13,48,00); Instant instantFromDateTime = dateTime.toInstant(romeZone); Instant instant1 = Instant.now(); LocalDateTime timeFromInstant = LocalDateTime.ofInstant(romeZone);
利用和 UTC/格林尼治時間的固定誤差計算時區
另外一種比較經常使用的表達時區的方式就是利用當前時區和 UTC/格林尼治 的固定誤差,好比,紐約落後倫敦5小時。這種狀況下,你可使用ZoneOffset類,它是ZoneId的一個子類,表示的是當前時間和倫敦格林尼治子午時間的差別:
ZoneOffset newYorkOffset = ZoneOffset.of("-05:00");
這種方式不推薦使用,由於 -05:00 的誤差其實是對應的美國東部標準時間,並未考慮任何日光時的影響。
LocalDateTime dateTime1 = LocalDateTime.now();
OffsetDateTime dateTimeInNewYork1 = OffsetDateTime.of(dateTime1,newYorkOffset);
它使用ISO-8601的歷法系統,以相對於UTC時間的誤差方式表示日期時間。
使用別的日曆系統
ISO-8601日曆系統是世界文明日曆系統的事實標準。可是,java 8 中另外提供了4種其餘的日曆系統。這些日曆系統中的每個都有一個對應的日誌類,分別是ThaiBuddhistDate、MinguoDate、JapaneseDate以及HijrahDate。全部這些類以及LocalDate都實現了ChronoLocalDate接口,可以對公曆的日期進行建模。利用LocalDate對象,能夠建立這些類的實例。
小結:
一、Java 8以前的java.util.Date類以及其餘用於建模日期時間的雷有不少不一致及設計上的缺陷,包括易變性以及糟糕的偏移值、默認值和命名。
二、新版的日期和時間API中,日期-時間對象是不可變的。
三、新的API提供了兩種不一樣的時間表示方式,有效地區分了運行時人喝機器的不一樣需求。
四、操縱的日期不會影響老值,而是新生成一個實例。
五、TemporalAdjuster能夠更精確的操縱日期,還能夠自定義日期轉換器。
六、他們都是線程安全的