You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java如何获取含季度在内的任意ChronoUnit对应周期首日

实现方案

核心思路是提前把不同时间单位对应的首日计算规则做映射存储,运行时直接匹配规则,完全避免多层if-elif分支判断,纯JDK实现就可以覆盖所有常用周期,不需要强制引入第三方依赖。

纯JDK无依赖实现(覆盖日/周/月/季/年全常用周期)

标准ChronoUnit枚举本身没有定义季度单位,季度逻辑只需要做一处轻量判断即可,其余单位直接用JDK自带的TemporalAdjusters实现,代码可直接复用:

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjuster;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;

public class CycleDateUtil {
    // 用EnumMap存储各单位的首日调整规则,初始化一次全局复用,性能优于普通HashMap
    private static final Map<ChronoUnit, TemporalAdjuster> FIRST_DAY_ADJUSTERS = new EnumMap<>(ChronoUnit.class);

    static {
        // 日维度:当日就是周期首日
        FIRST_DAY_ADJUSTERS.put(ChronoUnit.DAYS, date -> date);
        // 周维度:默认周一为周首日,需要周日起始的话替换为DayOfWeek.SUNDAY即可
        FIRST_DAY_ADJUSTERS.put(ChronoUnit.WEEKS, TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
        // 月维度:当月1号
        FIRST_DAY_ADJUSTERS.put(ChronoUnit.MONTHS, TemporalAdjusters.firstDayOfMonth());
        // 年维度:当年1月1号
        FIRST_DAY_ADJUSTERS.put(ChronoUnit.YEARS, TemporalAdjusters.firstDayOfYear());
    }

    /**
     * 获取指定日期所属周期的首日
     * @param date 待计算日期
     * @param unit 时间单位,支持DAYS/WEEKS/MONTHS/YEARS
     * @param isQuarter 是否为季度周期(标准ChronoUnit无季度枚举,用该标记区分)
     * @return 对应周期的首日
     */
    private static LocalDate findFirstDateOf(LocalDate date, ChronoUnit unit, boolean isQuarter) {
        // 季度仅做单处判断,无多层分支
        if (isQuarter) {
            int startMonth = ((date.getMonthValue() - 1) / 3) * 3 + 1;
            return LocalDate.of(date.getYear(), startMonth, 1);
        }
        TemporalAdjuster adjuster = FIRST_DAY_ADJUSTERS.get(unit);
        if (adjuster == null) {
            throw new IllegalArgumentException("暂不支持的时间单位: " + unit);
        }
        return date.with(adjuster);
    }

    // 对应业务里的周期日期计算逻辑示例
    public static List<LocalDate> calculateCompoundingDates(LocalDate start, LocalDate end, long duration, ChronoUnit unit, boolean isQuarter) {
        List<LocalDate> result = new ArrayList<>();
        LocalDate current = start;
        while (current.isBefore(end)) {
            result.add(current);
            LocalDate nextCycleDate = current.plus(duration, unit);
            current = findFirstDateOf(nextCycleDate, unit, isQuarter);
        }
        result.add(end);
        return result;
    }
}

逻辑说明

  • 所有常规单位的计算规则都存在Map中,后续如果要加半年、十年等其他周期,只需要往Map里新增一条规则即可,不需要修改核心判断逻辑
  • 季度默认按自然季度计算(1-3月为Q1,以此类推),如果是自定义财年季度(比如财年从4月起始),只需要修改季度首月的计算逻辑即可
  • 周首日可根据业务需求灵活调整,不需要改动整体结构

ThreeTen-Extra库适配方案

如果项目已经引入ThreeTen-Extra,不需要自己写季度计算逻辑,库中自带的YearQuarter类可以直接拿到季度首日,只需要在规则映射里新增一条对应季度单位的调整规则即可,整体结构和上面的纯JDK实现完全一致,不需要写分支判断。

内容的提问来源于stack exchange,提问作者user3081519

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 12:01:06