DateFormat方法也有它本身的問題。好比,它不是線程安全的。這意味着兩個線程若是嘗試使用同一個formatter解析日期,你可能會獲得沒法預期的結果。java
Java 8提供新的日期和時間API,LocalDate類實例是一個不可變對象,只提供簡單的日期而且不含當天時間信息。此外也不附帶任何與時區相關的信息。git
經過靜態工廠方法of建立一個LocalDate實例。LocalDate實例提供了多種方法來讀取經常使用的值,好比年份、月份、星期幾等,以下所示。github
LocalDate localDate = LocalDate.of(2014, 3, 18);
int year = localDate.getYear();
Month month = localDate.getMonth();
int day = localDate.getDayOfMonth();
DayOfWeek dow = localDate.getDayOfWeek();
int len = localDate.lengthOfMonth();
boolean leap = localDate.isLeapYear();
// 使用工廠方法從系統時鐘中獲取當前的日期
LocalDate today = LocalDate.now();
System.out.println(String.format("year:%s\nmonth:%s\nday:%s\ndow:%s\nlen:%s\nleap:%s", year, month, day, dow, len, leap));
System.out.println(today);
結果:
year:2014
month:MARCH
day:18
dow:TUESDAY
len:31
leap:false
2019-03-27
複製代碼
Java 8日期-時間類都提供了相似的工廠方法。經過傳遞TemporalField參數給get方法拿到一樣的信息。TemporalField接口定義瞭如何訪問temporal對象某個字段的值。ChronoField枚舉實現TemporalField接口,可使用get方法獲得枚舉元素的值。數據庫
int year = localDate.get(ChronoField.YEAR);
int month = localDate.get(ChronoField.MONTH_OF_YEAR);
int day = localDate.get(ChronoField.DAY_OF_MONTH);
複製代碼
使用LocalTime類表示時間,可使用of重載的兩個工廠方法建立LocalTime的實例。編程
LocalTime類也提供了一些get方法訪問這些變量的值,以下所示。安全
LocalTime localTime = LocalTime.of(13, 45, 20);
int hour = localTime.getHour();
int minute = localTime.getMinute();
int second = localTime.getSecond();
System.out.println(String.format("hour:%s\nminute:%s\nsecond:%s", hour, minute, second));
打印結果:
hour:13
minute:45
second:20
複製代碼
LocalDate和LocalTime均可以經過解析表明它們的字符串建立。使用靜態方法parse能夠實現:app
LocalDate date = LocalDate.parse("2019-03-27");
LocalTime time = LocalTime.parse("20:17:08");
複製代碼
能夠向parse方法傳遞一個DateTimeFormatter。該類的實例定義瞭如何格式化一個日期或者時間對象。用來替換老版java.util.DateFormat。 若是傳遞的字符串參數沒法被解析爲合法的LocalDate或LocalTime對象,這兩個parse方法都會拋出一個繼承自RuntimeException的DateTimeParseException異常。函數式編程
複合類LocalDateTime,是LocalDate和LocalTime的合體。它同時表示了日期和時間,不帶有時區信息。能夠直接建立,也能夠經過合併日期和時間對象構造。函數
LocalTime time = LocalTime.of(21, 31, 50);
LocalDate date = LocalDate.of(2019, 03, 27);
LocalDateTime dt1 = LocalDateTime.of(2017, Month.NOVEMBER, 07, 22, 31, 51);
LocalDateTime dt2 = LocalDateTime.of(date, time);
LocalDateTime dt3 = date.atTime(22, 21, 14);
LocalDateTime dt4 = date.atTime(time);
LocalDateTime dt5 = time.atDate(date);
複製代碼
建立LocalDateTime對象ui
也可使用toLocalDate或者toLocalTime方法,從LocalDateTime中提取LocalDate或者LocalTime組件:
LocalDate date1 = dt1.toLocalDate();
LocalTime time1 = dt1.toLocalTime();
複製代碼
從計算機的角度來看,"2019年03月27日11:20:03"這樣的方式是不容易理解的,計算機更加容易理解建模時間最天然的格式是表示一個持續時間段上某個點的單一大整型數。新的java.time.Instant類對時間建模的方式,基本上它是以Unix元年時間(傳統的設定爲UTC時區1970年1月1日午夜時分)開始所經歷的秒數進行計算。
Instant.ofEpochSecond(3);
Instant.ofEpochSecond(3, 0);
// 2 秒以後再加上100萬納秒(1秒)
Instant.ofEpochSecond(2, 1_000_000_000);
// 4秒以前的100萬納秒(1秒)
Instant.ofEpochSecond(4, -1_000_000_000);
複製代碼
Instant類也支持靜態工廠方法now,它可以獲取當前時刻的時間戳。
Instant now = Instant.now();
System.out.println(now);
2019-03-27T03:26:39.451Z
複製代碼
Instant的設計初衷是爲了便於機器使用,它包含的是由秒及納秒所構成的數字。所以Instant沒法處理那些咱們很是容易理解的時間單位。
int day = Instant.now().get(ChronoField.DAY_OF_MONTH);
它會拋出下面這樣的異常:
Exception in thread "main" java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: DayOfMonth
可是你能夠經過Duration和Period類使用Instant,接下來咱們會對這部份內容進行介紹。
複製代碼
全部類都實現了Temporal接口,該接口定義如何讀取和操縱爲時間建模的對象的值。若是須要建立兩個Temporal對象之間的duration,就須要Duration類的靜態工廠方法between。 能夠建立兩個LocalTimes對象、兩個LocalDateTimes對象,或者兩個Instant對象之間的duration:
LocalTime time1 = LocalTime.of(21, 50, 10);
LocalTime time2 = LocalTime.of(22, 50, 10);
LocalDateTime dateTime1 = LocalDateTime.of(2019, 03, 27, 21, 20, 40);
LocalDateTime dateTime2 = LocalDateTime.of(2019, 03, 27, 21, 20, 40);
Instant instant1 = Instant.ofEpochSecond(1000 * 60 * 2);
Instant instant2 = Instant.ofEpochSecond(1000 * 60 * 3);
Duration d1 = Duration.between(time1, time2);
Duration d2 = Duration.between(dateTime1, dateTime2);
Duration d3 = Duration.between(instant1, instant2);
// PT1H 相差1小時
System.out.println("d1:" + d1);
// PT2H 相差2小時
System.out.println("d2:" + d2);
// PT16H40M 相差16小時40分鐘
System.out.println("d3:" + d3);
複製代碼
LocalDateTime是爲了便於人閱讀使用,Instant是爲了便於機器處理,因此不能將兩者混用。若是在這兩類對象之間建立duration,會觸發一個DateTimeException異常。 此外,因爲Duration類主要用於以秒和納秒衡量時間的長短,你不能僅向between方法傳遞一個LocalDate對象作參數。
使用Period類以年、月或者日的方式對多個時間單位建模。使用該類的工廠方法between,可使用獲得兩個LocalDate之間的時長。
Period period = Period.between(LocalDate.of(2019, 03, 7), LocalDate.of(2019, 03, 17));
// 相差10天
System.out.println("Period between:" + period);
複製代碼
Duration和Period類都提供了不少很是方便的工廠類,直接建立對應的實例。
Duration threeMinutes = Duration.ofMinutes(3);
Duration fourMinutes = Duration.of(4, ChronoUnit.MINUTES);
Period tenDay = Period.ofDays(10);
Period threeWeeks = Period.ofWeeks(3);
Period twoYearsSixMonthsOneDay = Period.of(2, 6, 1);
Duration類和Period類共享了不少類似的方法,有興趣的能夠參考官網的文檔。
複製代碼
截至目前,咱們介紹的這些日期-時間對象都是不可修改的,這是爲了更好地支持函數式編程,確保線程安全,保持領域模式一致性而作出的重大設計決定。 固然,新的日期和時間API也提供了一些便利的方法來建立這些對象的可變版本。好比,你可能但願在已有的LocalDate實例上增長3天。除此以外,咱們還會介紹如何依據指定的模式, 好比dd/MM/yyyy,建立日期-時間格式器,以及如何使用這種格式器解析和輸出日期。
若是已經有一個LocalDate對象,想要建立它的一個修改版,最直接也最簡單的方法是使用withAttribute方法。withAttribute方法會建立對象的一個副本,並按照須要修改它的屬性。
// 這段代碼中全部的方法都返回一個修改了屬性的對象。它們都不會修改原來的對象!
LocalDate date1 = LocalDate.of(2017, 12, 15);
LocalDate date2 = date1.withYear(2019);
LocalDate date3 = date2.withDayOfMonth(25);
LocalDate date4 = date3.with(ChronoField.MONTH_OF_YEAR, 9);
複製代碼
它們都聲明於Temporal接口,全部的日期和時間API類都實現這兩個方法,它們定義了單點的時間,好比LocalDate、LocalTime、LocalDateTime以及Instant。更確切地說,使用get和with方法,咱們能夠將Temporal對象值的讀取和修改區分開。若是Temporal對象不支持請求訪問的字段,它會拋出一個UnsupportedTemporalTypeException異常,好比試圖訪問Instant對象的ChronoField.MONTH_OF_YEAR字段,或者LocalDate對象的ChronoField.NANO_OF_SECOND字段時都會拋出這樣的異常。
// 以聲明的方式操縱LocalDate對象,能夠加上或者減去一段時間
LocalDate date1 = LocalDate.of(2014, 10, 19);
LocalDate date2 = date1.plusWeeks(1);
LocalDate date3 = date2.minusYears(3);
LocalDate date4 = date3.plus(6, ChronoUnit.MONTHS);
複製代碼
與咱們剛纔介紹的get和with方法相似最後一行使用的plus方法也是通用方法,它和minus方法都聲明於Temporal接口中。經過這些方法,對TemporalUnit對象加上或者減去一個數字,咱們能很是方便地將Temporal對象前溯或者回滾至某個時間段,經過ChronoUnit枚舉咱們能夠很是方便地實現TemporalUnit接口。
有時須要進行一些更加複雜的操做,好比,將日期調整到下個週日、下個工做日,或者是本月的最後一天。可使用重載版本的with方法,向其傳遞一個提供了更多定製化選擇的TemporalAdjuster對象,更加靈活地處理日期。
// 對於最多見的用例,日期和時間API已經提供了大量預約義的TemporalAdjuster。能夠經過TemporalAdjuster類的靜態工廠方法訪問。
LocalDate date1 = LocalDate.of(2013, 12, 11);
LocalDate date2 = date1.with(TemporalAdjusters.nextOrSame(DayOfWeek.MONDAY));
LocalDate date3 = date2.with(TemporalAdjusters.lastDayOfMonth());
複製代碼
使用TemporalAdjuster能夠進行更加複雜的日期操做,方法的名稱很直觀。若是沒有找到符合預期的預約義的TemporalAdjuster,能夠建立自定義的TemporalAdjuster。TemporalAdjuster接口只聲明一個方法(即函數式接口)。實現該接口須要定義如何將一個Temporal對象轉換爲另外一個Temporal對象,能夠把它當作一個UnaryOperator。
@FunctionalInterface
public interface TemporalAdjuster {
Temporal adjustInto(Temporal temporal);
}
複製代碼
新的java.time.format包就是特別爲格式化以及解析日期-時間對象而設計的。其中最重要的類是DateTimeFormatter。建立格式器最簡單的方法是經過它的靜態工廠方法以及常量。全部的DateTimeFormatter實例都能用於以必定的格式建立表明特定日期或時間的字符串。
LocalDate date = LocalDate.of(2013, 10, 11);
String s1 = date.format(DateTimeFormatter.BASIC_ISO_DATE);
String s2 = date.format(DateTimeFormatter.ISO_LOCAL_DATE);
20131011
2013-10-11
複製代碼
經過解析表明日期或時間的字符串從新建立該日期對象,也可使用工廠方法parse從新建立。
LocalDate date2 = LocalDate.parse("20141007", DateTimeFormatter.BASIC_ISO_DATE);
LocalDate date3 = LocalDate.parse("2014-10-07", DateTimeFormatter.ISO_LOCAL_DATE);
複製代碼
DateTimeFormatter實例是線程安全的,老的java.util.DateFormat線程不安全。單例模式建立格式器實例,在多個線程間共享實例是沒有問題的。也能夠經過ofPattern靜態工廠方法,按照某個特定的模式建立格式器。
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
String formattedDateStr = date.format(formatter);
LocalDate date1 = LocalDate.parse(formattedDateStr, formatter);
複製代碼
ofPattern方法也提供了一個重載的版本,能夠傳入Locale建立格式器。
DateTimeFormatter italianFormatter = DateTimeFormatter.ofPattern("d. MMMM yyyy", Locale.ITALIAN);
LocalDate date = LocalDate.of(2015, 11, 14);
String formattedDate = date.format(italianFormatter);
LocalDate date1 = LocalDate.parse(formattedDate, italianFormatter);
複製代碼
DateTimeFormatterBuilder類還提供了更復雜的格式器,以提供更加細粒度的控制。同時也提供很是強大的解析功能,好比區分大小寫的解析、柔性解析、填充,以及在格式器中指定可選節等等。
經過DateTimeFormatterBuilder自定義格式器
DateTimeFormatter italianFormatter = new DateTimeFormatterBuilder()
.appendText(ChronoField.DAY_OF_MONTH)
.appendLiteral(". ")
.appendText(ChronoField.MONTH_OF_YEAR)
.appendLiteral(" ")
.appendText(ChronoField.YEAR)
.parseCaseInsensitive()
.toFormatter(Locale.ITALIAN);
LocalDate now = LocalDate.now();
String s = now.format(italianFormatter);
複製代碼
新版日期和時間API新增長的重要功能是時區的處理。新的java.time.ZoneId類替代老版java.util.TimeZone。跟其餘日期和時間類同樣,ZoneId類也是沒法修改的。是按照必定的規則將區域劃分紅的標準時間相同的區間。在ZoneRules這個類中包含了40個時區實例,能夠經過調用ZoneId的getRules()獲得指定時區的規則,每一個特定的ZoneId對象都由一個地區ID標識。
ZoneId shanghaiZone = ZoneId.of("Asia/Shanghai");
複製代碼
Java 8的新方法toZoneId將一個老的時區對象轉換爲ZoneId。地區ID都爲「{區域}/{城市}」的格式,地區集合的設定都由英特網編號分配機構(IANA)的時區數據庫提供。
ZoneId zoneId = TimeZone.getDefault().toZoneId();
複製代碼
ZoneId對象能夠與LocalDate、LocalDateTime或者是Instant對象整合構造爲成ZonedDateTime實例,它表明了相對於指定時區的時間點。
LocalDate date = LocalDate.of(2019, 03, 27);
ZonedDateTime zdt1 = date.atStartOfDay(shanghaiZone);
LocalDateTime dateTime = LocalDateTime.of(2015, 12, 21, 11, 11, 11);
ZonedDateTime zdt2 = dateTime.atZone(shanghaiZone);
Instant instant = Instant.now();
ZonedDateTime zdt3 = instant.atZone(shanghaiZone);
經過ZoneId,你還能夠將LocalDateTime轉換爲Instant:
LocalDateTime dateTime = LocalDateTime.of(2016, 10, 14, 15, 35);
Instant instantFromDateTime = dateTime.toInstant(shanghaiZone);
你也能夠經過反向的方式獲得LocalDateTime對象:
Instant instant = Instant.now();
LocalDateTime timeFromInstant = LocalDateTime.ofInstant(instant, shanghaiZone);
複製代碼
另外一種比較通用的表達時區的方式是利用當前時區和UTC/格林尼治的固定誤差。使用ZoneId的一個子類ZoneOffset,表示的是當前時間和倫敦格林尼治子午線時間的差別:
ZoneOffset newYorkOffset = ZoneOffset.of("-05:00");
複製代碼
本文同步發表在公衆號,歡迎你們關注!😁 後續筆記歡迎關注獲取第一時間更新!