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

在Java 8之前,我们常用的日期时间API是java.util.Date和java.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都是可变的,是线程不安全的。

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

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

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

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

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

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

演示代码:

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();
    } 复制代码

异常信息如下:

Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.NumberFormatException: multiple points 复制代码

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

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

线程封闭类代码:

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);
    }
} 复制代码

演示代码:

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类,不可变,线程安全。

代码演示:

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日期对象中获取该日期的年份、月份、这个月的第几天、这周的星期几、今年的第几天、这个月的长度(有几天)和是否为闰年等信息。

代码演示:

/**
     * 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方法包含三个重载,方法签名如下:

/**
 * @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); 复制代码

使用代码示例:

/**
     * 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异常。

代码示例:

/**
     * 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日期时间对象;

可由LocalDate和LocalTime组合出LocalDateTime对象;

可从LocalDateTime对象中提取出LocalDate和LocalTime。

代码示例:

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

int day = Instant.now().get(ChronoField.DAY_OF_MONTH); 复制代码

它会抛出如下异常:

java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: DayOfMonth 复制代码

示例代码:

/**
     * 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对象,虽然LocalDateTime和Instant都是Temporal接口的实现类,但是它们是为不同的目的而设计的,一个是为了便于人阅读使用, 另一个是为了便于机器处理,所以我们不能将它们混用,即不能计算LocalDateTime和Instant对象之间的间隔。

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

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

示例代码:

/**
     * 计算两个时间之间的间隔: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
    } 复制代码

操作、解析和格式化日期

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

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

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

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

代码示例:

/**
     * 操纵、解析和格式化日期
     *
     * 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是一个函数式接口,接口方法签名如下:

Temporal adjustInto(Temporal temporal); 复制代码

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

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

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

一:实现TemporalAdjuster接口;

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

public static TemporalAdjuster ofDateAdjuster(UnaryOperator<LocalDate> dateBasedAdjuster) 复制代码

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

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

static <T> UnaryOperator<T> identity() { return t -> t;
    } 复制代码

相关代码示例如下:

/**
     * 日期调整器
     */
    @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修饰而无法修改的。

代码示例:

/**
     * 带时区的日期时间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图如下:

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