Java的API提供了很多有用的组件,能帮助你构建复杂的应用。不过,Java API也不总是完美的。我们相信大多数有经验的程序员都会赞同Java 8之前的库对日期和时间的支持就非常不理想。然而,你也不用太担心:Java 8中引入全新的日期和时间API就是要解决这一问题。

1 LocalDate 、 LocalTime 、 Instant 、 Duration 以及 Period

让我们从探索如何创建简单的日期和时间间隔入手。 java.time 包中提供了很多新的类可以帮你解决问题,它们是 LocalDate 、 LocalTime 、 Instant 、 Duration 和 Period 。

1.1 LocalDate 和 LocalTime

LocalDate 类,该类的实例是一个不可变对象,它只提供了简单的日期,并不含当天的时间信息。另外,它也不附带任何与时区相关的信息。

你可以通过静态工厂方法 of 创建一个 LocalDate 实例。 LocalDate 实例提供了多种方法来读取常用的值,比如年份、月份、星期几等。

public static void main(String[] args) {
        //2014-3-18
        LocalDate date = LocalDate.of(2014, 3, 18);
        //2014
        int year = date.getYear();
        //march
        Month month = date.getMonth();
        //18
        int day = date.getDayOfMonth();
        DayOfWeek dow = date.getDayOfWeek();
        //这个月的天数
        int len = date.lengthOfMonth();
        //是否事闰年
        boolean leap = date.isLeapYear();
        System.out.println("year : " + year + " month : " + month + " day : " + day +
                " dow : " + dow + " len : " + len + " leap : " + leap);
    }

复制代码

似地,一天中的时间,比如13:45:20,可以使用 LocalTime 类表示。你可以使用 of 重载的两个工厂方法创建 LocalTime 的实例。第一个重载函数接收小时和分钟,第二个重载函数同时还接收秒。同 LocalDate 一样, LocalTime 类也提供了一些 getter 方法访问这些变量的值。

LocalTime time = LocalTime.of(13, 45, 20);
int hour = time.getHour();
int minute = time.getMinute();
int second = time.getSecond();
复制代码

LocalDate 和 LocalTime 都可以通过解析代表它们的字符串创建。使用静态方法 parse ,你可以实现这一目的:

LocalDate date = LocalDate.parse("2014-03-18");
LocalTime time = LocalTime.parse("13:45:20");
复制代码
1.2 合并日期和时间

这个复合类名叫 LocalDateTime ,是 LocalDate 和 LocalTime 的合体。它同时表示了日期和时间,但不带有时区信息,你可以直接创建,也可以通过合并日期和时间对象构造。

// 2014-03-18T13:45:20
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);
复制代码

通过它们各自的 atTime 或者 atDate 方法,向 LocalDate 传递一个时间对象,或者向LocalTime 传递一个日期对象的方式,你可以创建一个 LocalDateTime 对象。你也可以使用toLocalDate 或者 toLocalTime 方法,从 LocalDateTime 中提取 LocalDate 或者 LocalTime组件:

LocalDate date1 = dt1.toLocalDate();
LocalTime time1 = dt1.toLocalTime();
复制代码
1.3 Duration 和 Period

Duration 类的静态工厂方法 between 就是为比较两个时间而设计的。你可以创建两个 LocalTimes 对象、两个 LocalDateTimes对象,或者两个 Instant 对象之间的 duration:

Duration d1 = Duration.between(time1, time2);
Duration d1 = Duration.between(dateTime1, dateTime2);
复制代码

如果你需要以年、月或者日的方式对多个时间单位建模,可以使用 Period 类。使用该类的工厂方法 between ,你可以使用得到两个 LocalDate 之间的时长:

Period tenDays = Period.between(LocalDate.of(2014, 3, 8),
								LocalDate.of(2014, 3, 18));
复制代码

最后, Duration 和 Period 类都提供了很多非常方便的工厂类,直接创建对应的实例;换句话说,就像下面这段代码那样,不再是只能以两个temporal对象的差值的方式来定义它们的对象。

Duration threeMinutes = Duration.ofMinutes(3);
Duration threeMinutes = Duration.of(3, ChronoUnit.MINUTES);
Period tenDays = Period.ofDays(10);
Period threeWeeks = Period.ofWeeks(3);
Period twoYearsSixMonthsOneDay = Period.of(2, 6, 1);
复制代码

Duration 类和 Period 类共享方法



2 操纵、解析和格式化日期

2.1 比较直观的方式操纵 LocalDate 的属性
//2019-3-20
        LocalDate of = LocalDate.of(2019, 3, 20);
        //2018-3-20
        LocalDate localDate = of.withYear(2018);
        //2018-4-20
        LocalDate localDate1 = localDate.withMonth(4);
        //2018-9-20
        LocalDate with = localDate1.with(ChronoField.MONTH_OF_YEAR, 9);
复制代码

