bigywater.tech
Java · 349

TemporalAdjuster:把日期规则变成可复用调整器

「下一个工作日」「每月最后一个星期五」这类规则,TemporalAdjuster 可以把它们变成一个可复用、可测试的值。这篇逐个验证内置调整器的边界,写一个自己的调整器并逐天比对,并看它能用在哪些时间类型上。

4018 / 0
自写 NEXT_WORKING_DAY 与手写循环比对的天数 / 不一致天数
03-06
2026-02 只有 4 个星期五,dayOfWeekInMonth(5, FRIDAY) 溢出到的日期
3 种失败
YearMonth、LocalTime、Instant 上用 NEXT_WORKING_DAY 抛 DateTimeException

内置调整器

实测 · 本机执行输出
  [builtin]
    基准日期 2026-10-04 SUNDAY
      firstDayOfMonth()          → 2026-10-01 THURSDAY
      lastDayOfMonth()           → 2026-10-31 SATURDAY
      firstDayOfNextMonth()      → 2026-11-01 SUNDAY
      firstDayOfYear()           → 2026-01-01 THURSDAY
      lastDayOfYear()            → 2026-12-31 THURSDAY
      firstDayOfNextYear()       → 2027-01-01 FRIDAY
      next(SUNDAY)               → 2026-10-11 SUNDAY
      nextOrSame(SUNDAY)         → 2026-10-04 SUNDAY
      previous(SUNDAY)           → 2026-09-27 SUNDAY
      previousOrSame(SUNDAY)     → 2026-10-04 SUNDAY
      firstInMonth(MONDAY)       → 2026-10-05 MONDAY
      lastInMonth(FRIDAY)        → 2026-10-30 FRIDAY
    原对象不变(不可变):d = 2026-10-04

dayOfWeekInMonth 的边界

实测 · 本机执行输出
  [dowim]
    2026-10 有 5 个星期五;2026-02 有 4 个星期五
      dayOfWeekInMonth( 1, FRIDAY):2026-10 → 2026-10-02;2026-02 → 2026-02-06
      dayOfWeekInMonth( 2, FRIDAY):2026-10 → 2026-10-09;2026-02 → 2026-02-13
      dayOfWeekInMonth( 4, FRIDAY):2026-10 → 2026-10-23;2026-02 → 2026-02-27
      dayOfWeekInMonth( 5, FRIDAY):2026-10 → 2026-10-30;2026-02 → 2026-03-06
      dayOfWeekInMonth( 0, FRIDAY):2026-10 → 2026-09-25;2026-02 → 2026-01-30
      dayOfWeekInMonth(-1, FRIDAY):2026-10 → 2026-10-30;2026-02 → 2026-02-27
      dayOfWeekInMonth(-2, FRIDAY):2026-10 → 2026-10-23;2026-02 → 2026-02-20
      dayOfWeekInMonth(-5, FRIDAY):2026-10 → 2026-10-02;2026-02 → 2026-01-30
      dayOfWeekInMonth(-6, FRIDAY):2026-10 → 2026-09-25;2026-02 → 2026-01-23
    lastInMonth(FRIDAY) 与 dayOfWeekInMonth(-1, FRIDAY) 相同:true

自写调整器

实测 · 本机执行输出
  [custom]
    假期:国庆 2026-10-01、10-02、10-05、10-06、10-07(周末 10-03、10-04 照常休息)
      2026-09-30 WEDNESDAY → NEXT_WORKING_DAY = 2026-10-08 THURSDAY
      2026-10-01 THURSDAY → NEXT_WORKING_DAY = 2026-10-08 THURSDAY
      2026-10-04 SUNDAY → NEXT_WORKING_DAY = 2026-10-08 THURSDAY
      2026-10-07 WEDNESDAY → NEXT_WORKING_DAY = 2026-10-08 THURSDAY
      2026-10-08 THURSDAY → NEXT_WORKING_DAY = 2026-10-09 FRIDAY
      2026-10-09 FRIDAY → NEXT_WORKING_DAY = 2026-10-12 MONDAY
    与手写 while 循环对比:2020-01-01 ~ 2030-12-31 共 4018 天,不一致 0 天
    d.with(adj) 与 adj.adjustInto(d) 相同:true

适用的时间类型

