如何计算指定工作日的未来 N 个日期

本文提供一种简洁可靠的 php 方法,用于从起始日期开始,按顺序获取指定星期几(如周二、周四)的前 n 个匹配日期,支持任意组合与数量,逻辑清晰、易于扩展。

在日常开发中,我们常需生成周期*件(如课程表、会议安排、订阅提醒)对应的未来日期列表。例如:从 02/08/2025(2025年2月8日)起,依次获取接下来 5 个 属于 周二(Tue)或周四(Thu) 的日期。关键在于:按自然时间顺序遍历每一天,逐个判断是否匹配目标星期几,而非按周跳跃式计算——这能确保结果严格保序且无遗漏。

以下是一个健壮、可读性强的实现方案:

function getNextWeekdayDates(string $startDate, int $count, array $targetDays): array
{
    // 支持多种输入格式(如 '02/08/2025'、'2025-02-08'),自动解析
    $date = DateTime::createFromFormat('d/m/Y', $startDate) 
        ?: DateTime::createFromFormat('Y-m-d', $startDate)
        ?: new DateTime($startDate);

    if (!$date) {
        throw new InvalidArgumentException("Invalid start date: {$startDate}");
    }

    // 标准化目标星期缩写为大写首字母三字符('Mon', 'Tue', ... 'Sun')
    $normalizedDays = array_map(function($day) {
        $day = strtoupper(trim($day));
        $map = [
            'MON' => 'Mon', 'MONDAY' => 'Mon',
            'TUE' => 'Tue', 'TUESDAY' => 'Tue',
            'WED' => 'Wed', 'WEDNESDAY' => 'Wed',
            'THU' => 'Thu', 'THURSDAY' => 'Thu',
            'FRI' => 'Fri', 'FRIDAY' => 'Fri',
            'SAT' => 'Sat', 'SATURDAY' => 'Sat',
            'SUN' => 'Sun', 'SUNDAY' => 'Sun'
        ];
        return $map[$day] ?? $day;
    }, $targetDays);

    $result = [];
    while ($count > 0) {
        $dayName = $date->format('D'); // 返回 'Mon', 'Tue', ...
        if (in_array($dayName, $normalizedDays, true)) {
            $result[] = [
                'date' => $date->format('d/m/Y'),
                'day'  => $dayName
            ];
            $count--;
        }
        $date->modify('+1 day');
    }

    return $result;
}

// 使用示例
$dates = getNextWeekdayDates('02/08/2025', 5, ['Tue', 'Thu']);
foreach ($dates as $item) {
    echo "{$item['date']} - {$item['day']}\n";
}

输出结果:

08/02/2025 - Tue
10/02/2025 - Thu
15/02/2025 - Tue
17/02/2025 - Thu
22/02/2025 - Tue

优势说明:

  • 逻辑直观:每日递增 + 条件过滤,避免复杂偏移计算和边界错误;
  • 灵活兼容:支持 ['Tuesday','Thursday']、['Tue','Thu']、甚至 ['tue','THU'] 等多种输入形式;
  • 健壮容错:内置日期格式自动识别与异常提示;
  • 结构清晰:返回含日期与星期名的关联数组,便于后续渲染或处理。

⚠️ 注意事项:

  • 输入日期格式建议统一使用 d/m/Y 或 Y-m-d,避免因地区设置导致解析歧义;
  • 若需更高性能(如生成数百个日期),可改用“跳转到下一个目标日”的算法(基于 date('w') 计算差值),但对常规场景(≤100 项),本方案已足够高效且更易维护;
  • 如需包含起始日当天(即使非目标星期),当前逻辑已默认包含——若需排除当天,请将循环起始点设为 $date->modify('+1 day') 后再进入循环。

此方法摒弃了原代码中基于周粒度跳跃、嵌套修改等易错逻辑,回归“线性扫描+条件收集”的本质思路,是解决此类问题最直接、可靠且可扩展的实践方案。