Java 8 日期和时间解读
轉(zhuǎn)載自?Java 8 日期和時(shí)間解讀
????????現(xiàn)在,一些應(yīng)用程序仍然在使用java.util.Date和java.util.Calendar API和它們的類庫(kù),來(lái)使我們?cè)谏钪懈虞p松的處理日期和時(shí)間,比如:JodaTime。然而,Java 8 引進(jìn)的新的類庫(kù)來(lái)處理日期和時(shí)間,這可以使我們更加精細(xì)的控制時(shí)間的表示,可以管理不可變的時(shí)間對(duì)象,同時(shí),不需要使用其它的類庫(kù),更加流暢的API在大多數(shù)情況下對(duì)性能也有很大的提升。下面我們來(lái)了解一下Java 8 日期和時(shí)間的一些基礎(chǔ)知識(shí):
LocalDate/LocalTime/LocalDateTime
????????讓我們從與 java.util.Date最相關(guān)的新的API開(kāi)始: LocalDate:表示日期,不表示時(shí)間。 LocalTime:表示時(shí)間,不表示日期。 LocalDateTime:上面兩者的組合。 所有的這些日期和時(shí)間表示類型,都表示某個(gè)區(qū)域的日期或者時(shí)間。但是,就像java.util.Date中的零區(qū)域信息一樣,只是表示當(dāng)前區(qū)域的日期和時(shí)間。
????首先這些API支持一個(gè)簡(jiǎn)單的實(shí)例:
LocalDate date = LocalDate.of(2018,2,13); // Uses DateTimeformatter.ISOLOCALDATE for which the format is: yyyy-MM-dd LocalDate date = LocalDate.parse("2018-02-13"); LocalTime time = LocalTime.of(6,30); // Uses DateTimeFormatter.ISO_LOCAL_TIME for which the format is: HH:mm[:ss[.SSSSSSSSS]] // this means that both seconds and nanoseconds may optionally be present. LocalTime time = LocalTime.parse("06:30");LocalDateTime dateTime = LocalDateTime.of(2018,2,13,6,30); // Uses DateTimeFormatter.ISO_LOCAL_DATE_TIME for which the format is the // combination of the ISO date and time format, joined by 'T': yyyy-MM-dd'T'HH:mm[:ss[.SSSSSSSSS]] LocalDateTime dateTime = LocalDateTime.parse("2018-02-13T06:30");????在它們之間轉(zhuǎn)換時(shí)比較簡(jiǎn)單的:
// LocalDate to LocalDateTime LocalDateTime dateTime = LocalDate.parse("2018-02-13").atTime(LocalTime.parse("06:30"));?
// LocalTime to LocalDateTime LocalDateTime dateTime = LocalTime.parse("06:30").atDate(LocalDate.parse("2018-02-13"));// LocalDateTime to LocalDate/LocalTime LocalDate date = LocalDateTime.parse("2018-02-13T06:30").toLocalDate(); LocalTime time = LocalDateTime.parse("2018-02-13T06:30").toLocalTime();????????除此之外,使用“加”“減”法來(lái)進(jìn)行我們的日期和時(shí)間操作,像其它公用功能一樣,簡(jiǎn)單的難以置信:?
LocalDate date = LocalDate.parse("2018-02-13").plusDays(5); LocalDate date = LocalDate.parse("2018-02-13").plus(3, ChronoUnit.MONTHS);LocalTime time = LocalTime.parse("06:30").minusMinutes(30); LocalTime time = LocalTime.parse("06:30").minus(500, ChronoUnit.MILLIS);LocalDateTime dateTime = LocalDateTime.parse("2018-02-13T06:30").plus(Duration.ofHours(2));// using TemporalAdjusters, which implements a few useful cases: LocalDate date = LocalDate.parse("2018-02-13").with(TemporalAdjusters.lastDayOfMonth());
????????現(xiàn)在,我們?cè)撊绾螐膉ava.util.Date轉(zhuǎn)換到LocalDateTime呢?它們又有哪些不同?好吧,這很簡(jiǎn)單:我們可以把一個(gè)時(shí)間類型轉(zhuǎn)換為一個(gè)實(shí)例類型,這是從1970年1月1日開(kāi)始的,然后,我們可以在當(dāng)前區(qū)域使用這個(gè)實(shí)例來(lái)實(shí)例化一個(gè)LocalDateTime。
LocalDateTime dateTime = LocalDateTime.ofInstant(new Date().toInstant(), ZoneId.systemDefault());????????為了轉(zhuǎn)換日期,我們可以簡(jiǎn)單的使用java8時(shí)間類型的實(shí)例。需要注意的一點(diǎn)是,雖然LocalDate,LocalTime和LocalDateTime不包含任何區(qū)域和偏移信息,它們代表了一個(gè)特定區(qū)域的日期和/或時(shí)間,同樣的,它們帶有該區(qū)域的偏移。因此,為了正確的將特定類型轉(zhuǎn)換為實(shí)例,我們需要提供一個(gè)偏移。
// represents Wed Feb 28 23:24:43 CET 2018 Date now = new Date();// represents 2018-02-28T23:24:43.106 LocalDateTime dateTime = LocalDateTime.ofInstant(now.toInstant(), ZoneId.systemDefault());// represent Wed Feb 28 23:24:43 CET 2018 Date date = Date.from(dateTime.toInstant(ZoneOffset.ofHours(1))); Date date = Date.from(dateTime.toInstant(ZoneId.systemDefault().getRules().getOffset(dateTime)));時(shí)間差異-持續(xù)時(shí)間和日期段
????????就像你所注意到的一樣,在上面的一個(gè)例子中,我們使用了一個(gè)Duration對(duì)象。Duration和Period是兩個(gè)日期之間時(shí)間的兩種表示方法,前者用秒和納秒來(lái)區(qū)分時(shí)間,后者使用年月日。
????????它們應(yīng)該在哪些情況下使用呢?如果你需要知道兩個(gè)LocalDate表示的時(shí)間之間日期段的時(shí)候,你可以選擇使用Period:
Period period = Period.between(LocalDate.parse("2018-01-18"), LocalDate.parse("2018-02-14"));????????當(dāng)你想要找出兩個(gè)日期之差(即時(shí)間間隔)的時(shí)候,你可以選擇使用Duration。
Duration duration = Duration.between(LocalDateTime.parse("2018-01-18T06:30"),LocalDateTime.parse("2018-02-14T22:58"));????????當(dāng)我們使用toString()方法輸出Period和Duration的時(shí)候,將會(huì)用到基于ISO-8601標(biāo)準(zhǔn)的一種特定格式。Period的模式是PnYnMnD,日期中n定義了當(dāng)前的年月日。P1Y2D3意思就是1年2個(gè)月3天。模式中的‘P’表示日期標(biāo)識(shí)符,它告訴我們接下來(lái)的格式表示的是日期。使用這種模式我們同樣可以創(chuàng)建一個(gè)基于使用parse()方法的string的日期。
// represents a period of 27 days Period period = Period.parse("P27D");????????使用Duration的時(shí)候,我們稍微偏離了ISO-8601標(biāo)準(zhǔn),因?yàn)镴ava8不使用同樣的模式。ISO-8601定義的模式是PnYnMnDTnHnMn.nS,它是建立在Period的模式基礎(chǔ)上并對(duì)其進(jìn)行了拓展的一種時(shí)間表示。在這個(gè)模式中,T是時(shí)間標(biāo)識(shí)符,所以,它后面定義的是時(shí)分秒。
????????Java8中,Duration使用了兩種模式,當(dāng)把一個(gè)String解析為一個(gè)Duration的時(shí)候使用PnDTnHnMn.nS模式,當(dāng)在一個(gè)Duration實(shí)例中調(diào)用toString方法的時(shí)候,使用PTnHnMn.nS模式。
????????最后同樣重要的是,我們可以使用相應(yīng)的方法類型來(lái)檢索一個(gè)時(shí)期或者時(shí)間中的任何一部分。各種類型的日期時(shí)間通過(guò)使用ChronoUnit枚舉類型也同樣支持。讓我們來(lái)看下面的例子:
// represents PT664H28M Duration duration = Duration.between(LocalDateTime.parse("2018-01-18T06:30"),LocalDateTime.parse("2018-02-14T22:58"));// returns 664 long hours = duration.toHours();// returns 664 long hours = LocalDateTime.parse("2018-01-18T06:30").until(LocalDateTime.parse("2018-02-14T22:58"),ChronoUnit.HOURS);時(shí)區(qū)時(shí)間和偏移時(shí)間
????????到目前為止,我們已經(jīng)展示了新日期時(shí)間API如何使事情變的更加簡(jiǎn)單。但是,真正不同的是在時(shí)區(qū)環(huán)境下更加簡(jiǎn)單的使用日期和時(shí)間。Java8為我們提供了ZonedDateTime和OffsetDateTime,前者是LocalDateTime針對(duì)特定區(qū)域(例如:法國(guó)/巴黎)的信息,后者LocalDateTime的偏移。兩者有什么不同呢?OffsetDateTime使用UTC/格林威治和制定日期之間的固定時(shí)差,ZonedDateTime制定了表示時(shí)間的區(qū)域,并且考慮到了夏令時(shí)。
????????轉(zhuǎn)換為這些類型是很簡(jiǎn)單的:
OffsetDateTime offsetDateTime = LocalDateTime.parse("2018-02-14T06:30").atOffset(ZoneOffset.ofHours(2)); // Uses DateTimeFormatter.ISO_OFFSET_DATE_TIME for which the default format is // ISO_LOCAL_DATE_TIME followed by the offset ("+HH:mm:ss"). OffsetDateTime offsetDateTime = OffsetDateTime.parse("2018-02-14T06:30+06:00");ZonedDateTime zonedDateTime = LocalDateTime.parse("2018-02-14T06:30").atZone(ZoneId.of("Europe/Paris")); // Uses DateTimeFormatter.ISO_ZONED_DATE_TIME for which the default format is // ISO_OFFSET_DATE_TIME followed by the the ZoneId in square brackets. ZonedDateTime zonedDateTime = ZonedDateTime.parse("2018-02-14T06:30+08:00[Asia/Macau]"); // note that the offset does not matter in this case. // The following example will also return an offset of +08:00 ZonedDateTime zonedDateTime = ZonedDateTime.parse("2018-02-14T06:30+06:00[Asia/Macau]");????????當(dāng)在它們之間轉(zhuǎn)換的時(shí)候,你必須要記住把ZoneDateTime轉(zhuǎn)換為OffsetDateTime的時(shí)候,需要考慮到夏令時(shí),反而言之,當(dāng)把OffsetDateTime轉(zhuǎn)換為ZonedDateTime的時(shí)候,意味著你將不會(huì)獲得區(qū)域的信息,也不適用夏令時(shí)的規(guī)則。應(yīng)為偏移沒(méi)有定義任何時(shí)區(qū)規(guī)則,也不會(huì)綁定到任何區(qū)域。
ZonedDateTime winter = LocalDateTime.parse("2018-01-14T06:30").atZone(ZoneId.of("Europe/Paris")); ZonedDateTime summer = LocalDateTime.parse("2018-08-14T06:30").atZone(ZoneId.of("Europe/Paris"));// offset will be +01:00 OffsetDateTime offsetDateTime = winter.toOffsetDateTime(); // offset will be +02:00 OffsetDateTime offsetDateTime = summer.toOffsetDateTime();OffsetDateTime offsetDateTime = zonedDateTime.toOffsetDateTime();OffsetDateTime offsetDateTime = LocalDateTime.parse("2018-02-14T06:30").atOffset(ZoneOffset.ofHours(5)); ZonedDateTime zonedDateTime = offsetDateTime.toZonedDateTime();????????現(xiàn)在,如果我們想要知道相對(duì)于我們所在時(shí)區(qū)特定區(qū)域的時(shí)間和偏移量,該怎么辦?這里同樣定義了一些方便的功能!
// timeInMacau represents 2018-02-14T13:30+08:00[Asia/Macau] ZonedDateTime timeInMacau = LocalDateTime.parse( "2018-02-14T13:30" ).atZone( ZoneId.of( "Asia/Macau" ) ); // timeInParis represents 2018-02-14T06:30+01:00[Europe/Paris] ZonedDateTime timeInParis = timeInMacau.withZoneSameInstant( ZoneId.of( "Europe/Paris" ) );OffsetDateTime offsetInMacau = LocalDateTime.parse( "2018-02-14T13:30" ).atOffset( ZoneOffset.ofHours( 8 ) ); OffsetDateTime offsetInParis = offsetInMacau.withOffsetSameInstant( ZoneOffset.ofHours( 1 ) );????????如果在任何時(shí)候,你都必須手動(dòng)在兩種類型之間轉(zhuǎn)換的話,將會(huì)很麻煩。在這方面,Spring Framework給我們提供了幫助。Spring為我們提供了一些開(kāi)箱即用的日期時(shí)間轉(zhuǎn)換器,這些轉(zhuǎn)換器注冊(cè)在ConversionRegistry,在org.springframework.format.datetime.standard.DateTimeConverters類中可以找到。
????這使用這些轉(zhuǎn)換器的時(shí)候,重要的是要知道在區(qū)域和偏移之間是不會(huì)轉(zhuǎn)換的。比如說(shuō),ZonedDateTimeToLocalDateTimeConverter將會(huì)返回它所指定的區(qū)域的LocalDateTime,而不是你應(yīng)用程序中所代表的。
ZonedDateTime zonedDateTime = LocalDateTime.parse("2018-01-14T06:30").atZone(ZoneId.of("Asia/Macau")); // will represent 2018-01-14T06:30, regardless of the region your application has specified LocalDateTime localDateTime = conversionService.convert(zonedDateTime, LocalDateTime.class);????最后重要的是,你可以檢索ZonId.getAvailableZoneIds()來(lái)查找所有可用的時(shí)區(qū),或者使用ZoneId.SHORT_IDS,它包含了一些簡(jiǎn)寫(xiě)版本的時(shí)區(qū),例如:EST,CST等等。
格式化—使用DateTimeFormatter
????????當(dāng)然,世界上不同的區(qū)域使用不同的格式來(lái)指定時(shí)間。一個(gè)應(yīng)用程序可能使用MM-dd-yyyy,另一個(gè)可能會(huì)使用dd/MM/yyyy.一些應(yīng)用程序想要解決這些不一致的格式,統(tǒng)一用yyyy-MM-dd來(lái)表示日期。使用java.util.Date的時(shí)候,我們很快的就會(huì)轉(zhuǎn)向使用多個(gè)格式化器。但是DateTimeFormatter類,為我們提供了操作模式,使我們可以使用單一的格式化器來(lái)處理多種格式!讓我們通過(guò)一些例子來(lái)看一下。
// Let’s say we want to convert all of patterns mentioned above. // 09-23-2018, 23/09/2018 and 2018-09-23 should all convert to the same LocalDate. DateTimeFormatter formatter = DateTimeFormatter.ofPattern("[yyyy-MM-dd][dd/MM/yyyy][MM-dd-yyyy]"); LocalDate.parse("09-23-2018", formatter); LocalDate.parse("23/09/2018", formatter); LocalDate.parse("2018-09-23", formatter);????????方括號(hào)中的內(nèi)容定義了模式中的可操作部分,通過(guò)使我們的各種格式可選,匹配string的第一個(gè)模式將會(huì)被用來(lái)轉(zhuǎn)換我們表示的日期。當(dāng)我們使用混合模式的時(shí)候,閱讀起來(lái)將會(huì)非常困難,所以,讓我們使用builder模式來(lái)創(chuàng)建我們的DateTimeFormatter。
DateTimeFormatter formatter = new DateTimeFormatterBuilder().appendOptional( DateTimeFormatter.ofPattern( "yyyy-MM-dd" ) ).optionalStart().appendPattern( "dd/MM/yyyy" ).optionalEnd().optionalStart().appendPattern( "MM-dd-yyyy" ).optionalEnd().toFormatter();????????這些是包含多種模式的基礎(chǔ)知識(shí),但是,如果我們的模式僅僅是略有不同該怎么辦呢?讓我們來(lái)看一下yyy-MM-dd和yyyy-MMM-dd。
// 2018-09-23 and 2018-Sep-23 should convert to the same LocalDate. // Using the ofPattern example we’ve used above will work: DateTimeFormatter formatter = DateTimeFormatter.ofPattern("[yyyy-MM-dd][yyyy-MMM-dd]" ); LocalDate.parse( "2018-09-23", formatter ); LocalDate.parse( "2018-Sep-23", formatter );// Using the ofPattern example where we reuse the common part of the pattern DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyy-[MM-dd][MMM-dd]" ); LocalDate.parse( "2018-09-23", formatter ); LocalDate.parse( "2018-Sep-23", formatter );????????但是,當(dāng)轉(zhuǎn)換為String的時(shí)候,不可以使用支持多種格式的格式化器,因?yàn)楫?dāng)我們使用格式化器把我們的日期轉(zhuǎn)換為string表示的時(shí)候,它也將使用可選模式。
LocalDate date = LocalDate.parse("2018-09-23"); // will result in 2018-09-232018-Sep-23 date.format(DateTimeFormatter.ofPattern("[yyyy-MM-dd][yyyy-MMM-dd]" )); // will result in 2018-09-23Sep-23 date.format(DateTimeFormatter.ofPattern( "yyyy-[MM-dd][MMM-dd]" ));????????21世紀(jì)以來(lái),很明顯的我們必須要考慮到全球化,所以我們想要為我們的用戶提供本地化的日期。為了確保你的DateTimeFormatter返回一個(gè)指定的本地日期,你可以簡(jiǎn)單的做以下一些工作:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "EEEE, MMM dd, yyyy" ).withLocale(Locale.UK);DateTimeFormatter formatter = new DateTimeFormatterBuilder().appendPattern("yyyy-MMM-dd" ).toFormatter(Locale.UK);????????可以使用Locale.getAvailableLocales()方法來(lái)找出可用的區(qū)域設(shè)置。
?????現(xiàn)在,你接受到日期模式可能比你使用的帶有更多的信息。一旦提供的日期表示不符合模式,DateTimeFormatter就會(huì)拋出異常。讓我們更進(jìn)一步的來(lái)探討這個(gè)問(wèn)題及其處理方法。
// The issue: this will throw an exception. LocalDate date = LocalDate.parse("2018-02-15T13:45"); // We provide a DateTimeFormatter that can parse the given date representation. // The result will be a LocalDate holding 2018-02-15. LocalDate date = LocalDate.parse("2018-02-15T13:45", DateTimeFormatter.ISO_LOCAL_DATE_TIME);讓我們來(lái)創(chuàng)建一個(gè)可以處理ISO日期、時(shí)間和日期時(shí)間模式的格式化器。
DateTimeFormatter formatter = new DateTimeFormatterBuilder().appendOptional( DateTimeFormatter.ISO_LOCAL_DATE ).optionalStart().appendLiteral( "T" ).optionalEnd().appendOptional( DateTimeFormatter.ISO_LOCAL_TIME ).toFormatter();????????現(xiàn)在我們可以完美的執(zhí)行以下內(nèi)容:
// results in 2018-03-16 LocalDate date = LocalDate.parse( "2018-03-16T06:30", formatter ); LocalDate date = LocalDate.parse( "2018-03-16", formatter ); // results in 06:30 LocalTime time = LocalTime.parse( "2018-03-16T06:30", formatter ); LocalTime time = LocalTime.parse( "06:30", formatter ); LocalDateTime localDateTime = LocalDateTime.parse( "2018-03-16T06:30", formatter );????????下一個(gè)問(wèn)題是什么呢?如果您試圖解析LocalDateTime的日期模式,該怎么辦?反之,如果您期望通過(guò)一個(gè)日期表示得到一個(gè)LocalTime,該怎么辦?
// will throw an exception LocalDateTime localDateTime = LocalDateTime.parse("2018-03-16", formatter); LocalDate localDate = LocalDate.parse("06:30", formatter);????????最后的這兩個(gè)問(wèn)題,并沒(méi)有單一的正確解決方法,但是它依據(jù)你需要什么,或者是這些日期和時(shí)間表示的是什么,或者可以表示什么?這種魔法般的方法可以在TemporalQuery的使用中找到,你也可以使用TemporalQuery為模式的一部分來(lái)創(chuàng)建缺省值。
????????如果我們開(kāi)始使用的是LocalDateTime,但是你只是想要一個(gè)LocalTime或者是一個(gè)LocalTime,你將會(huì)接受到LocalDateTime的對(duì)應(yīng)部分。為了創(chuàng)建一個(gè)LocalDateTime,我們需要它所持有的日期和時(shí)間的默認(rèn)值。日入說(shuō),如果你沒(méi)有提供日期的信息,我們將會(huì)得到一個(gè)當(dāng)前日期的返回值,如果你沒(méi)有提供時(shí)間信息,我么會(huì)認(rèn)為這是一天的起始。
????????由于我們正在返回一個(gè)LocalDateTime,它不會(huì)被解析為一個(gè)LocalDate或者是LocalTime,所以,讓我們使用ConversionService來(lái)得到 一個(gè)正確的格式。
TemporalQuery<TemporalAccessor> myCustomQuery = new MyCustomTemporalQuery(); // results in 2018-03-16 LocalDateTime localDateTime = conversionService.convert( formatter.parse( "2018-03-16", myCustomQuery ),?LocalDateTime.class ); // results in 00:00 LocalTime localTime = conversionService.convert( formatter.parse( "2018-03-16", myCustomQuery ),?LocalTime.class );class MyCustomTemporalQuery implements TemporalQuery<TemporalAccessor> {@Overridepublic TemporalAccessor queryFrom( TemporalAccessor temporal ) {LocalDate date = temporal.isSupported( ChronoField.EPOCH_DAY )? LocalDate.ofEpochDay( temporal.getLong( ChronoField.EPOCH_DAY ) ) : LocalDate.now();LocalTime time = temporal.isSupported( ChronoField.NANO_OF_DAY )? LocalTime.ofNanoOfDay( temporal.getLong( ChronoField.NANO_OF_DAY ) ) : LocalTime.MIN;return LocalDateTime.of( date, time );} }使用TemporalQuery我們可以檢查哪些信息是當(dāng)前的,并且可以為許多丟失的信息提供缺省值,這使我們可以很簡(jiǎn)單的在我們的應(yīng)用程序中使用合理的邏輯轉(zhuǎn)換為我們所需要的類型。
結(jié)論
大多數(shù)新功能都需要時(shí)間來(lái)理解和習(xí)慣,Java8 Date/Time API也不例外。新的API為我們提供了訪問(wèn)所需要的更好的正確格式,為我們提供了使用日期時(shí)間操作的更加標(biāo)準(zhǔn)、更具可讀性的方式。使用這些技巧和竅門(mén),我們幾乎可以涵蓋我們所有的用例。
總結(jié)
以上是生活随笔為你收集整理的Java 8 日期和时间解读的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 图说:为什么Java中的字符串被定义为不
- 下一篇: 直面Java第45期