← 中文资源

简体中文版本

跳过周末的工作日加减

按工作日前后移动 UTC 日期,跳过周末与可选节假日集合,并保留原时间。

阅读英文原文

功能说明

该示例在日期上前后移动指定数量的工作日,自动跳过周六与周日,并可通过节假日集合排除额外日期。它适合计算 SLA 到期日、账单日、审批截止时间与项目排期。传入零个工作日会返回同一时刻的副本,负数量表示向前回溯。

关键代码

addBusinessDays 校验日期有效且天数为整数,复制起始时间后按正负决定每次移动一天,只有落入工作日才递减剩余数量。isBusinessDay 用 UTC 的 getUTCDay 判断周末,再把日期转成 YYYY-MM-DD 键查询节假日集合。toDateKey 使用 toISOString 截取前十位,保证键格式稳定。所有计算基于 UTC,避免夏令时让某天变成二十三或二十五小时。

使用注意

节假日集合必须使用与 toDateKey 相同的日期键格式,并注意业务所在地时区与 UTC 的日期差异。该函数只跳周末与显式节假日,不处理调休、半工作日或宗教节日。起始时间若在周末,向前或向后计数时不会先调整到最近工作日,而是逐日移动。数量很大时逐日循环会变慢,可改用按周跳转再修正。

验证方式

从 2026 年 12 月 24 日加一个工作日,跳过圣诞节与周末后断言得到 12 月 28 日。从 12 月 28 日减一个工作日,断言回到 12 月 24 日。测试周六返回 false、零工作日返回相同时间、空节假日集合与非法日期分别符合预期。

代码示例

export function addBusinessDays(
  start: Date,
  days: number,
  holidays: ReadonlySet<string> = new Set(),
): Date {
  if (!(start instanceof Date) || Number.isNaN(start.getTime())) {
    throw new TypeError("start must be a valid Date");
  }
  if (!Number.isInteger(days)) throw new TypeError("days must be an integer");
  const result = new Date(start.getTime());
  let remaining = Math.abs(days);
  const step = days < 0 ? -1 : 1;
  while (remaining > 0) {
    result.setUTCDate(result.getUTCDate() + step);
    if (isBusinessDay(result, holidays)) remaining -= 1;
  }
  return result;
}

export function isBusinessDay(date: Date, holidays: ReadonlySet<string> = new Set()): boolean {
  if (!(date instanceof Date) || Number.isNaN(date.getTime())) {
    throw new TypeError("date must be a valid Date");
  }
  const weekday = date.getUTCDay();
  if (weekday === 0 || weekday === 6) return false;
  return !holidays.has(toDateKey(date));
}

export function toDateKey(date: Date): string {
  return date.toISOString().slice(0, 10);
}

const holidays = new Set(["2026-12-25"]);
console.log(addBusinessDays(new Date("2026-12-24T12:00:00Z"), 1, holidays).toISOString());
console.log(addBusinessDays(new Date("2026-12-28T00:00:00Z"), -1, holidays).toISOString());
console.log(isBusinessDay(new Date("2026-10-24T00:00:00Z")));
console.log(addBusinessDays(new Date("2026-10-21T00:00:00Z"), 0).toISOString());