11 CompletableFuture: 组合式异步编程

12 新的日期和时间API

  • Date 和 Calendar这两个类增加了程序员的困惑,到底该使用哪一个类?此外,有的特性只在某一个类有提供,比如用于以语言无关方式格式化和解析日期或时间的 DateFormat 方法就只在 Date 类里有。
  • DateFormat 不是线程安全的。这意味着两个线程如果尝试使用同一个 formatter 解析日期,可能会得到无法预期的结果。
  • Date 和 Calendar 类都是可以变的。这种设计会将难以维护。
  • 为了解决这些问题,Oracle 决定在原生的 Java API 中提供高质量的日期和时间支持。Java 8 在 java.time 包中整合了很多 Joda-Time 的特性。

12.1 主要类

LocalDateLocalTimeInstantDuration *以及 *Period

12.1.1 使用LocalDateLocalTime

创建一个 LocalDate 对象并读取其值。

1
2
3
4
5
6
7
8
LocalDate date = LocalDate.of(2021, 2, 5);
int year = date.getYear();
Month month = date.getMonth();
int dayOfMonth = date.getDayOfMonth();
DayOfWeek dayOfWeek = date.getDayOfWeek();
int lengthOfMonth = date.lengthOfMonth();
boolean leapYear = date.isLeapYear();
System.out.println("LocalDate.now() = " + LocalDate.now());

也可以通过传递一个 TemporalField 参数给 get 方法拿到同样的信息。ChronoField 枚举实现了 TemporalField接口。

1
2
int year1 = date.get(ChronoField.YEAR);
int month1 = date.get(ChronoField.MONTH_OF_YEAR);

类似地,一天中的时间,比如 13:45:20,可以使用 LocalTime 类表示。

1
2
3
4
LocalTime time = LocalTime.of(13, 45, 20);
LocalTime now = LocalTime.now();
int hour = now.getHour();
int nano = now.getNano();

LocalDateLocalTime 都可以通过静态方法 parse 解析代表字符串创建。可以向 parse 方法传递一个 DateTimeFormatter

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

12.1.2 合并日期和时间

LocalDateTime,是 LocalDate 和 LocalTime 的合体,不带有时区信息,可以直接创建,也可以通过合并日期和时间对象创建。

1
2
3
4
5
6
7
8
LocalDateTime dt1 = LocalDateTime.of(2021, Month.FEBRUARY, 5, 17, 21, 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();

12.1.3 机器日期和时间

可以通过向静态工厂方法 ofEpochSecond 传递一个代表秒数的值创建一个该类的实例。 ofEpochSecond 的重载版本,接收第二个以纳秒为单位的参数值,对传入作为秒数的参数进行调整。重载的版本会调整纳秒参数,确保保存的纳秒分片在 0 到 999 999 999 之间。下面几个方法都会返回几乎同样的 Instant 对象。

1
2
3
4
Instant.ofEpochSecond(3);
Instant.ofEpochSecond(3, 0);
Instant.ofEpochSecond(2, 1_000_000_000);
Instant.ofEpochSecond(4, -1_000_000_000);

Instant 设计初衷是便于机器使用。它包含的是由秒及纳秒所构成的数字。所以,它无法处理常见的的时间单位,日月年等。

12.1.4 定义DurationPeriod

在本节之前出现的所有类都实现了 Temporal 接口。Duration 类的静态工厂方法 between 可以创建两个 Temporal 对象之间的间隔。

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

1
2
3
4
5
6
7
8
Period tenDays = Period.between(LocalDate.of(2021, 2, 5), LocalDate.of(2021, 2, 15));

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 类有许多的共享方法,如果用到的话当然还是去看源码,在这列表格也没什么用。

上面的这些日期和时间对象都是不可修改的,这是为了更好地支持函数式编 程,确保线程安全。

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

withAttribute 方法会创建对象的一个副本,并按照需要修改它的属性。下面的代码都返回一个修改了属性的对象,它们都不会修改原来的对象。

1
2
3
4
5
LocalDate date1 = LocalDate.of(2021, 2, 5);
LocalDate date2 = date1.withYear(2021);
LocalDate date3 = date2.withDayOfMonth(5);
// 第一个参数是一个 TemporalField 对象
LocalDate date4 = date3.with(ChronoField.MONTH_OF_YEAR, 9);

使用 getwith 方法,可以将 Temporal 对象值的读取和修改区分开。还能以声明的方式操纵LocalDate对象。

1
2
3
4
LocalDate date1 = LocalDate.of(2021, 2, 5);
LocalDate date2 = date1.plusWeeks(1);
LocalDate date3 = date2.minusYear(3);
LocalDate date4 = date3.plus(6, ChronoUnit.MONTHS);

表示时间点的日期-时间类的通用方法,用到的时候当然还是 Ctrl + Click

表示时间点的日期-时间类的通用方法

12.2.1 使用TemporalAdjuster

有时候需要进行一些复杂操作,比如,将日期调整到下个周日、下个工作日,或者是本月的最后一天。可以使用重载版本的 with 方法,向其传递一个提供了更多定制化选择的 TemporalAdjuster 对象。

1
2
3
4
5
6
// 周五
LocalDate date1 = LocalDate.of(2021, 2, 5);
// 周日,date2 = 2021-02-07
LocalDate date2 = date1.with(nextOrSame(DayOfWeek.SUNDAY));
// 这个月的最后一天,date3 = 2021-02-28
LocalDate date3 = date2.with(lastDayOfMonth());

TemporalAdjuster类中的工厂方法

容易发现 TemporalAdjuster 是一个函数式接口。

1
2
3
4
@FunctionalInterface
public interface TemporalAdjuster {
Temporal adjustInto(Temporal temporal);
}

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

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

12.2.2 打印输出及解析日期-时间对象

1
2
3
4
5
6
7
8
LocalDate date = LocalDate.of(2021, 2, 5);
// s1 = 20210205
String s1 = date.format(DateTimeFormatter.BASIC_ISO_DATE);
// s2 = 2021-02-05
String s2 = date.format(DateTimeFormatter.ISO_LOCAL_DATE);

// 当然也可以反向解析, parse = 2021-02-05
LocalDate parse = LocalDate.parse("20210205", DateTimeFormatter.BASIC_ISO_DATE);

按照某个格式创建 DateTimeFormatter

1
2
3
4
5
6
7
8
9
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
LocalDate date6 = LocalDate.of(2021, 2, 5);
// format = 05/02/2021
String format = date6.format(formatter);

// 创建本地化的 `DateTimeFormatter`。
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy MMMM dd", Locale.CHINA);
// format1 = 2021 二月 05
String format1 = date6.format(formatter);

12.3 处理不同的时区和历法

没什么重要的内容

评论