1. 程式人生 > >Java 8-新的日期和時間API

Java 8-新的日期和時間API

日期和時間:LocalDate和LocalTime

LocalDate類,該類的例項是一個不可變物件,它只提供了簡單的日期,並不含當天的時間資訊。另外,它也不附帶任何與時區相關的資訊。

可以通過靜態工廠方法of建立一個LocalDate例項。LocalDate例項提供了多種方法來讀取常用的值,比如年份、月份、星期幾等。

LocalDate date=LocalDate.of(2014,3,18);//2014-03-18
int year=date.getYear();//2014
Month month=date.getMonth();//MARCH
int day=date.getDayOfMonth();//18
DayOfWeek dayOfWeek=date.getDayOfWeek();//TUESDAY int len=date.lengthOfMonth();//31(days in March) boolean leap=date.isLeapYear();//false //使用工廠方法從系統時鐘獲取當前的日期 LocalDate today=LocalDate.now();

還可以使用傳遞一個TemporalField引數給get方法拿到同樣的資訊。TemporalField是一個介面,它定義瞭如何訪問temporal物件某個欄位的值。ChronoField列舉實現了這一介面。

int year=date.get(ChronoField.YEAR);
int
month=date.get(ChronoField.MONTH_OF_YEAR); int day=date.get(ChronoField.DAY_OF_MONTH);

LocalTime

//建立並讀取其值
LocalTime time=LocalTime.of(13,45,20);
int hour=time.getHour();
int minute=time.getMinute();
int second=time.getSecond();

LocalDate和LocalTime都可以通過解析代表它們的字串建立。

LocalDate date=LocalDate.parse("2014-03-18"
); LocalTime time=LocalTime.parse("13:45:20");

可以向parse方法傳遞一個DateTimeFormatter。該類的例項定義瞭如何格式化一個日期或者時間物件。

合併的日期和時間:LocalDateTime

LocalDateTime。它同時表示了日期和時間,但不帶有時區資訊,可以直接建立,也可以通過合併日期和時間物件構造。

LocalDateTime dt1=LocalDateTime.of(2014,Month.MARCH,18,13,45,20);
LocalDateTime dt2=LocalDateTime.of(date,time);
LocalDateTime dt3=date.atTime(13,45,20);
LocalDateTime dt4=date.atTime(time);
LocalDateTime dt5=time.atDate(date);

提取日期或時間

LocalDate date1=dt1.toLocalDate();
LocalTime time1=dt1.toLocalTime();

機器的日期和時間格式:Instant

我們習慣於以星期幾、幾號、幾點、幾分這樣的方式理解日期和時間。但這種方式對於計算機而言並不容易理解。從計算機的角度來看,建模時間最自然的格式時表示一個持續時間段上某個點的單一大整數型。

這也是新的java.time.Instant類對時間建模的方式,基本上它是以Unix元年時間(傳統的設定為UTC時區1970年1月1日午夜時分)開始所經歷的秒數進行計算。

可以通過向靜態工廠方法ofEpochSecond傳遞一個代表秒數的值建立一個該類的例項。靜態工廠方法ofEpochSecond還有一個增強的過載版本,它接收第二個以納秒為單位的引數值,對傳入作為秒數的引數進行調整。過載的版本會調整納秒引數,確保儲存的納秒分片在0到999 999 999之間。

Instant.ofEpochSecond(3);
Instant.ofEpochSecond(3,0);
Instant.ofEpochSecond(2,1_000_000_000);//2秒之後再加上100萬納秒(1秒)
Instant.ofEpochSecond(4,-1_000_000_000);//4秒之後再減去100萬納秒(1秒)

列印當前時間

Instant.now();

時間段:Duration和Period

建立兩個時間之間的duration

Duration d1=Duration.between(time1,time2);
Duration d1=Duration.between(dateTime1,dateTime2);
Duration d1=Duration.between(instant1,instant2);

由於Duration類主要用於以秒和納秒衡量時間的長度,你不能僅向between方法傳遞一個LocalDate物件做引數。

如果需要以年、月或者日的方式對多個時間單位建模,可以使用Period類。

Period tenDays=Period.between(LocalDate.of(2014,3,8),
                             LocalDate.of(2014,3,18));

建立Duration和Period物件

Duration threeMinutes=Duration.ofMinutes(3);
Duration threeMinutes=Duration.of(3,ChronoUnit.MINUTES);

Period tenDays=Period.ofDays(10);
Period tenDays=Period.ofWeeks(3);
Period twoYearsSixMonthsOneDay=Period.of(2,6,1);

Duration類和Period類共享了很多相似的方法

方法名 是否是靜態方法 方法描述
between 建立兩個時間點之間的interval
from 由一個臨時時間點建立interval
of 由它的組成部分建立interval的例項
parse 由字串建立interval的例項
addTo 建立該interval的副本,並將其疊加到某個指定的temporal物件
get 讀取該interval的狀態
isNegative 檢查該interval是否為負值,不包含零
isZero 檢查該interval的時長是否為零
minus 通過減去一定的時間建立該interval的副本
multipliedBy 將interval的值乘以某個標量建立該interval的副本
negated 以忽略某個時長的方式建立該interval的副本
plus 以增加某個指定的時長的方式建立該interval的副本
substractFrom 從指定的temporal物件中減去該interval

