根据提供的开始日期以及工作日数量生成结束日期

文章介绍了如何使用moment-recur和moment-business-days这两个JavaScript插件来处理年度调休和工作日的计算。首先生成全年的周末日期序列,然后结合法定节假日,通过排除调休日来确定工作日。最后设置工作日和自定义假期,以便进行工作日相关的计算。代码示例展示了整个流程,包括生成双休日序列、设置工作日及自定义假期、合并假期和双休日,以及计算特定工作日数量的结束日期。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

叨叨:真的讨厌调休,呐呐呐

简介插件

一共使用了两款插件,moment-recurmoment-business-days

  1. moment-recur:是用来生成时间范围的,可以根据间隔天数,每周固定日或者每年固定月份指定生成规则,如果提供了开始和结束的日期可以获取时间范围内所有的指定生成规则的日期序列,本文利用该插件生成全年的周六和周日的日期序列。
  2. moment-business-days:工作日插件,默认提供了双休日作为工作日,并且支持自定工作日范围以及节假日。

大致思路

由于每年的法定节假日和调休的时间多且繁杂,利用双休日并设置法定节假日为假期,然后生成结束日期,再根据开始日期和结束日期的范围去判断是否含有调休日再对日期进行加减的思路较为不便。

首先应当明确:

  1. 一年中要么为假期、要么为工作日。
  2. 调休日都是周六和周日,则一定会在生成的双休日序列a中。

实际使用的思路是:

  1. 生成每一年的周六和周日的双休日序列:a
  2. 和已知的假期序列:b进行合并得到 c
  3. 将c中的调休日剔除,得到d
  4. 将一周7天都设置为工作日
  5. 自定义假期

则d即为一年中所需要休息的日期序列。如果将d设置为假期,则其他日期都是工作日了,就可以利用工作日插件对相应内容进行计算了。

代码实现

需要安装相应插件

npm i moment moment-business-days moment-recur

主要有两部分:生成双休日序列,设置工作日以及自定义假期,合并假期以及双休日。

生成双休日序列

const formatStr = "YYYY-MM-DD";
const saturday = 6
const sunday = 7

/**
 * 根据给定的年份,生成该年所有的周六和周日的日期
 * @param startYear 开始年份,默认值为当年
 * @param endYear 结束年份,默认值为当年
 * @return {Array} 年份范围内的所有周六周日的时间,格式:YYYY-MM-DD
 */
const getAllYearWeekend = (startYear = moment().year(), endYear = moment().year()) => {
  // 指定时间生成插件规则时间范围为开始年份的第一天到结束年份的最后一天
  const recurInstance = moment(startYear, "YYYY").startOf("year").recur(moment(endYear, "YYYY").endOf("year"));
  return recurInstance.every([saturday, sunday]).daysOfWeek().days().all().map(item => item.format(formatStr));
};

可能有坑的点有两个地方,一个是周一对应的是1不是0,一个是recurInstance如果没有设置结束日期的话,不能使用all方法。

设置工作日及自定义假期

这块实际上是moment的自定义语言区的功能,如果说有坑的地方就是需要搞清语言区的代码,不然可能用不了,中国是zh-cn

const language = "zh-cn";
const localSpec = {
  workingWeekdays: [monday, tuesday, wednesday, thursday, friday, saturday, sunday],
  holidays: yearRangeHolidayList,
  holidayFormat: formatStr
}
moment.updateLocale(language, localSpec);
moment.locale(language);

合并假期以及双休日

const yearStart = 0, yearEnd = 4, toNumber = 1;
// 取假期的前四位作为年份,并将其乘1转为数字,之后利用set去重,再利用解构重新转为数字数组作为节假日中存在的年份序列
const holidayListYearRange = [...new Set(holidays.map(dateStr => dateStr.substring(yearStart, yearEnd) * toNumber))];
// 从年份序列中找到最大和最小的作为生成周六周日的开始和结束年份
const yearRangeWeekendDateList = getAllYearWeekend(Math.min(...holidayListYearRange), Math.max(...holidayListYearRange));
// 合并双休日序列和法定节假日序列
const yearRangeHolidaySet = new Set([...holidays, ...yearRangeWeekendDateList]);
// 删除节日中调休的日期
businessDays.forEach(item => yearRangeHolidaySet.delete(item));
const yearRangeHolidayList = [...yearRangeHolidaySet];

这块是用了[...new Set(Array)]做了一个去重,还有一个是Set和数组可以利用展开运算符...相互转换。

整体代码

import moment from "moment";
import "moment-recur";
import "moment-business-days";
import holidayDetail from "./holiday.json";

const { holidays, businessDays } = holidayDetail;

