freeCodeCamp 每日编程挑战解析:Business Day Count 工作日统计算法的 JavaScript 实现 📅 发布时间:2026/9/11 7:42:53 👁 浏览次数: freeCodeCamp 每日编程挑战解析Business Day Count 工作日统计算法的 JavaScript 实现【免费下载链接】freeCodeCampfreeCodeCamp.orgs open-source codebase and curriculum. Learn math, programming, and computer science for free.项目地址: https://gitcode.com/GitHub_Trending/fr/freeCodeCamp本篇技术指南围绕 freeCodeCamp 开源仓库中的每日编程挑战Challenge 198: Business Day Count题目文件见 curriculum/challenges/english/blocks/daily-coding-challenges-javascript/698a1a73ade5ac0e19180fa1.md展开完整解析题目规则、5 组官方断言测试的判定逻辑、基于Date的 UTC 安全实现方案以及不使用循环的数学公式优化思路。读完本文你将掌握两日期之间工作日计数这类常见业务算法在 JavaScript 中的正确写法理解时区陷阱与getUTCDay()的底层机制并能看懂 freeCodeCamp 每日编程挑战的题目组织方式与测试驱动形式。一、题目原文与规则拆解Challenge 198 的--description--部分给出了一段简洁而精确的需求描述Given a start date and an end date, return the number of business days between the two.并附带了四条明确的规则约束Given dates are in the formatYYYY-MM-DD日期输入统一为YYYY-MM-DD字符串格式Weekdays are business days (Monday through Friday)周一至周五为工作日Weekends are not business days (Saturday and Sunday)周六、周日不算工作日Include both the start and end dates when counting计数时首尾两天都要包含在内。注意最后一条规则是本题最容易踩坑的地方题目要求的是闭区间计数而不是传统意义上两个日期之间的开区间天数差。例如2026-02-24周二到2026-02-26周四这三个自然日全部落在周内官方期望返回3而如果误用(end - start) / 86400000 - 1之类的间隔天数公式得到的结果会是2从而无法通过测试。二、官方断言测试逐条剖析题目文件中的--hints--部分是 freeCodeCamp 测试驱动形式的核心每条 hint 都对应一个assert.equal断言由测试框架逐条校验用户提交的函数。本题共 5 组测试覆盖了同周内、跨周末、跨月、跨年等多个边界场景断言调用期望结果覆盖场景countBusinessDays(2026-02-24, 2026-02-26)3周二到周四全在工作日内验证闭区间计数countBusinessDays(2026-02-24, 2026-02-28)4周二到周六排除周六验证周末过滤countBusinessDays(2026-02-21, 2026-03-01)5周六到下周日跨越整个周末并跨月验证跨月与跨周末countBusinessDays(2026-03-08, 2026-03-17)7周日至次周二中间跨越一个完整周末验证周起点为周末countBusinessDays(2026-02-24, 2027-02-24)262跨整年含闰年相关的 2 月 29 日不存在问题验证算法正确性以2026-02-21, 2026-03-01为例2026-02-21 是周六、02-22 是周日随后 02-23 至 02-27 为周一至周五5 个工作日02-28 是周六、03-01 是周日因此结果为5。该用例同时验证了算法对起始日是周末和结束日是周末两种边缘情况的处理能力。最后一个用例返回262说明 2026-02-24 到 2027-02-24 之间共有 262 个工作日约 365 天中有 104 天周末再考虑起始日落在工作日内这一结果也可作为手工推演验证的基准。三、种子代码与函数契约题目给出的--seed--/--seed-contents--是学习者需要在其基础上补全的初始代码function countBusinessDays(start, end) { return start; }从中可以提炼出必须遵守的函数契约函数名固定为countBusinessDays接受两个参数start与end两个参数均为YYYY-MM-DD格式的字符串返回值必须是数字类型的工作日个数测试框架通过assert.equal(countBusinessDays(...), n)直接比对返回值因此不能打印额外输出、不能返回字符串或undefined。种子代码中return start;只是占位保证函数在补全前也能被调用而不抛异常最终解法需要替换整个函数体。四、官方参考解法逐日遍历 UTC 安全题目文件--solutions--段提供了官方参考实现function countBusinessDays(start, end) { const startDate new Date(start T00:00:00Z); const endDate new Date(end T00:00:00Z); let count 0; let current new Date(startDate); while (current endDate) { const day current.getUTCDay(); if (day ! 0 day ! 6) count; current.setUTCDate(current.getUTCDate() 1); } return count; }这段代码的精妙之处集中在三处1. 用T00:00:00Z构造 UTC 零点日期。直接调用new Date(2026-02-24)在 ECMAScript 规范中会被解析为 UTC 零点但new Date(2026, 1, 24)这类数字构造方式则使用本地时区而题目输入是纯日期字符串必须显式拼上T00:00:00Z将其锁定为 UTC 时间零点。这一步彻底规避了浏览器/Node 环境本地时区偏移导致的日期偏差一天问题。2. 用getUTCDay()而非getDay()判断星期。getDay()返回的是本地时区下的星期几0 表示周日6 表示周六而getUTCDay()返回 UTC 时区下的星期几。由于日期被构造为 UTC 零点二者在时区为负偏移如美东 UTC-5的环境下会出现不一致——例如 UTC 的周一零点在本地可能仍是周日的晚上。统一使用getUTCDay()才能保证判断与日期构造使用同一时区基准。3. 用setUTCDate(getUTCDate() 1)做日期自增。这是逐日遍历方案的核心setUTCDate会自动处理月份进位和年份进位如 2 月 28 日 1 变为 3 月 1 日12 月 31 日 1 变为次年 1 月 1 日并且天然兼容闰年2028 年 2 月 29 日能正确出现无需手写月份天数表。循环终止条件current endDate使用对象比较由于current始终被构造为 UTC 零点与endDate的 UTC 零点可以直接比较从而保证首尾两天闭区间都被计入。对于跨整年的用例262 个工作日该循环最多迭代 366 次性能完全足够。变体不修改原对象的写法如果希望保持传入的startDate不被修改参考解法中current是startDate的副本实际上startDate并未被改动也可以写成指针递增形式function countBusinessDays(start, end) { const startDate new Date(start T00:00:00Z); const endDate new Date(end T00:00:00Z); let count 0; let cursor Date.UTC( startDate.getUTCFullYear(), startDate.getUTCMonth(), startDate.getUTCDate() ); const end endDate.getTime(); while (cursor end) { const day new Date(cursor).getUTCDay(); if (day ! 0 day ! 6) count; cursor 24 * 60 * 60 * 1000; // 一天 86400000 毫秒 } return count; }用毫秒时间戳自增 86400000 可以避免setUTCDate的隐式对象变更语义更贴近步进一天但需要注意跨夏令时地区时毫秒加法与真实日历日可能不一致——这正是官方解法坚持用setUTCDate的原因。五、进阶优化无循环的数学公式方案当区间跨度极大例如十年以上时逐日遍历的时间复杂度为 O(n)。可以推导出 O(1) 的数学公式方案核心思路是先求总天数再减去其中的完整周末天数function countBusinessDays(start, end) { const startDate new Date(start T00:00:00Z); const endDate new Date(end T00:00:00Z); const totalDays Math.floor((endDate - startDate) / 86400000) 1; // 闭区间总天数 // 将起始日对齐到该日所在周的周日getUTCDay() 返回 0~6 const startOffset startDate.getUTCDay(); // 0 Sunday const weeks Math.floor((totalDays startOffset) / 7); const weekendDays weeks * 2; // 处理首尾不完整周中残留的周末 const remainder (totalDays startOffset) % 7; let extraWeekend 0; if (remainder 5) extraWeekend remainder - 5; // 周日(6)与周六(7)多出的天数 return totalDays - weekendDays - extraWeekend; }该公式将起始日是周几作为偏移量纳入计算把区间想象成从周日开始排列的若干个完整周每个完整周贡献 2 个周末日最后不足一周的零头再单独判断是否含周六、周日。它与官方逐日遍历解法在数学上等价读者可用上文 5 组测试用例逐一验证。实际工程中是否采用公式法取决于区间规模与可读性偏好——官方参考解法选择逐日遍历胜在直观、无边界公式推导错误风险。六、边界情况与测试完备性思考结合题目约束一个健壮的countBusinessDays还应正确处理以下边界起止日期相同闭区间计数应返回1若该日是工作日或0若该日是周末。题目给出的 5 组测试虽未直接覆盖该场景但Include both the start and end dates的规则隐含了这一定义起始日或结束日是周末如2026-02-21, 2026-03-01用例所示周末只影响自身是否计数不影响中间工作日的统计跨月、跨年、闰年 2 月 29 日依赖setUTCDate的自动进位即可正确处理无效日期字符串题目保证输入格式合法但生产代码可额外用Number.isNaN(startDate.getTime())做防御校验。这 5 组测试的分布也体现了测试设计的思路既有同周内的基础用例又有跨周末、跨月、跨年的渐进式复杂度用例最后用一个整年用例验证大规模正确性。七、该题目在 freeCodeCamp 仓库中的组织方式Challenge 198 并非孤立文件它在仓库中有着完整的配套设施理解这些结构有助于读者把题目放入整体上下文区块定义题目所属的daily-coding-challenges-javascript区块定义在 curriculum/structure/blocks/daily-coding-challenges-javascript.json其中challengeOrder按id列出了 Challenge 1 至 200 的全部题目本题目id为698a1a73ade5ac0e19180fa1即 Challenge 198并声明了helpCategory: JavaScript、usesMultifileEditor: true、blockLayout: legacy-challenge-list等区块级配置题目文件规范每个题目的 Markdown 文件采用 freeCodeCamp 的挑战格式frontmatter 中challengeType: 28标识题目类型正文由--description--、--hints--、--seed--、--solutions--四段构成这与curriculum/challenges/english/blocks/下其余题目的结构完全一致测试框架curriculum/src/test/daily-challenges.test.js 展示了每日挑战的通用测试模式通过getChallenges加载区块过滤出daily-coding-challenges-javascript与daily-coding-challenges-python两个区块并断言 JavaScript 与 Python 版本的数量、标题、描述与测试数量一一对应保证双语题目同步数据校验客户端通过 client/src/utils/daily-coding-challenge-validator.ts 中的 Joi schema 校验每日挑战数据其中tests数组每一项包含text与testString两个必填字段——testString正是本文所见的assert.equal(...)断言代码字符串后端接口API 服务在 api/src/daily-coding-challenge/routes/daily-coding-challenge.ts 中提供了按日期、按天、按月、全部、最新等多个只读查询路由如/daily-coding-challenge/date/:date其中对日期字符串同样要求YYYY-MM-DD格式非法格式返回 400与题目对输入格式的约束保持一致前端入口client/src/components/daily-coding-challenge/widget.tsx 是学习地图Map上的每日编程挑战入口组件链接到当日挑战页面与历史归档页面。八、总结Challenge 198: Business Day Count 是一道小而精的日期算法题其考察点可以归纳为三层规则理解闭区间计数 周末排除、API 选择UTC 构造日期 getUTCDay判断星期 setUTCDate步进、边界处理跨月跨年闰年。官方参考解法以 13 行代码同时解决这三个问题是学习 JavaScript 日期处理的绝佳范本。读者在独立实现时可先用本文第二节的 5 组断言测试自测再对照第四节官方解法检查时区处理是否一致最后可尝试第五节的公式法对比两种方案的性能与可读性取舍。【免费下载链接】freeCodeCampfreeCodeCamp.orgs open-source codebase and curriculum. Learn math, programming, and computer science for free.项目地址: https://gitcode.com/GitHub_Trending/fr/freeCodeCamp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考