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

你可能感兴趣的:(Java 8-新的日期和时间API)