第十二章:新的日期时间API

在Java 8之前,我们常用的日期时间API是java.util.Datejava.util.Calendar这两个类。

如果我们要构建一个指定年月日的日期时间对象,比如2019-9-2,使用java.util.Date类的构造方法Date(int year, int month, int date),传入的年份year参数必须是年份2019减去1900,即传入119。每次创建对象前还需要先进行计算,很不方便。

JDK 1.1提供的java.util.Calendar类在使用时也有很多不方便的地方,比如对一个日期加两天,使用add方法,传入2;对一个日期减两天,也使用add方法,传入-2。还有一点是这个类默认每周第一天是周日。使用起来也有点不方便。

归根到底,JDK1.8之前的日期时间API都是可变的,是线程不安全的。

另外,对时间日期进行格式化的类SimpleDateFormatjava.text包下,该类的定义位置不规范。它也是线程不安全的。

而在Java 8中,时间格式转化器是java.time.format.DateTimeFormatter类,它被声明为final,是不可变的类,线程安全。

另外,Java 8中提供的新日期时间API包含两类:一个是为了便于人阅读使用,包含LocalDateLocalTimeLocalDateTime这三个类,它们都是用final修饰的类,是不可变的对象,分别表示ISO-8601日历系统中的日期、时间、日期和时间。另外一个是便于机器处理的类,Instant:用来表示机器的日期和时间格式:时间戳。

ISO-8601日历系统:是国际标准化组织制定的现代公民的日期和时间的表示法。
时间戳:从UNIX元年:1970年1月1日 00:00:00到某个时间之间的毫秒值。

传统时间格式转换器SimpleDateFormat线程安全问题演示

当多个线程同时操作同一个SimpleDateFormat对象时,就会出现线程安全问题。

演示代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String[] args) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// 使用线程池模拟多线程
ExecutorService threadPool = Executors.newFixedThreadPool(10);
// 定义解析日期字符串任务:使用SimpleDateFormat对象解析
Callable<Date> task = () -> sdf.parse("2019-8-29");
// 存储结果容器
List<Future<Date>> result = new ArrayList<>();
for (int i = 0; i < 10; i++) {
// 执行得到解析结果
result.add(threadPool.submit(task));
}
// 遍历输出
for (Future<Date> r : result) {
System.out.println(r.get());
}
threadPool.shutdown();
}

异常信息如下:

1
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.NumberFormatException: multiple points

解决方法:
1、将SimpleDateFormat定义为方法内的局部变量,且方法中没有多线程的操作。
2、使用ThreadLocal进行线程封闭。为每个线程保存一个SimpleDateFormat对象。

传统时间格式转换器线程安全问题解决方案:使用ThreadLocal进行线程封闭

线程封闭类代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package cn.org.lilu.chapter12;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
* @Auther: lilu
* @Date: 2019/8/29
* @Description: 传统时间格式转换器线程安全问题解决方案:使用ThreadLocal进行线程封闭
*/
public class TraditionalSimpleDateFormatThreadLocal {
private static final ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};

public static Date convert(String source) throws Exception {
return threadLocal.get().parse(source);
}
}

演示代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String[] args) throws Exception {
// 使用线程池模拟多线程
ExecutorService threadPool = Executors.newFixedThreadPool(10);
// 定义解析日期字符串任务:每个任务都有一份SimpleDateFormat对象的副本
Callable<Date> task = () -> TraditionalSimpleDateFormatThreadLocal.convert("2019-8-29");
// 解析结果容器
List<Future<Date>> result = new ArrayList<>();
for (int i = 0; i < 10; i++) {
// 执行得到解析结果
result.add(threadPool.submit(task));
}
// 遍历输出
for (Future<Date> r : result) {
System.out.println(r.get());
}
threadPool.shutdown();
}

运行结果正常。

Java 8新的时间格式转化器DateTimeFormatter

DateTimeFormatter类定义在java.time.format包下,且声明为final类,不可变,线程安全。