const formatStr = "YYYY-MM-DD";
/**
 * 根据给定的年份,生成该年所有的周六和周日的日期,参数应当格式化为YYYY
 * @param startYear {Number} 开始年份,默认值为当年
 * @param endYear {Number} 结束年份,默认值为当年
 * @return {Array} 年份范围内的所有周六周日的时间,格式:YYYY-MM-DD
 */
const getAllYearWeekend = (startYear = moment().year(), endYear = moment().year()) => {
  // 指定时间生成插件规则时间范围为开始年份的第一天到结束年份的最后一天
  const recurInstance = moment(startYear, yearFormat).startOf("year").recur(moment(endYear, yearFormat).endOf("year"));
  return recurInstance.every([saturday, sunday]).daysOfWeek().days().all().map(item => item.format(formatStr));
};

/**
 * 初始化,设置工作日及自定义假期
 */
const initHolidays = () => {
  const yearStart = 0, yearEnd = 4, toNumber = 1;
  const monday = 1;
  const tuesday = 2;
  const wednesday = 3;
  const thursday = 4;
  const friday = 5;
  const saturday = 6;
  const sunday = 7;
  const yearFormat = "YYYY";
  const language = "zh-cn";
  // 取假期的前四位作为年份,并将其乘1转为数字,之后利用set去重,再利用解构重新转为数字数组
  const holidayListYearRange = [...new Set(holidays.map(dateStr => dateStr.substring(yearStart, yearEnd) * toNumber))];
  const yearRangeWeekendDateList = getAllYearWeekend(Math.min(...holidayListYearRange), Math.max(...holidayListYearRange));
  const yearRangeHolidaySet = new Set([...holidays, ...yearRangeWeekendDateList]);
  businessDays.forEach(item => yearRangeHolidaySet.delete(item));
  const yearRangeHolidayList = [...yearRangeHolidaySet];
  const localSpec = {
    workingWeekdays: [monday, tuesday, wednesday, thursday, friday, saturday, sunday],
    holidays: yearRangeHolidayList,
    holidayFormat: formatStr
  }
  moment.updateLocale(language, localSpec);
  moment.locale(language);
};

/**
 *  根据给定的起始日期和工作日数量,返回对应的结束日期
 * @param startDate {String} YYYY-MM-DD
 * @param businessDayCount {Number} 工作日数量
 * @return {String} 结束日期,YYYY-MM-DD
 */
const getSpecificBusinessDayCountEndDate = (startDate, businessDayCount) => {
  console.log(moment(startDate, formatStr).businessAdd(2));
  return moment(startDate, formatStr).businessAdd(businessDayCount).format(formatStr);
};

initHolidays();
// 调用,传入测试日期,应当返回 2023-10-07,因为中间跨了2023-09-29~2023-10-06一个中秋一个国庆
getSpecificBusinessDayCountEndDate("2023-09-28", 2);

计算工作日(2023-01-30 update)

传入一个开始日期一个结束日期的话,可以计算出中间究竟用了多少工作日。
实际上如果现在有了工作日序列了,如果传入一个开始日期一个结束日期的话,也就可以计算出中间究竟用了多少工作日了,可以使用business-days
businessDiff api了。或者说有多少休息日了。但是该api有三个问题:

  1. 如果计算工作日同一天(2023-01-30,2023-01-30会返回0)
  2. 如果开始时间选择周五,结束时间为周六,则工作日会返回1
  3. 如果开始时间大于结束时间,只会继续返回整数差值,不会返回负数差值或者报错。
    所以需要被改造。
    getBusinessDayCountByDateRange: () => (startDate, endDate) => {
      if (!endDate) {
        return 0
      }
      // 对开始和结束时间做校验,使开始时间大于结束时间
      if (moment(endDate, formatStr).diff(moment(startDate, formatStr), 'seconds') < 0) {
        throw new Error('开始时间必须小于结束时间')
      }

      const businessDayCount = moment(startDate, formatStr).businessDiff(moment(endDate, formatStr))
      // 如果工作日天数为0有两种情况,一是开始和结束时间都是工作日,但是是同一天(加1)。二是开始和结束时间都是休息日,则返回0
      if (businessDayCount === 0) {
        if (moment(startDate, formatStr).isBusinessDay() && moment(startDate, formatStr).diff(moment(endDate, formatStr), 'day')=== 0) {
          return 1
        }
        if (moment(startDate, formatStr).isHoliday() && moment(endDate, formatStr).isHoliday()) {
          return 0
        }
      }
      // 边界处理:如果结束时间为休息日,则无需加一
      if (moment(endDate).isHoliday()) {
        return businessDayCount
      }
      return businessDayCount + 1
    }

如果使用可能存在的坑

这里没有对在已知假期之外的进行处理,所以如果说假期序列中存在2022,2023的假日,但是最后计算出来的时间到2024了,那肯定会出问题的,如果使用的话请注意下这点。

另外没有对开始日期进行判断,如果开始日期落在了假日内,不知道是否存在坑。

代码仓库

暂无

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值