最后这一行中使用的 with 方法和get方法有些类似,它们都声明于 Temporal 接口,所有的日期和时间API类都实现这两个方法,它们定义了单点的时间,比如 LocalDate 、 LocalTime 、 LocalDateTime 以及 Instant 。更确切 地说,使用 get 和 with 方法,我们可以将 Temporal 对象值的读取和修改区分开。

2.2 以相对方式修改 LocalDate 对象的属性
//2014-3-18
LocalDate date1 = LocalDate.of(2014, 3, 18);
//2014-3-25
LocalDate date2 = date1.plusWeeks(1);

LocalDate date3 = date2.minusYears(3);
LocalDate date4 = date3.plus(6, ChronoUnit.MONTHS);
复制代码

最后一行使用的 plus 方法也是通用方法,它和 minus 方法都声明于 Temporal 接口中。通过这些方法,对 TemporalUnit 对象加上或者减去一个数字,我们能非常方便地将 Temporal 对象前溯或者回滚至某个时间段,通过ChronoUnit 枚举我们可以非常方便地实现 TemporalUnit 接口。



2.3 使用 TemporalAdjuster

有的时候,你需要进行一些更加复杂的操作,比如,将日期调整到下个周日、下个工作日,或者是本月的最后一天。这时,你可以使用重载版本的 with 方法,向其传递一个提供了更多定制化选择的 TemporalAdjuster 对象,更加灵活地处理日期。对于最常见的用例,日期和时间API已经提供了大量预定义的TemporalAdjuster 。你可以通过 TemporalAdjuster 类的静态工厂方法访问它们。

//2014-03-18
LocalDate date1 = LocalDate.of(2014, 3, 18);
//2014-03-23
LocalDate date2 = date1.with(nextOrSame(DayOfWeek.SUNDAY));
//2014-03-31
LocalDate date3 = date2.with(lastDayOfMonth());
复制代码



正如我们看到的,使用 TemporalAdjuster 我们可以进行更加复杂的日期操作,而且这些方 法的名称也非常直观,方法名基本就是问题陈述。此外,即使你没有找到符合你要求的预定义的 TemporalAdjuster ,创建你自己的 TemporalAdjuster 也并非难事。实际上, Temporal- Adjuster 接口只声明了单一的一个方法(这使得它成为了一个函数式接口)。

@FunctionalInterface
public interface TemporalAdjuster {
	Temporal adjustInto(Temporal temporal);
}
复制代码

这意味着 TemporalAdjuster 接口的实现需要定义如何将一个 Temporal 对象转换为另一个 Temporal 对象。你可以把它看成一个 UnaryOperator

2.4 实现一个定制的 TemporalAdjuster

设计一个 NextWorkingDay 类,该类实现了 TemporalAdjuster 接口,能够计算明天的日期,同时过滤掉周六和周日这些节假日。格式如下所示:

public class MyTemporalAdjuster {
    public static void main(String[] args) {
        LocalDate of = LocalDate.of(2019, 3, 20);
        LocalDate with = of.with(new NewTemporalAdjuster());
        System.out.println("with = " + with);
    }
}

//如果当天的星期介于周一至周五之间,日期向后移动一天;如果当天是周六或者周日,则返回下一个周一
class  NewTemporalAdjuster implements TemporalAdjuster {
    @Override
    public Temporal adjustInto(Temporal temporal) {
        DayOfWeek of = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK));
        int dayToAdd = 1;
        if (of == DayOfWeek.FRIDAY){
            dayToAdd = 3;
        }else if (of == DayOfWeek.SATURDAY){
            dayToAdd = 2;
        }
        return temporal.plus(dayToAdd, ChronoUnit.DAYS);
    }
}
复制代码
2.5 打印输出及解析日期--时间对象

处理日期和时间对象时,格式化以及解析日期时间对象是另一个非常重要的功能。新的java.time.format 包就是特别为这个目的而设计的。这个包中,最重要的类是 DateTimeFormatter 。创建格式器最简单的方法是通过它的静态工厂方法以及常量。像 BASIC_ISO_DATE和 ISO_LOCAL_DATE 这 样 的 常 量 是 DateTimeFormatter 类 的 预 定 义 实 例 。 所 有 的DateTimeFormatter 实例都能用于以一定的格式创建代表特定日期或时间的字符串。

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

你也可以通过解析代表日期或时间的字符串重新创建该日期对象。所有的日期和时间API都提供了表示时间点或者时间段的工厂方法,你可以使用工厂方法 parse 达到重创该日期对象的目的:

LocalDate date1 = LocalDate.parse("20140318",DateTimeFormatter.BASIC_ISO_DATE);
LocalDate date2 = LocalDate.parse("2014-03-18",DateTimeFormatter.ISO_LOCAL_DATE);
复制代码
2.6 按照某个模式创建 DateTimeFormatter
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
LocalDate date1 = LocalDate.of(2014, 3, 18);
String formattedDate = date1.format(formatter);
LocalDate date2 = LocalDate.parse(formattedDate, formatter);
复制代码

LocalDate 的 formate 方法使用指定的模式生成了一个代表该日期的字符串。