代码演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String[] args) throws Exception {
// 按照哪种格式进行格式转换
// DateTimeFormatter dtf = DateTimeFormatter.ISO_LOCAL_DATE;
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMdd");
// LocalDate.parse("20190829",dtf):将第一个字符串参数按照第二个参数定义的格式器解析,返回一个LocalDate对象
Callable<LocalDate> task = () -> LocalDate.parse("20190829",dtf);
ExecutorService threadPool = Executors.newFixedThreadPool(10);
List<Future<LocalDate>> result = new ArrayList<>();
for (int i = 0; i < 10; i++) {
result.add(threadPool.submit(task));
}
// 遍历输出
for (Future<LocalDate> r : result) {
System.out.println(r.get());
}
threadPool.shutdown();
}

Java 8日期时间API:LocalDate、LocalTime、LocalDateTime

这三个类的用法几乎一样,LocalDate表示日期,LocalTime表示时间,LocalDateTime包含前两者,表示日期和时间。

可由前两个类组合出第三个类,也可由第三个类提取出前两个类。

年月日对象:LocalDate

可使用静态工厂方法now获取当前日期;

可使用静态工厂方法of创建一个LocalDate日期对象,可从一个LocalDate日期对象中获取该日期的年份、月份、这个月的第几天、这周的星期几、今年的第几天、这个月的长度(有几天)和是否为闰年等信息。

代码演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* LocalDate:年月日
*/
@Test
public void testLocalDate() {
// 静态工厂方法now获取当前日期
LocalDate now = LocalDate.now();
// 静态工厂方法of创建一个LocalDate实例
LocalDate date = LocalDate.of(2019,8,26);
// 年份
int year = date.getYear();
// 月份
Month month = date.getMonth();
// 这个月第几天
int dayOfMonth = date.getDayOfMonth();
// 这周星期几
DayOfWeek dayOfWeek = date.getDayOfWeek();
// 今年第几天
int dayOfYear = date.getDayOfYear();
// 这个月的长度(有几天)
int lengthOfMonth = date.lengthOfMonth();
// 是否闰年
boolean leapYear = date.isLeapYear();
System.out.println(now); // 2019-09-02
System.out.println(year); // 2019
System.out.println(month); // AUGUST
System.out.println(dayOfMonth); // 26
System.out.println(dayOfWeek); // MONDAY
System.out.println(dayOfYear); // 238
System.out.println(lengthOfMonth); // 31
System.out.println(leapYear); // false
}

时分秒对象:LocalTime

可使用静态工厂方法now获取当前时间的时分秒(包含纳秒)。

可使用静态工厂方法of创建一个LocalTime时间对象,可从一个LocalTime时间对象中获取该时间的时、分、秒和纳秒等信息。

of方法包含三个重载,方法签名如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* @param hour the hour-of-day to represent, from 0 to 23
* @param minute the minute-of-hour to represent, from 0 to 59
*/
public static LocalTime of(int hour, int minute);

/**
* @param hour the hour-of-day to represent, from 0 to 23
* @param minute the minute-of-hour to represent, from 0 to 59
* @param second the second-of-minute to represent, from 0 to 59
*/
public static LocalTime of(int hour, int minute, int second);

/**
* @param hour the hour-of-day to represent, from 0 to 23
* @param minute the minute-of-hour to represent, from 0 to 59
* @param second the second-of-minute to represent, from 0 to 59
* @param nanoOfSecond the nano-of-second to represent, from 0 to 999,999,999
*/
public static LocalTime of(int hour, int minute, int second, int nanoOfSecond);