我們介紹的這些日期-時間物件都是不可修改的,這是為了更好地支援函數語言程式設計,確保執行緒安全。

操縱、解析和格式化日期

如果你已經有一個LocalDate物件,想要建立它的一個修改版,最直接也最簡單的方法是使用withAttribute方法。withAttribute方法會建立物件的一個副本,並按照需要修改它的屬性。下面的這段程式碼中所有的方法都返回了一個修改了屬性的物件。它們都不會修改原來的物件。

LocalDate date1=LocalDate.of(2014,3,18);//2014-03-18
LocalDate date2=date1.withYear(2011);//2011-03-18
LocalDate date3=date2.withDayOfMonth(25);//2011-03-25
LocalDate date4=date3.with(ChronoField.MONTH_OF_YEAR,9);//2011-09-25

採用更通用的with方法能達到同樣的目的,它接受的第一個引數是一個TemporalField物件。

可以以宣告的方式操縱LocalDate物件。比如,你可以像下面這段程式碼那樣加上或者減去一段時間。

以想對方式修改LocalDate物件的屬性

LocalDate date1=LocalDate.of(2014,3,18);//2014-03-18
LocalDate date2=date1.plusWeeks(1);
LocalDate date3=date2.minuxYears(3);
LocalDate date4=date3.plus(6,ChronoUnit.MONTHS);

像LocalDate、LocalTime、LocalDateTime以及Instant這樣表示時間點的日期-時間類提供了大量通用的方法

方法名 是否是靜態方法 描述
from 依據傳入的Temporal建立物件例項
now 依據系統時鐘建立Temporal物件
of 由Temporal物件的某個部分建立該物件的例項
parse 由字串建立Temporal物件的例項
atOffset 將Temporal物件和某個時區偏移相結合
atZone 將Temporal物件和某個時區相結合
format 使用某個指定的格式器將Temporal物件轉換為字串(Instant類不提供該方法)
get 讀取Temporal物件的某一部分的值
minus 建立Temporal物件的一個副本,通過將當前Temporal物件的值減去一定的時長建立該副本
plus 建立Temporal物件的一個副本,通過將當前Temporal物件的值加上一定的時長建立該副本
with 以該Temporal物件為模板,對某些狀態進行修改建立該物件的副本
LocalDate date=LocalDate.of(2014,3,18);
date=date.with(ChronoField.MONTH_OF_YEAR,9);
date=date.plusYears(2).minusDays(10);
date.withYear(2011);

使用TemporalAdjuster

有的時候,你需要進行一些更加複雜的操作,比如,將日期調整到下個週日、下個工作日,或者是本月的最後一天。這時,你可以使用過載版本的with方法,向其傳遞一個提供了更多定製化選擇的TemporalAdjuster物件,更加靈活地處理日期。可以通過TemporalAdjuster類的靜態工廠方法訪問它們。

import static java.time.temporal.TemporalAdjusters.*;

LocalDate date1=LocalDate.of(2014,3,18);
LocalDate date2=date1.with(nextOrSame(DayOfWeek.SUNDAY));
LocalDate date3=date2.with(lastDayOfMonth());

TemporalAdjuster中包含的工廠方法列表

方法名 描述
dayOfWeekInMonth 建立一個新的日期,它的值為同一個月中每一週的第幾天
firstDayOfMonth 建立一個新的日期,它的值為當月的第一天
firstDayOfNextMonth 建立一個新的日期,它的值為下個月的第一天
firstDayOfNextYear 建立一個新的日期,它的值為明年的第一天
firstDayOfYear 建立一個新的日期,它的值為當年的第一天
firstInMonth 建立一個新的日期,它的值為同一個月中,第一個符合星期幾要求的值
lastDayOfMonth 建立一個新的日期,它的值為當月的最後一天
lastDayOfNextMonth 建立一個新的日期,它的值為下月的最後一天
lastDayOfNextYear 建立一個新的日期,它的值為明年的最後一天
lastDayOfYear 建立一個新的日期,它的值為今年的最後一天
lastInMonth 建立一個新的日期,它的值為同一個月中,最後一個複合星期幾要求的值
next/previous 建立一個新的日期,並將其值設定為日期調整後或調整前,第一個符合指定星期幾要求的日期
nextOrSame/previousOrSame 建立一個新的日期,並將其值設定為日期調整後或者調整前,第一個符合指定星期幾要求的日期,如果該日期已經符合要求,直接返回該物件

使用TemporalAdjuster我們可以進行更加複雜的日期操作,而且這些方法的名稱也非常直觀,方法名基本就是問題陳述。如果沒有符合要求的預定義的TemporalAdjuster,可以建立自己的TemporalAdjuster。TemporalAdjuster介面只聲明瞭單一的一個方法。

