1. 概述

本文将快速介绍Java中的TemporalAdjuster,并通过几个实用场景演示其用法。

Java 8引入了全新的日期时间库java.timeTemporalAdjuster是其中重要组成部分。如果想了解java.time的更多细节,可参考这篇入门文章

简单来说,TemporalAdjuster是一种用于调整Temporal对象的策略模式实现。在深入用法前,我们先了解Temporal接口本身。

2. Temporal接口

Temporal表示日期、时间或二者组合的抽象表示,具体取决于实现类。常见实现包括:

LocalDate – 不带时区的日期
LocalDateTime – 不带时区的日期时间
HijrahDate – 伊斯兰历日期
MinguoDate – 民国历日期
ThaiBuddhistDate – 泰国佛历日期

3. TemporalAdjuster接口

TemporalAdjusterjava.time库中的函数式接口,在TemporalAdjusters工具类中提供了大量预定义实现。其核心方法是adjustInto(),通过传入Temporal对象执行调整操作。

TemporalAdjuster特别适合处理复杂日期计算,比如:

  • 下个星期日的日期
  • 当前月份最后一天
  • 下一年第一天

虽然用老版java.util.Calendar也能实现,但新API通过预定义实现封装了底层逻辑。详情可查阅Javadoc

4. 预定义的TemporalAdjusters

TemporalAdjusters类提供大量静态方法,返回能以不同方式调整Temporal对象的TemporalAdjuster实例。核心方法包括:

⚠️ 常用调整器列表

  • dayOfWeekInMonth() – 月内第几个星期几(如三月第二个星期二)
  • firstDayOfMonth() – 当月第一天
  • firstDayOfNextMonth() – 下个月第一天
  • firstDayOfNextYear() – 下一年第一天
  • firstDayOfYear() – 当年第一天
  • lastDayOfMonth() – 当月最后一天
  • nextOrSame() – 下一个指定星期几(如果当天匹配则返回当天)

方法名基本自解释,完整列表见Javadoc

举个栗子:获取2017-07-08之后的下一个星期日(测试用固定日期避免时间变化):

@Test
public void whenAdjust_thenNextSunday() {
    LocalDate localDate = LocalDate.of(2017, 07, 8);
    LocalDate nextSunday = localDate.with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
    
    String expected = "2017-07-09";
    
    assertEquals(expected, nextSunday.toString());
}

获取当月最后一天:

LocalDate lastDayOfMonth = localDate.with(TemporalAdjusters.lastDayOfMonth());

5. 自定义TemporalAdjuster实现

可通过两种方式创建自定义调整器:

5.1. 使用Lambda表达式

计算14天后的日期:

@Test
public void whenAdjust_thenFourteenDaysAfterDate() {
    LocalDate localDate = LocalDate.of(2017, 07, 8);
    TemporalAdjuster temporalAdjuster = t -> t.plus(Period.ofDays(14));
    LocalDate result = localDate.with(temporalAdjuster);
    
    String fourteenDaysAfterDate = "2017-07-22";
    
    assertEquals(fourteenDaysAfterDate, result.toString());
}

⚠️ 工作日计算器:获取下个工作日(跳过周末):

static TemporalAdjuster NEXT_WORKING_DAY = TemporalAdjusters.ofDateAdjuster(date -> {
    DayOfWeek dayOfWeek = date.getDayOfWeek();
    int daysToAdd;
    if (dayOfWeek == DayOfWeek.FRIDAY)
        daysToAdd = 3;  // 周五+3天=周一
    else if (dayOfWeek == DayOfWeek.SATURDAY)
        daysToAdd = 2;  // 周六+2天=周一
    else
        daysToAdd = 1;  // 其他+1天
    return date.plusDays(daysToAdd);
});

测试代码:

@Test
public void whenAdjust_thenNextWorkingDay() {
    LocalDate localDate = LocalDate.of(2017, 07, 8); // 周六
    TemporalAdjuster temporalAdjuster = NEXT_WORKING_DAY;
    LocalDate result = localDate.with(temporalAdjuster);

    assertEquals("2017-07-10", result.toString()); // 应为周一
}

5.2. 实现TemporalAdjuster接口

创建自定义类实现TemporalAdjuster接口:

public class CustomTemporalAdjuster implements TemporalAdjuster {

    @Override
    public Temporal adjustInto(Temporal temporal) {
        DayOfWeek dayOfWeek 
          = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK));
        
        int daysToAdd;
        if (dayOfWeek == DayOfWeek.FRIDAY)
            daysToAdd = 3;
        else if (dayOfWeek == DayOfWeek.SATURDAY)
            daysToAdd = 2;
        else
            daysToAdd = 1;
        return temporal.plus(daysToAdd, ChronoUnit.DAYS);
    }
}

测试实现:

@Test
public void whenAdjustAndImplementInterface_thenNextWorkingDay() {
    LocalDate localDate = LocalDate.of(2017, 07, 8);
    CustomTemporalAdjuster temporalAdjuster = new CustomTemporalAdjuster();
    LocalDate nextWorkingDay = localDate.with(temporalAdjuster);
    
    assertEquals("2017-07-10", nextWorkingDay.toString());
}

6. 总结

本文介绍了TemporalAdjuster的核心概念、预定义调整器的使用,以及两种自定义实现方式(Lambda表达式和接口实现)。在实际开发中,合理利用这些工具可以简单粗暴地解决各种日期计算需求,避免重复造轮子。

完整代码示例可在GitHub仓库查看。


原始标题:TemporalAdjuster in Java