使用代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* LocalTime:时分秒
* 一天中的时间,比如:13:45:20,可以使用LocalTime类表示
* 可以使用of重载的三个静态工厂方法创建LocalTime实例
* 第一个重载方法接收小时和分钟
* 第二个重载方法同时还接收秒
* 第三个重载方法同时还接收纳秒
*/
@Test
public void testLocalTime() {
LocalTime now = LocalTime.now();
LocalTime localTime = LocalTime.of(13, 45, 20,1);
int hour = localTime.getHour();
int minute = localTime.getMinute();
int second = localTime.getSecond();
int nano = localTime.getNano();
System.out.println(now); // 19:47:51.212
System.out.println(hour); // 13
System.out.println(minute); // 45
System.out.println(second); // 20
System.out.println(nano); // 1
}

日期时间字符串解析

LocalDate和LocalTime都可以通过解析代表它们的字符串创建。使用静态方法parse。一旦传递的字符串参数无法被解析为合法的LocalDate或LocalTime对象,这两个parse方法都会抛出一个继承自RuntimeException的DateTimeParseException异常。

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* LocalDate和LocalTime都可以通过解析代表它们的字符串创建。使用静态方法parse。
* 一旦传递的字符串参数无法被解析为合法的LocalDate或LocalTime对象,
* 这两个parse方法都会抛出一个继承自RuntimeException的DateTimeParseException异常。
*/
@Test
public void testParse() {
// 小于10的必须在前面补0,否则抛出异常
LocalDate localDate = LocalDate.parse("2019-08-26");
LocalTime localTime = LocalTime.parse("13:45:20");
System.out.println(localDate);
System.out.println(localTime);
}

年月日时分秒对象:LocalDateTime

可使用静态工厂方法now获取当前时间的年月日时分秒纳秒;

可使用静态工厂方法of创建一个LocalDateTime日期时间对象;

可由LocalDateLocalTime组合出LocalDateTime对象;

可从LocalDateTime对象中提取出LocalDateLocalTime

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* LocalDateTime:年月日时分秒
*/
@Test
public void testLocalDateTime() {
LocalDateTime localDateTime = LocalDateTime.now();
LocalDateTime localDateTime1 = LocalDateTime.of(2019, Month.AUGUST, 26, 10, 47, 20);
LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.now();
// 由LocalDate和LocalTime组合出LocalDateTime
LocalDateTime localDateTime2 = LocalDateTime.of(localDate,localTime);
LocalDateTime localDateTime3 = localDate.atTime(10,51,32);
LocalDateTime localDateTime4 = localDate.atTime(localTime);
LocalDateTime localDateTime5 = localTime.atDate(localDate);
// 由LocalDateTime提取出LocalDate和LocalTime
LocalDate localDateFromLocalDateTime = localDateTime2.toLocalDate();
LocalTime localTimeFromLocalDateTime = localDateTime2.toLocalTime();

System.out.println(localDateTime); // 2019-09-02T19:57:16.516
System.out.println(localDateTime1); // 2019-08-26T10:47:20
System.out.println(localDateTime2); // 2019-09-02T19:57:16.517
System.out.println(localDateTime3); // 2019-09-02T10:51:32
System.out.println(localDateTime4); // 2019-09-02T19:57:16.517
System.out.println(localDateTime5); // 2019-09-02T19:57:16.517
System.out.println(localDateFromLocalDateTime); // 2019-09-02
System.out.println(localTimeFromLocalDateTime); // 19:57:16.517
}

时间戳对象:Instant

机器的日期和时间格式:从UNIX元年时间开始到现在所经过的秒数对时间进行建模。包含的是由秒及纳秒组成的数字。

使用静态工厂方法now获取当前时刻的时间戳,默认获取的是UTC时区(世界协调时间)所在的时刻,可做时区偏移运算获取带偏移量的日期时间对象OffsetDateTime。可使用toEpochMilli方法获取表示的时间戳秒数。

可使用静态工厂方法ofEpochSecond等对时间戳进行运算。

Instant的设计初衷是为了便于机器使用。它包含的是由秒及纳秒所构成的数字。所以,它无法处理那些我们非常容易理解的时间单位。比如下面这行代码:

1
int day = Instant.now().get(ChronoField.DAY_OF_MONTH);

它会抛出如下异常:

1
java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: DayOfMonth

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* Instant:从UNIX元年时间开始到现在所经过的秒数对时间进行建模。包含的是由秒及纳秒组成的数字。
*
* 静态工厂方法:ofEpochSecond包含两个重载版本
* // 传入一个代表秒数的值创建一个Instant实例
* Instant ofEpochSecond(long epochSecond)
* // 第一个参数:代表秒数的值,第二个参数:纳秒数,对第一个参数传入的秒数进行调整,确保保存的纳秒分片在0到999 999 999之间。
* Instant ofEpochSecond(long epochSecond, long nanoAdjustment)
*
* 静态工厂方法:now
*/
@Test
public void testInstant() {
Instant instant1 = Instant.ofEpochSecond(3);
Instant instant2 = Instant.ofEpochSecond(3, 0);
Instant instant3 = Instant.ofEpochSecond(2, 1_000_000_000);
Instant instant4 = Instant.ofEpochSecond(4, -1_000_000_000);
System.out.println(instant1);
System.out.println(instant2);
System.out.println(instant3);
System.out.println(instant4);

Instant now = Instant.now(); // 默认获取UTC时区的时间
System.out.println(now);
// 时区偏移运算:获取偏移8小时的时区的时间。OffsetDateTime:带偏移量的日期时间对象
OffsetDateTime offsetDateTime = now.atOffset(ZoneOffset.ofHours(8));
System.out.println(offsetDateTime);
// 转化成时间戳
System.out.println(now.toEpochMilli());
// java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: DayOfMonth
int day = now.get(ChronoField.DAY_OF_MONTH);
System.out.println(day);
}

计算两个时间之间的间隔:Duration 和计算两个日期之间的间隔:Period

可以使用Duration类的静态方法between计算两个时间点之间的间隔,between方法接收的参数是两个Temporal对象,虽然LocalDateTimeInstant都是Temporal接口的实现类,但是它们是为不同的目的而设计的,一个是为了便于人阅读使用, 另一个是为了便于机器处理,所以我们不能将它们混用,即不能计算LocalDateTimeInstant对象之间的间隔。

可以使用Period类的静态方法between计算两个日期之间的间隔。

也可使用静态工厂方法直接创建DurationPeriod类的对象。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* 计算两个时间之间的间隔:Duration
* 计算两个日期之间的间隔:Period
*/
@Test
public void testDuration() throws Exception {
LocalTime localTime1 = LocalTime.parse("13:45:20");
LocalTime localTime2 = LocalTime.parse("13:45:30");
LocalDateTime localDateTime1 = LocalDateTime.now();
Thread.sleep(100);
LocalDateTime localDateTime2 = LocalDateTime.now();
Instant instant1 = Instant.ofEpochSecond(3);
Instant instant2 = Instant.ofEpochSecond(6);
System.out.println(Duration.between(localTime1,localTime2)); // PT10S
System.out.println(Duration.between(localDateTime1,localDateTime2)); // PT0.1S
System.out.println(Duration.between(instant1,instant2)); // PT3S

// 计算两个LocalDate之间的时长
Period periodBetween = Period.between(LocalDate.of(2019, 8, 26), LocalDate.of(2019, 8, 28));
System.out.println(periodBetween); // P1D
System.out.println(periodBetween.getDays()); // 2

// Duration和Period的静态工厂方法直接创建实例
Duration durationOfMinutes = Duration.ofMinutes(3);
Duration durationOf = Duration.of(3, ChronoUnit.MINUTES);
Period periodOfDays = Period.ofDays(10);
Period periodOfWeeks = Period.ofWeeks(3);
Period periodOf = Period.of(2, 6, 1);
System.out.println(durationOfMinutes); // PT3M
System.out.println(durationOf); // PT3M
System.out.println(periodOfDays); // P10D
System.out.println(periodOfWeeks); // P21D
System.out.println(periodOf); // P2Y6M1D
}

操作、解析和格式化日期

