摘要:合并日期和時(shí)間這個(gè)復(fù)合類(lèi)名叫,是和的合體。截至目前,我們介紹的這些日期時(shí)間對(duì)象都是不可修改的,這是為了更好地支持函數(shù)式編程,確保線程安全,保持領(lǐng)域模式一致性而做出的重大設(shè)計(jì)決定。
新的日期和時(shí)間API
Java的API提供了很多有用的組件,能幫助你構(gòu)建復(fù)雜的應(yīng)用。不過(guò),Java API也不總是完美的。我們相信大多數(shù)有經(jīng)驗(yàn)的程序員都會(huì)贊同Java 8之前的庫(kù)對(duì)日期和時(shí)間的支持就非常不理想。然而,你也不用太擔(dān)心:Java 8中引入全新的日期和時(shí)間API就是要解決這一問(wèn)題。
在Java 1.0中,對(duì)日期和時(shí)間的支持只能依賴(lài)java.util.Date類(lèi)。正如類(lèi)名所表達(dá)的,這個(gè)類(lèi)無(wú)法表示日期,只能以毫秒的精度表示時(shí)間。更糟糕的是它的易用性,由于某些原因未知的設(shè)計(jì)決策,這個(gè)類(lèi)的易用性被深深地?fù)p害了,比如:年份的起始選擇是1900年,月份的起始從0開(kāi)始。這意味著,如果你想要用Date表示Java 8的發(fā)布日期,即2014年3月18日,需要?jiǎng)?chuàng)建下面這樣的Date實(shí)例:
Date date = new Date(114, 2, 18);
它的打印輸出效果為:
Tue Mar 18 00:00:00 CST 2014
看起來(lái)不那么直觀,不是嗎?此外,甚至Date類(lèi)的toString方法返回的字符串也容易誤導(dǎo)人。
隨著Java 1.0退出歷史舞臺(tái),Date類(lèi)的種種問(wèn)題和限制幾乎一掃而光,但很明顯,這些歷史舊賬如果不犧牲前向兼容性是無(wú)法解決的。所以,在Java 1.1中,Date類(lèi)中的很多方法被廢棄了,取而代之的是java.util.Calendar類(lèi)。很不幸,Calendar類(lèi)也有類(lèi)似的問(wèn)題和設(shè)計(jì)缺陷,導(dǎo)致使用這些方法寫(xiě)出的代碼非常容易出錯(cuò)。比如,月份依舊是從0開(kāi)始計(jì)算(不過(guò),至少Calendar類(lèi)拿掉了由1900年開(kāi)始計(jì)算年份這一設(shè)計(jì))。更糟的是,同時(shí)存在Date和Calendar這兩個(gè)類(lèi),也增加了程序員的困惑。到底該使用哪一個(gè)類(lèi)呢?此外,有的特性只在某一個(gè)類(lèi)有提供,比如用于以語(yǔ)言無(wú)關(guān)方式格式化和解析日期或時(shí)間的DateFormat方法就只在Date類(lèi)里有。
DateFormat方法也有它自己的問(wèn)題。比如,它不是線程安全的。這意味著兩個(gè)線程如果嘗試使用同一個(gè)formatter解析日期,你可能會(huì)得到無(wú)法預(yù)期的結(jié)果。
最后,Date和Calendar類(lèi)都是可以變的。能把2014年3月18日修改成4月18日意味著什么呢?這種設(shè)計(jì)會(huì)將你拖入維護(hù)的噩夢(mèng),接下來(lái)的一章,我們會(huì)討論函數(shù)式編程,你在該章中會(huì)了解到更多的細(xì)節(jié)。
這一章中,我們會(huì)一起探索新的日期和時(shí)間API所提供的新特性。我們從最基本的用例入手,比如創(chuàng)建同時(shí)適合人與機(jī)器的日期和時(shí)間,逐漸轉(zhuǎn)入到日期和時(shí)間API更高級(jí)的一些應(yīng)用,比如操縱、解析、打印輸出日期?時(shí)間對(duì)象,使用不同的時(shí)區(qū)和年歷。
LocalDate、LocalTime、Instant、Duration 以及Period讓我們從探索如何創(chuàng)建簡(jiǎn)單的日期和時(shí)間間隔入手。java.time包中提供了很多新的類(lèi)可以幫你解決問(wèn)題,它們是LocalDate、LocalTime、Instant、Duration和Period。
使用LocalDate 和LocalTime開(kāi)始使用新的日期和時(shí)間API時(shí),你最先碰到的可能是LocalDate類(lèi)。該類(lèi)的實(shí)例是一個(gè)不可變對(duì)象,它只提供了簡(jiǎn)單的日期,并不含當(dāng)天的時(shí)間信息。另外,它也不附帶任何與時(shí)區(qū)相關(guān)的信息。
你可以通過(guò)靜態(tài)工廠方法of創(chuàng)建一個(gè)LocalDate實(shí)例。LocalDate實(shí)例提供了多種方法來(lái)讀取常用的值,比如年份、月份、星期幾等,如下所示。
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(); System.out.println(String.format("year:%s month:%s day:%s dow:%s len:%s leap:%s", year, month, day, dow, len, leap));
打印結(jié)果:
year:2014 month:MARCH day:18 dow:TUESDAY len:31 leap:false
你還可以使用工廠方法從系統(tǒng)時(shí)鐘中獲取當(dāng)前的日期:
LocalDate today = LocalDate.now();
接下來(lái)剩余的部分會(huì)探討所有日期-時(shí)間類(lèi),這些類(lèi)都提供了類(lèi)似的工廠方法。你還可以通過(guò)傳遞一個(gè)TemporalField參數(shù)給get方法拿到同樣的信息。TemporalField是一個(gè)接口,它定義了如何訪問(wèn)temporal對(duì)象某個(gè)字段的值。ChronoField枚舉實(shí)現(xiàn)了這一接口,所以你可以很方便地使用get方法得到枚舉元素的值,如下所示。
int year = localDate.get(ChronoField.YEAR); int month = localDate.get(ChronoField.MONTH_OF_YEAR); int day = localDate.get(ChronoField.DAY_OF_MONTH);
類(lèi)似地,一天中的時(shí)間,比如13:45:20,可以使用LocalTime類(lèi)表示。你可以使用of重載的兩個(gè)工廠方法創(chuàng)建LocalTime的實(shí)例。第一個(gè)重載函數(shù)接收小時(shí)和分鐘,第二個(gè)重載函數(shù)同時(shí)還接收秒。同LocalDate一樣,LocalTime類(lèi)也提供了一些getter方法訪問(wèn)這些變量的值,如下所示。
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 minute:%s second:%s", hour, minute, second));
打印結(jié)果:
hour:13 minute:45 second:20
LocalDate和LocalTime都可以通過(guò)解析代表它們的字符串創(chuàng)建。使用靜態(tài)方法parse,你可以實(shí)現(xiàn)這一目的:
LocalDate date = LocalDate.parse("2018-11-17"); LocalTime time = LocalTime.parse("21:27:58");
你可以向parse方法傳遞一個(gè)DateTimeFormatter。該類(lèi)的實(shí)例定義了如何格式化一個(gè)日期或者時(shí)間對(duì)象。正如我們之前所介紹的,它是替換老版java.util.DateFormat的推薦替代品。這個(gè)我們后面將會(huì)討論到。同時(shí),也請(qǐng)注意,一旦傳遞的字符串參數(shù)無(wú)法被解析為合法的LocalDate或LocalTime對(duì)象,這兩個(gè)parse方法都會(huì)拋出一個(gè)繼承自RuntimeException的DateTimeParseException異常。
合并日期和時(shí)間這個(gè)復(fù)合類(lèi)名叫LocalDateTime,是LocalDate和LocalTime的合體。它同時(shí)表示了日期和時(shí)間,但不帶有時(shí)區(qū)信息,你可以直接創(chuàng)建,也可以通過(guò)合并日期和時(shí)間對(duì)象構(gòu)造,如下所示。
// 2018-11-17T21:31:50 LocalTime time = LocalTime.of(21, 31, 50); LocalDate date = LocalDate.of(2018, 11, 17); LocalDateTime dt1 = LocalDateTime.of(2018, Month.NOVEMBER, 17, 21, 31, 50); LocalDateTime dt2 = LocalDateTime.of(date, time); LocalDateTime dt3 = date.atTime(21, 11, 17); LocalDateTime dt4 = date.atTime(time); LocalDateTime dt5 = time.atDate(date);
注意,通過(guò)它們各自的atTime或者atDate方法,向LocalDate傳遞一個(gè)時(shí)間對(duì)象,或者向LocalTime傳遞一個(gè)日期對(duì)象的方式,你可以創(chuàng)建一個(gè)LocalDateTime對(duì)象。你也可以使用toLocalDate或者toLocalTime方法,從LocalDateTime中提取LocalDate或者LocalTime組件:
LocalDate date1 = dt1.toLocalDate(); LocalTime time1 = dt1.toLocalTime();機(jī)器的日期和時(shí)間格式
作為人,我們習(xí)慣于以星期幾、幾號(hào)、幾點(diǎn)、幾分這樣的方式理解日期和時(shí)間。毫無(wú)疑問(wèn),這種方式對(duì)于計(jì)算機(jī)而言并不容易理解。從計(jì)算機(jī)的角度來(lái)看,建模時(shí)間最自然的格式是表示一個(gè)持續(xù)時(shí)間段上某個(gè)點(diǎn)的單一大整型數(shù)。這也是新的java.time.Instant類(lèi)對(duì)時(shí)間建模的方式,基本上它是以Unix元年時(shí)間(傳統(tǒng)的設(shè)定為UTC時(shí)區(qū)1970年1月1日午夜時(shí)分)開(kāi)始所經(jīng)歷的秒數(shù)進(jìn)行計(jì)算。
你可以通過(guò)向靜態(tài)工廠方法ofEpochSecond傳遞一個(gè)代表秒數(shù)的值創(chuàng)建一個(gè)該類(lèi)的實(shí)例。靜態(tài)工廠方法ofEpochSecond還有一個(gè)增強(qiáng)的重載版本,它接收第二個(gè)以納秒為單位的參數(shù)值,對(duì)傳入作為秒數(shù)的參數(shù)進(jìn)行調(diào)整。重載的版本會(huì)調(diào)整納秒?yún)?shù),確保保存的納秒分片在0到999 999999之間。這意味著下面這些對(duì)ofEpochSecond工廠方法的調(diào)用會(huì)返回幾乎同樣的Instant對(duì)象:
Instant.ofEpochSecond(3); Instant.ofEpochSecond(3, 0); // 2 秒之后再加上100萬(wàn)納秒(1秒) Instant.ofEpochSecond(2, 1_000_000_000); // 4秒之前的100萬(wàn)納秒(1秒) Instant.ofEpochSecond(4, -1_000_000_000);
正如你已經(jīng)在LocalDate及其他為便于閱讀而設(shè)計(jì)的日期-時(shí)間類(lèi)中所看到的那樣,Instant類(lèi)也支持靜態(tài)工廠方法now,它能夠幫你獲取當(dāng)前時(shí)刻的時(shí)間戳。我們想要特別強(qiáng)調(diào)一點(diǎn),Instant的設(shè)計(jì)初衷是為了便于機(jī)器使用。它包含的是由秒及納秒所構(gòu)成的數(shù)字。所以,它無(wú)法處理那些我們非常容易理解的時(shí)間單位。比如下面這段語(yǔ)句:
int day = Instant.now().get(ChronoField.DAY_OF_MONTH);
它會(huì)拋出下面這樣的異常:
Exception in thread "main" java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: DayOfMonth
但是你可以通過(guò)Duration和Period類(lèi)使用Instant,接下來(lái)我們會(huì)對(duì)這部分內(nèi)容進(jìn)行介紹。
定義Duration 或Period目前為止,你看到的所有類(lèi)都實(shí)現(xiàn)了Temporal接口,Temporal接口定義了如何讀取和操縱為時(shí)間建模的對(duì)象的值。之前的介紹中,我們已經(jīng)了解了創(chuàng)建Temporal實(shí)例的幾種方法。很自然地你會(huì)想到,我們需要?jiǎng)?chuàng)建兩個(gè)Temporal對(duì)象之間的duration。Duration類(lèi)的靜態(tài)工廠方法between就是為這個(gè)目的而設(shè)計(jì)的。你可以創(chuàng)建兩個(gè)LocalTimes對(duì)象、兩個(gè)LocalDateTimes對(duì)象,或者兩個(gè)Instant對(duì)象之間的duration,如下所示:
LocalTime time1 = LocalTime.of(21, 50, 10); LocalTime time2 = LocalTime.of(22, 50, 10); LocalDateTime dateTime1 = LocalDateTime.of(2018, 11, 17, 21, 50, 10); LocalDateTime dateTime2 = LocalDateTime.of(2018, 11, 17, 23, 50, 10); 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小時(shí) System.out.println("d1:" + d1); // PT2H 相差2小時(shí) System.out.println("d2:" + d2); // PT16H40M 相差16小時(shí)40分鐘 System.out.println("d3:" + d3);
由于LocalDateTime和Instant是為不同的目的而設(shè)計(jì)的,一個(gè)是為了便于人閱讀使用,另一個(gè)是為了便于機(jī)器處理,所以你不能將二者混用。如果你試圖在這兩類(lèi)對(duì)象之間創(chuàng)建duration,會(huì)觸發(fā)一個(gè)DateTimeException異常。此外,由于Duration類(lèi)主要用于以秒和納秒衡量時(shí)間的長(zhǎng)短,你不能僅向between方法傳遞一個(gè)LocalDate對(duì)象做參數(shù)。
如果你需要以年、月或者日的方式對(duì)多個(gè)時(shí)間單位建模,可以使用Period類(lèi)。使用該類(lèi)的工廠方法between,你可以使用得到兩個(gè)LocalDate之間的時(shí)長(zhǎng),如下所示:
Period period = Period.between(LocalDate.of(2018, 11, 7), LocalDate.of(2018, 11, 17)); // P10D 相差10天 System.out.println("Period between:" + period);
最后,Duration和Period類(lèi)都提供了很多非常方便的工廠類(lèi),直接創(chuàng)建對(duì)應(yīng)的實(shí)例;換句話說(shuō),就像下面這段代碼那樣,不再是只能以?xún)蓚€(gè)temporal對(duì)象的差值的方式來(lái)定義它們的對(duì)象。
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類(lèi)和Period類(lèi)共享了很多相似的方法,有興趣的可以參考官網(wǎng)的文檔。
截至目前,我們介紹的這些日期?時(shí)間對(duì)象都是不可修改的,這是為了更好地支持函數(shù)式編程,確保線程安全,保持領(lǐng)域模式一致性而做出的重大設(shè)計(jì)決定。當(dāng)然,新的日期和時(shí)間API也提供了一些便利的方法來(lái)創(chuàng)建這些對(duì)象的可變版本。比如,你可能希望在已有的LocalDate實(shí)例上增加3天。除此之外,我們還會(huì)介紹如何依據(jù)指定的模式,比如dd/MM/yyyy,創(chuàng)建日期-時(shí)間格式器,以及如何使用這種格式器解析和輸出日期。
操縱、解析和格式化日期如果你已經(jīng)有一個(gè)LocalDate對(duì)象,想要?jiǎng)?chuàng)建它的一個(gè)修改版,最直接也最簡(jiǎn)單的方法是使用withAttribute方法。withAttribute方法會(huì)創(chuàng)建對(duì)象的一個(gè)副本,并按照需要修改它的屬性。注意,下面的這段代碼中所有的方法都返回一個(gè)修改了屬性的對(duì)象。它們都不會(huì)修改原來(lái)的對(duì)象!
// 2018-11-17 LocalDate date1 = LocalDate.of(2018, 11, 17); // 2019-11-17 LocalDate date2 = date1.withYear(2019); // 2019-11-25 LocalDate date3 = date2.withDayOfMonth(25); // 2019-09-25 LocalDate date4 = date3.with(ChronoField.MONTH_OF_YEAR, 9);
它們都聲明于Temporal接口,所有的日期和時(shí)間API類(lèi)都實(shí)現(xiàn)這兩個(gè)方法,它們定義了單點(diǎn)的時(shí)間,比如LocalDate、LocalTime、LocalDateTime以及Instant。更確切地說(shuō),使用get和with方法,我們可以將Temporal對(duì)象值的讀取和修改區(qū)分開(kāi)。如果Temporal對(duì)象不支持請(qǐng)求訪問(wèn)的字段,它會(huì)拋出一個(gè)UnsupportedTemporalTypeException異常,比如試圖訪問(wèn)Instant對(duì)象的ChronoField.MONTH_OF_YEAR字段,或者LocalDate對(duì)象的ChronoField.NANO_OF_SECOND字段時(shí)都會(huì)拋出這樣的異常。
它甚至能以聲明的方式操縱LocalDate對(duì)象。比如,你可以像下面這段代碼那樣加上或者減去一段時(shí)間。
// 2018-11-17 LocalDate date1 = LocalDate.of(2018, 11, 17); // 2018-11-24 LocalDate date2 = date1.plusWeeks(1); // 2015-11-24 LocalDate date3 = date2.minusYears(3); // 2016-05-24 LocalDate date4 = date3.plus(6, ChronoUnit.MONTHS);
與我們剛才介紹的get和with方法類(lèi)似最后一行使用的plus方法也是通用方法,它和minus方法都聲明于Temporal接口中。通過(guò)這些方法,對(duì)TemporalUnit對(duì)象加上或者減去一個(gè)數(shù)字,我們能非常方便地將Temporal對(duì)象前溯或者回滾至某個(gè)時(shí)間段,通過(guò)ChronoUnit枚舉我們可以非常方便地實(shí)現(xiàn)TemporalUnit接口。
大概你已經(jīng)猜到,像LocalDate、LocalTime、LocalDateTime以及Instant這樣表示時(shí)
間點(diǎn)的日期?時(shí)間類(lèi)提供了大量通用的方法,我們目前所使用的只有一小部分,有興趣的可以去看官網(wǎng)文檔。
截至目前,你所看到的所有日期操作都是相對(duì)比較直接的。有的時(shí)候,你需要進(jìn)行一些更加復(fù)雜的操作,比如,將日期調(diào)整到下個(gè)周日、下個(gè)工作日,或者是本月的最后一天。這時(shí),你可以使用重載版本的with方法,向其傳遞一個(gè)提供了更多定制化選擇的TemporalAdjuster對(duì)象,更加靈活地處理日期。對(duì)于最常見(jiàn)的用例, 日期和時(shí)間API已經(jīng)提供了大量預(yù)定義的TemporalAdjuster。你可以通過(guò)TemporalAdjuster類(lèi)的靜態(tài)工廠方法訪問(wèn)它們,如下所示。
// 2018-11-17 LocalDate date1 = LocalDate.of(2018, 11, 17); // 2018-11-19 LocalDate date2 = date1.with(TemporalAdjusters.nextOrSame(DayOfWeek.MONDAY)); // 2018-11-30 LocalDate date3 = date2.with(TemporalAdjusters.lastDayOfMonth());
正如我們看到的,使用TemporalAdjuster我們可以進(jìn)行更加復(fù)雜的日期操作,而且這些方法的名稱(chēng)也非常直觀,方法名基本就是問(wèn)題陳述。此外,即使你沒(méi)有找到符合你要求的預(yù)定義的TemporalAdjuster,創(chuàng)建你自己的TemporalAdjuster也并非難事。實(shí)際上,TemporalAdjuster接口只聲明了單一的一個(gè)方法(這使得它成為了一個(gè)函數(shù)式接口),定義如下。
@FunctionalInterface public interface TemporalAdjuster { Temporal adjustInto(Temporal temporal); }
這意味著TemporalAdjuster接口的實(shí)現(xiàn)需要定義如何將一個(gè)Temporal對(duì)象轉(zhuǎn)換為另一個(gè)Temporal對(duì)象。你可以把它看成一個(gè)UnaryOperator
你可能希望對(duì)你的日期時(shí)間對(duì)象進(jìn)行的另外一個(gè)通用操作是,依據(jù)你的業(yè)務(wù)領(lǐng)域以不同的格式打印輸出這些日期和時(shí)間對(duì)象。類(lèi)似地,你可能也需要將那些格式的字符串轉(zhuǎn)換為實(shí)際的日期對(duì)象。接下來(lái)的一節(jié),我們會(huì)演示新的日期和時(shí)間API提供那些機(jī)制是如何完成這些任務(wù)的。
打印輸出及解析日期-時(shí)間對(duì)象處理日期和時(shí)間對(duì)象時(shí),格式化以及解析日期?時(shí)間對(duì)象是另一個(gè)非常重要的功能。新的java.time.format包就是特別為這個(gè)目的而設(shè)計(jì)的。這個(gè)包中,最重要的類(lèi)是DateTimeFormatter。創(chuàng)建格式器最簡(jiǎn)單的方法是通過(guò)它的靜態(tài)工廠方法以及常量。像BASIC_ISO_DATE和ISO_LOCAL_DATE 這樣的常量是DateTimeFormatter 類(lèi)的預(yù)定義實(shí)例。所有的DateTimeFormatter實(shí)例都能用于以一定的格式創(chuàng)建代表特定日期或時(shí)間的字符串。比如,下面的這個(gè)例子中,我們使用了兩個(gè)不同的格式器生成了字符串:
LocalDate date1 = LocalDate.of(2018, 11, 17); // 20181117 String s1 = date1.format(DateTimeFormatter.BASIC_ISO_DATE); // 2018-11-17 String s2 = date1.format(DateTimeFormatter.ISO_LOCAL_DATE);
你也可以通過(guò)解析代表日期或時(shí)間的字符串重新創(chuàng)建該日期對(duì)象。所有的日期和時(shí)間API都提供了表示時(shí)間點(diǎn)或者時(shí)間段的工廠方法,你可以使用工廠方法parse達(dá)到重創(chuàng)該日期對(duì)象的目的:
LocalDate date2 = LocalDate.parse("20181117", DateTimeFormatter.BASIC_ISO_DATE); LocalDate date3 = LocalDate.parse("2018-11-17", DateTimeFormatter.ISO_LOCAL_DATE);
和老的java.util.DateFormat相比較,所有的DateTimeFormatter實(shí)例都是線程安全的。所以,你能夠以單例模式創(chuàng)建格式器實(shí)例,就像DateTimeFormatter所定義的那些常量,并能在多個(gè)線程間共享這些實(shí)例。DateTimeFormatter類(lèi)還支持一個(gè)靜態(tài)工廠方法,它可以按照某個(gè)特定的模式創(chuàng)建格式器,代碼清單如下。
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy"); // 17/11/2018 String formattedDate = date1.format(formatter); LocalDate date4 = LocalDate.parse(formattedDate, formatter);
這段代碼中,LocalDate的formate方法使用指定的模式生成了一個(gè)代表該日期的字符串。緊接著,靜態(tài)的parse方法使用同樣的格式器解析了剛才生成的字符串,并重建了該日期對(duì)象。ofPattern方法也提供了一個(gè)重載的版本,使用它你可以創(chuàng)建某個(gè)Locale的格式器,代碼清單如下所示。
DateTimeFormatter italianFormatter = DateTimeFormatter.ofPattern("d. MMMM yyyy", Locale.ITALIAN); LocalDate date5 = LocalDate.of(2018, 11, 16); // 16. novembre 2018 String formattedDate2 = date5.format(italianFormatter); // 2018-11-16 LocalDate date6 = LocalDate.parse(formattedDate2, italianFormatter);
最后,如果你還需要更加細(xì)粒度的控制,DateTimeFormatterBuilder類(lèi)還提供了更復(fù)雜的格式器,你可以選擇恰當(dāng)?shù)姆椒ǎ徊揭徊降貥?gòu)造自己的格式器。另外,它還提供了非常強(qiáng)大的解析功能,比如區(qū)分大小寫(xiě)的解析、柔性解析(允許解析器使用啟發(fā)式的機(jī)制去解析輸入,不精確地匹配指定的模式)、填充, 以及在格式器中指定可選節(jié)。
比如, 你可以通過(guò)DateTimeFormatterBuilder自己編程實(shí)現(xiàn)我們?cè)谏厦娲a中使用的italianFormatter,代碼清單如下。
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(); // 17. novembre 2018 String s1 = now.format(italianFormatter);
目前為止,你已經(jīng)學(xué)習(xí)了如何創(chuàng)建、操縱、格式化以及解析時(shí)間點(diǎn)和時(shí)間段,但是你還不了解如何處理日期和時(shí)間之間的微妙關(guān)系。比如,你可能需要處理不同的時(shí)區(qū),或者由于不同的歷法系統(tǒng)帶來(lái)的差異。接下來(lái)的一節(jié),我們會(huì)探究如何使用新的日期和時(shí)間API解決這些問(wèn)題。
處理不同的時(shí)區(qū)和歷法之前你看到的日期和時(shí)間的種類(lèi)都不包含時(shí)區(qū)信息。時(shí)區(qū)的處理是新版日期和時(shí)間API新增加的重要功能,使用新版日期和時(shí)間API時(shí)區(qū)的處理被極大地簡(jiǎn)化了。新的java.time.ZoneId類(lèi)是老版java.util.TimeZone的替代品。它的設(shè)計(jì)目標(biāo)就是要讓你無(wú)需為時(shí)區(qū)處理的復(fù)雜和繁瑣而操心,比如處理日光時(shí)(Daylight Saving Time,DST)這種問(wèn)題。跟其他日期和時(shí)間類(lèi)一樣,ZoneId類(lèi)也是無(wú)法修改的。
時(shí)區(qū)是按照一定的規(guī)則將區(qū)域劃分成的標(biāo)準(zhǔn)時(shí)間相同的區(qū)間。在ZoneRules這個(gè)類(lèi)中包含了40個(gè)這樣的實(shí)例。你可以簡(jiǎn)單地通過(guò)調(diào)用ZoneId的getRules()得到指定時(shí)區(qū)的規(guī)則。每個(gè)特定的ZoneId對(duì)象都由一個(gè)地區(qū)ID標(biāo)識(shí),比如:
ZoneId shanghaiZone = ZoneId.of("Asia/Shanghai");
地區(qū)ID都為“{區(qū)域}/{城市}”的格式,這些地區(qū)集合的設(shè)定都由英特網(wǎng)編號(hào)分配機(jī)構(gòu)(IANA)的時(shí)區(qū)數(shù)據(jù)庫(kù)提供。你可以通過(guò)Java 8的新方法toZoneId將一個(gè)老的時(shí)區(qū)對(duì)象轉(zhuǎn)換為ZoneId:
ZoneId zoneId = TimeZone.getDefault().toZoneId();
一旦得到一個(gè)ZoneId對(duì)象,你就可以將它與LocalDate、LocalDateTime或者是Instant對(duì)象整合起來(lái),構(gòu)造為一個(gè)ZonedDateTime實(shí)例,它代表了相對(duì)于指定時(shí)區(qū)的時(shí)間點(diǎn),代碼清單如下所示。
LocalDate date = LocalDate.of(2018, 11, 17); ZonedDateTime zdt1 = date.atStartOfDay(shanghaiZone); LocalDateTime dateTime = LocalDateTime.of(2018, 11, 27, 18, 13, 15); ZonedDateTime zdt2 = dateTime.atZone(shanghaiZone); Instant instant = Instant.now(); ZonedDateTime zdt3 = instant.atZone(shanghaiZone);
通過(guò)ZoneId,你還可以將LocalDateTime轉(zhuǎn)換為Instant:
LocalDateTime dateTime = LocalDateTime.of(2018, 11, 17, 18, 45); Instant instantFromDateTime = dateTime.toInstant(shanghaiZone);
你也可以通過(guò)反向的方式得到LocalDateTime對(duì)象:
Instant instant = Instant.now(); LocalDateTime timeFromInstant = LocalDateTime.ofInstant(instant, shanghaiZone);利用和UTC/格林尼治時(shí)間的固定偏差計(jì)算時(shí)區(qū)
另一種比較通用的表達(dá)時(shí)區(qū)的方式是利用當(dāng)前時(shí)區(qū)和UTC/格林尼治的固定偏差。比如,基于這個(gè)理論,你可以說(shuō)“紐約落后于倫敦5小時(shí)”。這種情況下,你可以使用ZoneOffset類(lèi),它是ZoneId的一個(gè)子類(lèi),表示的是當(dāng)前時(shí)間和倫敦格林尼治子午線時(shí)間的差異:
ZoneOffset newYorkOffset = ZoneOffset.of("-05:00");
“-05:00”的偏差實(shí)際上對(duì)應(yīng)的是美國(guó)東部標(biāo)準(zhǔn)時(shí)間。注意,使用這種方式定義的ZoneOffset并未考慮任何日光時(shí)的影響,所以在大多數(shù)情況下,不推薦使用。由于ZoneOffset也是ZoneId,所以你可以像上面的代碼那樣使用它。你甚至還可以創(chuàng)建這樣的OffsetDateTime,它使用ISO-8601的歷法系統(tǒng),以相對(duì)于UTC/格林尼治時(shí)間的偏差方式表示日期時(shí)間。
LocalDateTime dateTime = LocalDateTime.of(2018, 11, 17, 18, 45); OffsetDateTime offsetDateTime = OffsetDateTime.of(dateTime, newYorkOffset);總結(jié)
Java 8之前老版的java.util.Date類(lèi)以及其他用于建模日期時(shí)間的類(lèi)有很多不一致及設(shè)計(jì)上的缺陷,包括易變性以及糟糕的偏移值、默認(rèn)值和命名。
新版的日期和時(shí)間API中,日期-時(shí)間對(duì)象是不可變的。
新的API提供了兩種不同的時(shí)間表示方式,有效地區(qū)分了運(yùn)行時(shí)人和機(jī)器的不同需求。
你可以用絕對(duì)或者相對(duì)的方式操縱日期和時(shí)間,操作的結(jié)果總是返回一個(gè)新的實(shí)例,老的日期時(shí)間對(duì)象不會(huì)發(fā)生變化。
TemporalAdjuster讓你能夠用更精細(xì)的方式操縱日期,不再局限于一次只能改變它的一個(gè)值,并且你還可按照需求定義自己的日期轉(zhuǎn)換器。
你現(xiàn)在可以按照特定的格式需求,定義自己的格式器,打印輸出或者解析日期?時(shí)間對(duì)象。這些格式器可以通過(guò)模板創(chuàng)建,也可以自己編程創(chuàng)建,并且它們都是線程安全的。
你可以用相對(duì)于某個(gè)地區(qū)/位置的方式,或者以與UTC/格林尼治時(shí)間的絕對(duì)偏差的方式表示時(shí)區(qū),并將其應(yīng)用到日期?時(shí)間對(duì)象上,對(duì)其進(jìn)行本地化。
可以說(shuō)《Java8實(shí)戰(zhàn)》的讀書(shū)筆記相關(guān)的已經(jīng)寫(xiě)完了,這本書(shū)后面還有最后一部分超越Java8,這一部分相關(guān)的章節(jié)都是跟函數(shù)式編程的思考與技巧相關(guān),以及Java以后的未來(lái)等等。《Java8實(shí)戰(zhàn)》這本書(shū)真的寫(xiě)的太好了而且這本書(shū)完全可以當(dāng)作一本關(guān)于Java8使用的工具書(shū),隨時(shí)可以翻開(kāi)看看,看看關(guān)于Java8的特性是如何使用,該如何去避免一些坑,該如何使用Stream和Lambda表達(dá)式去簡(jiǎn)化你的代碼。
代碼Gitee:chap12
Github:chap12
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/72235.html
摘要:類(lèi)似地,一天中的時(shí)間,比如,可以使用類(lèi)表示。合并日期和時(shí)間這個(gè)復(fù)合類(lèi)名叫,是和的合體。對(duì)于最常見(jiàn)的用例,日期和時(shí)間已經(jīng)提供了大量預(yù)定義的。你甚至還可以創(chuàng)建這樣的,它使用的歷法系統(tǒng),以相對(duì)于格林尼治時(shí)間的偏差方式表示日期時(shí)間。 一、LocalDate、LocalTime、Instant、Duration 以及 Period 1.使用 LocalDate 和 LocalTime 創(chuàng)建一個(gè)L...
摘要:包括元素的高度上下內(nèi)邊距上下邊框值,如果元素的的值為那么該值為。該值為元素的包含元素。最后,所有這些偏移量都是只讀的,而且每次訪問(wèn)他們都需要重新計(jì)算。為了避免重復(fù)計(jì)算,可以將計(jì)算的值保存起來(lái),以提高性能。 offsetHeight 包括元素的高度、上下內(nèi)邊距、上下邊框值,如果元素的style.display的值為none,那么該值為0。offsetWidth 包括元素的寬度、左...
摘要:概述本文為協(xié)議的第十二章,本文翻譯的主要內(nèi)容為如何使用其他規(guī)范中的協(xié)議。使用其他規(guī)范中的協(xié)議協(xié)議正文協(xié)議旨在由另一規(guī)范使用,以提供動(dòng)態(tài)作者定義內(nèi)容的通用機(jī)制。當(dāng)連接打開(kāi)時(shí),文檔需要處理收到一條消息第節(jié)的場(chǎng)景。 概述 本文為 WebSocket 協(xié)議的第十二章,本文翻譯的主要內(nèi)容為如何使用其他規(guī)范中的 WebSocket 協(xié)議。 使用其他規(guī)范中的WebSocket協(xié)議(協(xié)議正文) Web...
摘要:最近買(mǎi)了深入理解的書(shū)籍來(lái)看,為什么學(xué)習(xí)這么久還要買(mǎi)這本書(shū)呢主要是看到核心團(tuán)隊(duì)成員及的創(chuàng)造者為本書(shū)做了序,作為一個(gè)粉絲,還是挺看好這本書(shū)能給我?guī)?lái)一個(gè)新的升華,而且本書(shū)的作者也非常厲害。 使用ES6開(kāi)發(fā)已經(jīng)有1年多了,以前看的是阮一峰老師的ES6教程,也看過(guò)MDN文檔的ES6語(yǔ)法介紹。 最近買(mǎi)了《深入理解ES6》的書(shū)籍來(lái)看,為什么學(xué)習(xí)ES6這么久還要買(mǎi)這本書(shū)呢?主要是看到Daniel A...
閱讀 986·2021-09-26 10:15
閱讀 2064·2021-09-24 10:37
閱讀 2580·2019-08-30 13:46
閱讀 2631·2019-08-30 11:16
閱讀 2421·2019-08-29 10:56
閱讀 2591·2019-08-26 12:24
閱讀 3472·2019-08-23 18:26
閱讀 2662·2019-08-23 15:43