@FunctionalInterface
public interface TemporalAdjuster{
    Temporal adjustInto(Temporal temporal);
}

TemporalAdjuster介面的實現需要定義如何將一個Temporal物件轉換為另一個Temporal物件。可以把它看成一個UnaryOperator。

實現一個定製的TemporalAdjuster

設計一個NextWorkingDay類,該類實現了TemporalAdjuster介面,能夠計算明天的日期,同時過濾掉週六和週日這些節假日。格式如下:

date=date.with(new NextWorkingDay());

如果當天的星期介於週一至週五之間,日期向後移動一天;如果當天是週六或週日,則返回下一個週一。

public class NextWorkingDay implements TemporalAdjuster{
    @Override
    public Temporal adjustTo(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通常情況下將日期往後順延一天,如果當天是週六或者週日,則依據情況分別將日期順延3天或者2天。由於TemporalAdjuster是一個函式式介面,你只能以Lambda表示式的方式向該adjuster介面傳遞行為:

date=date.with(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);
});

如果希望在程式碼的多個地方使用同樣的方式去操作日期,建議將他的邏輯封裝到一個類中。對於你經常使用的操作,都應該採用類似的方式,進行封裝。最終,你會建立自己的類庫,讓你和你的團隊能輕鬆地實現程式碼複用。

如果你想要使用Lambda表示式定義TemporalAdjuster物件,推薦使用TemporalAdjusters類的靜態工廠方法ofDateAdjuster,它接受一個UnaryOperator型別的引數。

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);
    }
);

date=date.with(nextWorkingDay);

列印輸出及解析日期-時間物件

處理日期和時間物件時,格式化以及解析日期-時間物件時另一個非常重要的功能。新的java.time.format包就是特別為這個目的而設計的。這個包中,最重要的類時DateTimeFormatter。建立格式器最簡單的方法是通過它的靜態工廠方法以及常量。像BASIC_ISO_DATE和ISO_LOCAL_DATE這樣的常量是DateTimeFormatter類的預定義例項。所有的DateTimeFormatter例項都能用於以一定格式建立代表特定日期或時間的字串。

LocalDate date=LocalDate.of(2014,3,18);
String s1=date.format(DateTimeFormatter.BASIC_ISO_DATE);//20140318
String s2=date.format(DateTimeFormatter.ISO_LOCAL_DATE);//2014-03-18

可以通過解析代表日期或時間的字串重新建立該日期物件。所有的日期和時間API都提供了表示時間點或者時間段的工廠方法,你可以使用工廠方法parse達到重創該日期物件的目的。

LocalDate date1=LocalDAte.parse("20140318",DateTimeFormatter.BASIC_ISO_DATE);
LocalDate date2=LocalDate.parse("2014-03-18",DateTimeFormatter.ISO_LOCAL_DATE);

和老的java.util.DateFormat相比較,所有的DateTimeFormatter例項都是執行緒安全的。所以,你能夠以單例模式建立格式器例項,就像DateTimeFormatter所定義的那些常量,並能在多個執行緒間共享這些例項。DateTimeFormatter類還支援一個靜態工廠方法,它可以按照某個特定的模式建立格式器。

DateTimeFormatter formatter=DateTimeFormatter.ofPattern("dd/MM/yyyy");
LocalDate date1=LocalDate.of(2014,3,18);
String formattedDate=date1.format(formatters);
LocalDate date2=LocalDate.parse(formattedDate,formatter);

LocalDate的formate方法使用指定的模式生成了一個代表該日期的字串。緊接著,靜態的parse方法使用同樣的格式器解析了剛才生成的字串,並重建了該日期物件。

ofPattern方法也提供了一個過載的版本,使用它你可以建立某個Locale的格式器

DateTimeFormatter italianFormatter=
                DateTimeFormatter.ofPattern("d. MMMM yyyy",Locale.ITALIAN);
LocalDate date1=LocalDate.of(2014,3,18);
String fromattedDate=date.format(italianFormatter);//18. marzo 2014
LocalDate date2=LocalDate.parse(formattedDate,italianFormatter);

最後,如果還需要更加細粒度的控制,DateTimeFormatterBuilder類還提供了更復雜的格式器,可以選擇恰當的方法,一步一步地構造自己的格式器。另外,它還提供了非常強大的解析功能,比如區分大小寫的解析、柔性解析(允許解析器使用啟發式的機制去解析輸入,不精確地匹配指定的模式)、填充,以及在格式器中指定可選節。

比如,可以通過DateTimeFormatterBuilder自己程式設計實現我們在程式碼清單中使用的italianFormatter

DateTimeFormatter italianFormatter=new DateTimeFormatterBuilder()
        .appendText(ChronoField.DAY_OF_MONTH)
        .appendLiteral(". ")
        .appendText(ChronoField.MONTH_OF_YEAR)
        .appendLiteral(" ")
        .appendText(ChronoField.YEAR)
        .parseCaseInsensitive()
        .toFormatter(Locale.ITALIAN);