LocalDateLocalTimeLocalDateTime以及Instant这样表示时间点的日期-时间类提供了很多通用的方法用来操作日期-时间。所有的方法都返回一个修改了属性的对象。它们都不会修改原来的对象。

最直接也最简单的方法是使用”withAttribute“方法。”withAttribute“方法会创建对象的一个副本,并按照需要修改它的属性。

也可使用通用的with方法,它接受的第一个参数是一个TemporalField对象,第二个参数是需要修改的值。

with方法还有一个重载的方法,它接收一个日期调整器TemporalAdjuster对象,更加灵活地处理日期。

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 操纵、解析和格式化日期
*
* LocalDate、LocalTime和LocalDateTime类都为final类,不可变,每次操作后都返回一个新的对应对象
*/
@Test
public void testUpdateTime() {
LocalDate date1 = LocalDate.of(2019, 8, 26);
LocalDate date2 = date1.withYear(2020);
LocalDate date3 = date2.withDayOfMonth(25);
LocalDate date4 = date3.with(ChronoField.MONTH_OF_YEAR, 9);
System.out.println(date1); // 2019-08-26
System.out.println(date2); // 2020-08-26
System.out.println(date3); // 2020-08-25
System.out.println(date4); // 2020-09-25

LocalDate date5 = LocalDate.of(2019,8,26);
LocalDate date6 = date5.plusWeeks(1); // 加一周
LocalDate date7 = date6.minusYears(3); // 减三年
LocalDate date8 = date7.plus(6, ChronoUnit.MONTHS); // 加六月
System.out.println(date5); // 2019-08-26
System.out.println(date6); // 2019-09-02
System.out.println(date7); // 2016-09-02
System.out.println(date8); // 2017-03-02
}

日期调整器:TemporalAdjuster

TemporalAdjuster是一个函数式接口,接口方法签名如下:

1
Temporal adjustInto(Temporal temporal);

接收一个Temporal对象,返回一个Temporal对象。由于所有的日期时间API都实现了Temporal接口,故它可以用来自定义更加复杂的日期时间操作。

Java 8提供了TemporalAdjusters类,该类通过静态方法提供了大量的常用TemporalAdjuster的实现。

同时还支持定制TemporalAdjuster,定制的方式有两种:

一:实现TemporalAdjuster接口;

二:使用Lambda表达式定制TemporalAdjuster对象,推荐使用TemporalAdjusters类的静态工厂方法ofDateAdjuster,该方法签名如下:

1
public static TemporalAdjuster ofDateAdjuster(UnaryOperator<LocalDate> dateBasedAdjuster)

接收一个UnaryOperator函数式接口,返回一个TemporalAdjuster对象。

UnaryOperator函数式接口中方法签名如下,它总是返回它的输入。

1
2
3
static <T> UnaryOperator<T> identity() {
return t -> t;
}