实测 · 本机执行输出
  [types]
      LocalDate          2026-10-04                         with(NEXT_WORKING_DAY) → 2026-10-08
      LocalDateTime      2026-10-04T15:30                   with(NEXT_WORKING_DAY) → 2026-10-08T15:30
      ZonedDateTime      2026-10-04T15:30+08:00[Asia/Shanghai] with(NEXT_WORKING_DAY) → 2026-10-08T15:30+08:00[Asia/Shanghai]
      OffsetDateTime     2026-10-04T15:30+08:00             with(NEXT_WORKING_DAY) → 2026-10-08T15:30+08:00
      YearMonth          2026-10                            → DateTimeException: Unable to obtain LocalDate from TemporalAccessor: 2026-10 of type java.time.YearMonth
      LocalTime          15:30                              → DateTimeException: Unable to obtain LocalDate from TemporalAccessor: 15:30 of type java.time.LocalTime
      Instant            2026-10-04T07:30:00Z               → DateTimeException: Unable to obtain LocalDate from TemporalAccessor: 2026-10-04T07:30:00Z of type java.time.Instant
      ThaiBuddhistDate   ThaiBuddhist BE 2569-10-04         with(NEXT_WORKING_DAY) → ThaiBuddhist BE 2569-10-08
      JapaneseDate       Japanese Reiwa 8-10-04             with(NEXT_WORKING_DAY) → Japanese Reiwa 8-10-08
      MinguoDate         Minguo ROC 115-10-04               with(NEXT_WORKING_DAY) → Minguo ROC 115-10-08
    同样的 lastDayOfMonth():
      LocalDate          → 2026-10-31
      LocalDateTime      → 2026-10-31T15:30
      ZonedDateTime      → 2026-10-31T15:30+08:00[Asia/Shanghai]
      OffsetDateTime     → 2026-10-31T15:30+08:00
      YearMonth          → UnsupportedTemporalTypeException: Unsupported field: DayOfMonth
      LocalTime          → UnsupportedTemporalTypeException: Unsupported field: DayOfMonth
      Instant            → UnsupportedTemporalTypeException: Unsupported field: DayOfMonth
      ThaiBuddhistDate   → ThaiBuddhist BE 2569-10-31
      JapaneseDate       → Japanese Reiwa 8-10-31
      MinguoDate         → Minguo ROC 115-10-31

与夏令时

实测 · 本机执行输出
  [dst]
    起点 2026-03-06T02:30-05:00[America/New_York](周FRIDAY)
      with(nextOrSame(SUNDAY)) → 2026-03-08T03:30-04:00[America/New_York](03-08 的 02:30 在纽约不存在,被顺延)
      with(firstDayOfNextMonth()) → 2026-04-01T02:30-04:00[America/New_York]
    起点 2026-10-30T01:30-04:00[America/New_York] → with(nextOrSame(SUNDAY)) = 2026-11-01T01:30-04:00[America/New_York](重叠的 11-01 01:30 取较早偏移)
      同一个起点 plusDays(2) = 2026-03-08T03:30-04:00[America/New_York](与 with(nextOrSame(SUNDAY)) 结果相同)

组合调整器

实测 · 本机执行输出
  [chain]
      8 月最后一个工作日:2026-08-31 MONDAY(月末 2026-08-31 MONDAY)
      9 月最后一个工作日:2026-09-30 WEDNESDAY(月末 2026-09-30 WEDNESDAY)
      10 月最后一个工作日:2026-10-30 FRIDAY(月末 2026-10-31 SATURDAY)
      11 月最后一个工作日:2026-11-30 MONDAY(月末 2026-11-30 MONDAY)
    每月 25 号发薪,遇非工作日提前:
      2026-09-25 FRIDAY → 2026-09-25 FRIDAY
      2026-10-25 SUNDAY → 2026-10-23 FRIDAY
      2026-11-25 WEDNESDAY → 2026-11-25 WEDNESDAY
      2026-12-25 FRIDAY → 2026-12-25 FRIDAY

分步演示

TemporalAdjuster
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12;假期表是本文为演示编造的(2026 年国庆放假日期并非官方安排)
    • 没有验证:真实的法定节假日与调休(调休上班日不在这里的假期表模型里)、调整器在并发使用时的行为(调整器是无状态 lambda)、用 TemporalAdjuster 与 TemporalQuery 搭配、其它历法(Hijrah)与调整器的组合、性能对比
    京ICP备2026067502号-1京公网安备11010502063487号