相关代码示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
/**
* 日期调整器
*/
@Test
public void testTemporalAdjuster() {
LocalDate date1 = LocalDate.of(2019,8,26);
// TemporalAdjuster dayOfWeekInMonth(int ordinal, DayOfWeek dayOfWeek)
// dayOfWeek表示星期几
// 如果ordinal为0,则表示本日期所在的月的上一个月的最后一个星期几
// 如果ordinal为正数,则以本日期所在的月从前向后数,第ordinal个星期几
// 如果ordinal为负数,则以本日期所在的月从后往前数,第-ordinal个星期几
LocalDate date2 = date1.with(TemporalAdjusters.dayOfWeekInMonth(1, DayOfWeek.FRIDAY));
System.out.println("date2=" + date2);

// TemporalAdjuster firstDayOfMonth():创建一个新的日期,它的值为当月的第一天
LocalDate date3 = date1.with(TemporalAdjusters.firstDayOfMonth());
System.out.println("date3=" + date3);

// TemporalAdjuster firstDayOfNextMonth():创建一个新的日期,它的值为下月的第一天
LocalDate date4 = date1.with(TemporalAdjusters.firstDayOfNextMonth());
System.out.println("date4=" + date4);

// TemporalAdjuster firstDayOfNextYear():创建一个新的日期,它的值为明年的第一天
LocalDate date5 = date1.with(TemporalAdjusters.firstDayOfNextYear());
System.out.println("date5=" + date5);

// TemporalAdjuster firstDayOfYear():创建一个新的日期,它的值为今年的第一天
LocalDate date6 = date1.with(TemporalAdjusters.firstDayOfYear());
System.out.println("date6=" + date6);

// TemporalAdjuster firstInMonth(DayOfWeek dayOfWeek):创建一个新的日期,它的值为同一个月中,第一个符合星期几要求的日期(这个月的第一个星期几)
LocalDate date7 = date1.with(TemporalAdjusters.firstInMonth(DayOfWeek.FRIDAY));
System.out.println("date7=" + date7);

// TemporalAdjuster lastDayOfMonth():创建一个新的日期,它的值为这个月的最后一天
LocalDate date8 = date1.with(TemporalAdjusters.lastDayOfMonth());
System.out.println("date8=" + date8);

// TemporalAdjuster lastDayOfYear():创建一个新的日期,它的值为今年的最后一天
LocalDate date9 = date1.with(TemporalAdjusters.lastDayOfYear());
System.out.println("date9=" + date9);

// TemporalAdjuster lastInMonth(DayOfWeek dayOfWeek):创建一个新的日期,它的值为同一个月中,最后一个符合星期几要求的日期
LocalDate date10 = date1.with(TemporalAdjusters.lastInMonth(DayOfWeek.FRIDAY));
System.out.println("date10=" + date10);

// TemporalAdjuster next(DayOfWeek dayOfWeek):创建一个新的日期,并将其值设定为指定日期之后第一个符合指定星期几的日期
LocalDate date11 = date1.with(TemporalAdjusters.next(DayOfWeek.MONDAY));
System.out.println("date11=" + date11);

// TemporalAdjuster nextOrSame(DayOfWeek dayOfWeek):
// 创建一个新的日期,并将其值设定为指定日期之后第一个符合指定星期几的日期;
// 如果指定日期已符合要求,则直接返回该日期
LocalDate date12 = date1.with(TemporalAdjusters.nextOrSame(DayOfWeek.MONDAY));
System.out.println("date12=" + date12);

// TemporalAdjuster previous(DayOfWeek dayOfWeek):创建一个新的日期,并将其值设定为指定日期之前第一个符合指定星期几的日期
LocalDate date13= date1.with(TemporalAdjusters.previous(DayOfWeek.MONDAY));
System.out.println("date13=" + date13);

// TemporalAdjuster previousOrSame(DayOfWeek dayOfWeek):
// 创建一个新的日期,并将其值设定为指定日期之前第一个符合指定星期几的日期;
// 如果指定日期已符合要求,则直接返回该日期
LocalDate date14 = date1.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
System.out.println("date14=" + date14);

// 使用Lambda表达式定制TemporalAdjuster对象,推荐使用TemporalAdjusters类的静态工厂方法ofDateAdjuster
// TemporalAdjuster ofDateAdjuster(UnaryOperator<LocalDate> dateBasedAdjuster)
TemporalAdjuster nextWorkingDay = TemporalAdjusters.ofDateAdjuster(temporal -> {
DayOfWeek dayOfWeek = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK));
int dayToAdd = 1;
if (dayOfWeek == DayOfWeek.FRIDAY) dayToAdd = 3;
else if (dayOfWeek == DayOfWeek.SATURDAY) dayToAdd = 2;
return temporal.plus(dayToAdd,ChronoUnit.DAYS);
});
}

/**
* 定制TemporalAdjuster
*
* 计算下一个工作日
*/
class NextWorkingDay implements TemporalAdjuster {

/**
* 周一到周五为工作日
* 如果是周日到周四,则返回下一天
* 如果是周五、周六、返回下周周一
* @param temporal
* @return
*/
@Override
public Temporal adjustInto(Temporal temporal) {
// 得到今天星期几
DayOfWeek dayOfWeek = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK));
int dayToAdd = 1;
if (dayOfWeek == DayOfWeek.FRIDAY) dayToAdd = 3;
else if (dayOfWeek == DayOfWeek.SATURDAY) dayToAdd = 2;
return temporal.plus(dayToAdd,ChronoUnit.DAYS);
}
}

TemporalAdjusters类中包含的工厂方法列表:

方法签名描述
TemporalAdjuster dayOfWeekInMonth(int ordinal, DayOfWeek dayOfWeek)dayOfWeek表示星期几
如果ordinal为0,则表示本日期所在的月的上一个月的最后一个星期几
如果ordinal为正数,则以本日期所在的月从前向后数,第ordinal个星期几
如果ordinal为负数,则以本日期所在的月从后往前数,第-ordinal个星期几
TemporalAdjuster firstDayOfMonth()创建一个新的日期,它的值为当月的第一天
TemporalAdjuster firstDayOfNextMonth()创建一个新的日期,它的值为下月的第一天
TemporalAdjuster firstDayOfNextYear()创建一个新的日期,它的值为明年的第一天
TemporalAdjuster firstDayOfYear()创建一个新的日期,它的值为今年的第一天
TemporalAdjuster firstInMonth(DayOfWeek dayOfWeek)创建一个新的日期,它的值为同一个月中,第一个符合星期几要求的日期(这个月的第一个星期几)
TemporalAdjuster lastDayOfMonth()创建一个新的日期,它的值为这个月的最后一天
TemporalAdjuster lastDayOfYear()创建一个新的日期,它的值为今年的最后一天
TemporalAdjuster lastInMonth(DayOfWeek dayOfWeek)创建一个新的日期,它的值为同一个月中,最后一个符合星期几要求的日期
TemporalAdjuster next(DayOfWeek dayOfWeek)创建一个新的日期,并将其值设定为指定日期之后第一个符合指定星期几的日期
TemporalAdjuster nextOrSame(DayOfWeek dayOfWeek)创建一个新的日期,并将其值设定为指定日期之后第一个符合指定星期几的日期;如果指定日期已符合要求,则直接返回该日期
TemporalAdjuster previous(DayOfWeek dayOfWeek)创建一个新的日期,并将其值设定为指定日期之前第一个符合指定星期几的日期
TemporalAdjuster previousOrSame(DayOfWeek dayOfWeek)创建一个新的日期,并将其值设定为指定日期之前第一个符合指定星期几的日期;如果指定日期已符合要求,则直接返回该日期

时区

时区的处理是新版日期和时间API新增加的重要功能,使用新版日期和时间API时区的处理被极大地简化了。新的java.time.ZoneId类是老版java.util.TimeZone的替代品。它的设计目标就是要让你无需为时区处理的复杂和繁琐而操心,跟其他日期和时间类一 样,ZoneId类也是被final修饰而无法修改的。

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 带时区的日期时间API
*/
@Test
public void testZoneLocalDateTime() {
// 查看所有支持的时区
Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();
for (String s: availableZoneIds) {
System.out.println(s);
}
// 通过时区构建LocalDateTime对象
LocalDateTime localDateTimeNow = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
// 指定时区构建带时区的日期时间对象
ZonedDateTime zonedDateTime = localDateTimeNow.atZone(ZoneId.of("Asia/Shanghai"));
System.out.println(localDateTimeNow); // 2019-09-03T10:35:25.677
// 2019-09-03T10:35:25.677+08:00[Asia/Shanghai] 与UTC时间相差8小时
System.out.println(zonedDateTime);
}

日期时间API的部分UML图

以上介绍的新的日期时间API相关类的UML图如下:

12-newDateTimeUML.png

下面这张图能帮助我们更好的理解LocaleDate、 LocalTime、LocalDateTime以及ZoneId之间的差异。

12-ZonedDateTime组成.png