首页
/ freeCodeCamp 每日编程挑战解析:用 Python 计算距离周末的天数(Challenge 96: Is It the Weekend?)

freeCodeCamp 每日编程挑战解析:用 Python 计算距离周末的天数(Challenge 96: Is It the Weekend?)

2026-09-09 20:35:31作者:平淮齐Percy

本篇技术指南围绕 freeCodeCamp 开源仓库中 Python 每日编程挑战第 96 题 "Is It the Weekend?" 展开,完整拆解题目的输入输出规则、官方测试用例、种子代码与标准解法,并结合仓库中的课程结构、双语挑战校验测试、数据种子脚本与 API 路由,深入讲解 datetime 日期解析、星期编号、时区无关计算的底层原理。读完本文,你既能独立通过这道挑战,也能理解它在 freeCodeCamp 每日编程挑战体系中的完整运行链路。

挑战背景:freeCodeCamp 每日编程挑战体系

这道题属于仓库中 dev-playground 超级块(superblock)下的 daily-coding-challenges-python 块,是共 365 道每日挑战中的第 96 道。它的块配置定义在 curriculum/structure/blocks/daily-coding-challenges-python.json 中,关键元数据如下:

  • isUpcomingChange: true:标记为即将上线的新内容,需要启用相应环境变量(SHOW_UPCOMING_CHANGES)才能在客户端显示;
  • usesMultifileEditor: true:挑战使用多文件编辑器;
  • helpCategory: "Python":归入 Python 帮助分类;
  • blockLayout: "legacy-challenge-list":按传统挑战列表布局展示。

值得注意的是,这套每日挑战是双语配对的:每个挑战同时存在 JavaScript 与 Python 两个版本,二者共享同一个挑战 ID。本道题的 JS 版本位于 curriculum/challenges/english/blocks/daily-coding-challenges-javascript/68f6587287ad1f4ad39b0c82.md,其函数名为 daysUntilWeekend,而 Python 版本(即本文主题)的函数名为 days_until_weekend。仓库中的测试 curriculum/src/test/daily-challenges.test.js 会严格校验 JS 与 Python 两套挑战的数量一致、ID 一致,且在英语环境下标题、描述与测试数量也必须完全匹配——这就是题目规则双语完全对等的工程保障。

题目要求:返回距离周末的天数

题目本身是一道典型的日期与星期运算练习,原文规则如下:

Given a date in the format "YYYY-MM-DD", return the number of days left until the weekend.

逐条拆解输入输出契约:

  • 输入是一个形如 "YYYY-MM-DD" 的字符串,例如 "2025-11-14"
  • 周末从星期六开始(这是规则定义的关键:即使星期日也被视为"周末",但倒计时目标始终锚定在星期六);
  • 如果给定日期本身就是星期六或星期日,直接返回 "It's the weekend!"
  • 其他情况返回 "X days until the weekend.",其中 X 是距离下一个星期六的天数;
  • X 等于 1 时,必须使用单数形式 "day",而不是复数 "days"
  • 计算必须忽略本地时区——这是本题真正的隐藏考点,防止不同时区的运行环境得到不同结果。

用一句话概括核心逻辑:把字符串解析成"纯日期"(不带时区),算出它是星期几,然后分"周末/非周末"两条分支处理。

官方测试用例:六组断言全览

题目通过 # --hints-- 提供了六组由 unittest 驱动的测试断言,每组都在 runPython 环境中执行 TestCase().assertEqual(...) 对函数返回值做精确比对。这些用例覆盖了周一至周日全部七种星期情形以及单复数切换:

输入日期 星期 期望输出
"2025-11-14" 星期五 "1 day until the weekend."
"2025-01-01" 星期三 "3 days until the weekend."
"2025-12-06" 星期六 "It's the weekend!"
"2026-01-27" 星期二 "4 days until the weekend."
"2026-09-07" 星期一 "5 days until the weekend."
"2026-11-29" 星期日 "It's the weekend!"

以第一个断言为例,其原始测试代码为:

({test: () => { runPython(`
from unittest import TestCase
TestCase().assertEqual(days_until_weekend("2025-11-14"), "1 day until the weekend.")`)
}})

这套测试设计覆盖了三个关键点:周五边界(结果为 1,触发单数形式)、工作日普遍情形(周三、周二、周一)、周六与周日两个周末分支(返回固定文案)。注意最后一条 "2026-11-29" 是星期日,它同样返回 "It's the weekend!",验证了"周六或周日都算周末"的判定。

种子代码:从函数骨架开始

题目为学习者提供的起点(--seed--)极其精简,只定义了函数签名与占位返回:

def days_until_weekend(date_string):

    return date_string

你需要在这个骨架内补充完整实现,替换掉占位的 return date_stringdate_string 是唯一入参,函数必须返回题目规定的三种输出之一(周末文案、单数天数字符串、复数天数字符串)。

官方参考解法:逐步解析

题目 # --solutions-- 给出的参考实现如下:

from datetime import datetime

def days_until_weekend(date_string):
    date = datetime.strptime(date_string, "%Y-%m-%d").date()
    day_of_week = date.weekday()

    if day_of_week == 5 or day_of_week == 6:
        return "It's the weekend!"

    days_until_saturday = 5 - day_of_week
    return f"{days_until_saturday} day{'s' if days_until_saturday != 1 else ''} until the weekend."

逐行拆解其设计思路:

第 1 步:解析字符串为纯日期。 datetime.strptime(date_string, "%Y-%m-%d") 按格式指令 %Y-%m-%d(四位年份-两位月份-两位日期)把字符串解析为 datetime 对象,紧接着调用 .date() 剥离时间部分,得到 datetime.date 对象。date不带时区、不带时分秒的纯日历日期,这正是"忽略本地时区"的实现基础——后面会详细展开。

第 2 步:取得星期编号。 date.weekday() 返回一个 06 的整数:0 代表星期一,1 代表星期二……5 代表星期六,6 代表星期日。

第 3 步:周末分支。day_of_week 等于 5(周六)或 6(周日)时,直接返回固定文案 "It's the weekend!"。这里没有用 in (5, 6) 的写法而是拆成两个 or 条件,只是风格差异,逻辑等价。

第 4 步:计算倒计时。 days_until_saturday = 5 - day_of_week。由于 weekday() 的周六编号是 5,对于周一至周五(编号 04),这个差值恰好是"到下一个周六还差几天":周一 5 天、周二 4 天、周三 3 天、周四 2 天、周五 1 天。

第 5 步:单复数处理。 f"{days_until_saturday} day{'s' if days_until_saturday != 1 else ''} until the weekend." 是一个条件表达式嵌套在 f-string 中:当 days_until_saturday 不等于 1 时拼接 s,等于 1 时留空,从而得到 "1 day until the weekend." 而非 "1 days ..."。六组测试中唯一的单数情形是周五 "2025-11-14",正是对这条分支的验证。

时区无关性:这道题的隐藏考点

题目规则最后一条强调 "Make sure the calculation ignores your local timezone",这是最容易踩坑的地方。理解这一点需要区分 Python 中三类日期时间对象:

  • datetime:既含日期又含时间,可能带 tzinfo(时区信息),也可能不带(naive);
  • datetime.date:只有年月日,根本没有时区概念,因此它的星期编号在任何运行环境下都一致;
  • datetime(带 tzinfo)+ astimezone():会受本地时区影响,如果直接对带时区的对象取星期几,同一 UTC 时刻在不同时区可能落在不同日历日。

参考解法通过两步消除时区影响:strptime 按固定格式解析出的 datetime 本身是 naive 的,.date() 再进一步丢弃时间部分。此后 weekday() 完全由日历本身决定,与进程运行在 UTC、北京、纽约还是芝加哥无关。

对照 JS 版本可以看得更清楚,其官方解法是:

function daysUntilWeekend(dateString) {
  const [year, month, day] = dateString.split("-").map(Number);
  const date = new Date(Date.UTC(year, month - 1, day));
  const dayOfWeek = date.getUTCDay();
  if (dayOfWeek === 6 || dayOfWeek === 0) {
    return "It's the weekend!";
  }
  const daysUntilSaturday = 6 - dayOfWeek;
  return `${daysUntilSaturday} day${daysUntilSaturday === 1 ? "" : "s"} until the weekend.`;
}

这里用 Date.UTC(...) 构造时间戳再用 getUTCDay() 取星期,同样是为了绕开 getDay() 的本地时区偏移。注意两版星期编号体系不同:Python weekday()周一为 0(周一 0 … 周日 6),JS getUTCDay()周日为 0(周日 0 … 周六 6),所以 Python 版周末判定是 5 or 6、倒计时用 5 - weekday,而 JS 版周末判定是 6 or 0、倒计时用 6 - getUTCDay。逻辑一一对应,只是编号基准不同,这也解释了为什么两版测试断言完全一致。

边界情况与易错点分析

基于测试用例与解法,可以归纳出四类边界情况,供自行验证时对照:

  1. 周五 → 1 天:星期五编号为 45 - 4 = 1,输出单数 "1 day until the weekend."。这是唯一触发单数形式的工作日。
  2. 周六/周日 → 周末文案:编号 56 直接命中分支,输出 "It's the weekend!",不再计算天数。
  3. 周一 → 5 天:编号 05 - 0 = 5,即从周一开始需要跨越周二、周三、周四、周五才到周六。
  4. 字符串格式严格性strptime"%Y-%m-%d" 严格解析,"2025-1-1"(月份/日期无前导零)或 "2025/01/01" 这类格式会抛出 ValueError。六组测试全部使用标准格式,因此可以依赖严格解析,但若需防御性处理,可以提前用正则校验格式。

另外值得注意:题目描述虽然要求"忽略本地时区",但在 freeCodeCamp 的前端展示链路中,日期本身是与**美国中部时区(America/Chicago)**绑定的——客户端组件 client/src/components/daily-coding-challenge/helpers.ts 通过 toZonedTime(dateObj, 'America/Chicago') 获取"今日"并格式化为 yyyy-MM-dd 后请求对应挑战。也就是说,"哪一天是哪道题"由美国中部时区决定,而"这道题算出来是星期几"则完全由日历决定,两者并不冲突。

挑战在完整系统中的运行链路

这道题不只是孤立的练习,它在 freeCodeCamp 仓库中拥有完整的生产链路,理解这条链路有助于把握题目的工程背景:

  1. 课程编写:挑战以 Markdown 形式维护在 curriculum/challenges/english/blocks/daily-coding-challenges-python/ 目录下,frontmatter 中的 id68f6587287ad1f4ad39b0c82)、challengeType(本 Python 题为 29,对应 JS 版为 28)与 dashedName 用于课程解析。

  2. 双语配对校验curriculum/src/test/daily-challenges.test.jsSHOW_UPCOMING_CHANGES=true 环境下从 dev-playground 超级块加载挑战,逐一比对 JS/Python 两版的 ID、标题、描述与测试数量,防止两套课程内容漂移。

  3. 数据种子tools/daily-challenges/seed-daily-challenges.ts 从 2025-08-11 起按每天一道的节奏,把 365 道挑战写入 MongoDB 的 DailyCodingChallenges 集合。其辅助模块 tools/daily-challenges/helpers.ts 通过 GraphQL(端点 http://localhost:8000/___graphql)从正在运行的客户端拉取挑战,并用 combineChallenges 把 JS/Python 两版合并为一条同时含 javascriptpython 字段的记录;脚本内置了 EXPECTED_CHALLENGE_COUNT = 365 的校验,并锁定起始日期(若被改动会直接抛错)。完整操作步骤见 tools/daily-challenges/README.md

  4. API 提供api/src/daily-coding-challenge/routes/daily-coding-challenge.ts 暴露了 GET /daily-coding-challenge/date/:date/day/:day/today/month/:month/all/newest 等公开路由,按日期从 Prisma 查询挑战,且对"晚于美国中部今日"的日期返回 404;本道题对应的日期落在 2025-11-14 之后(第 96 道,即 2025-11-14 当天,因为种子从 2025-08-11 起每天一道,2025-08-11 + 95 天 = 2025-11-14)。

  5. 前端渲染client/src/client-only-routes/show-daily-coding-challenge.tsx 从 API 取回挑战数据后,将其转换为 show-classic 挑战模板所需的格式(补上 <section id="description"> 包装、设置 superBlock: 'daily-coding-challenge'usesMultifileEditor: true 等),用户在页面上就能用多文件编辑器编写 days_until_weekend 并通过测试。

自行验证:本地运行与测试

如果你想在本地亲手验证本题解法,可以按以下方式操作(仓库为只读,仅用于运行验证):

  1. 使用任一 Python 3 环境,将官方解法保存为脚本;
  2. 在脚本末尾追加六组断言对应的 unittest 用例,例如:
from unittest import TestCase

TestCase().assertEqual(days_until_weekend("2025-11-14"), "1 day until the weekend.")
TestCase().assertEqual(days_until_weekend("2025-01-01"), "3 days until the weekend.")
TestCase().assertEqual(days_until_weekend("2025-12-06"), "It's the weekend!")
TestCase().assertEqual(days_until_weekend("2026-01-27"), "4 days until the weekend.")
TestCase().assertEqual(days_until_weekend("2026-09-07"), "5 days until the weekend.")
TestCase().assertEqual(days_until_weekend("2026-11-29"), "It's the weekend!")
print("All tests passed!")

运行通过即说明实现与题目契约一致;也可以用 date -dcal 等工具交叉核对任一日期的真实星期,验证 weekday() 编号与周六倒计时的正确性。

小结

"Challenge 96: Is It the Weekend?" 是一道麻雀虽小、五脏俱全的日期处理题:它要求你掌握 datetime.strptime 的格式解析、date.weekday() 的星期编号体系、分支逻辑与单复数文案的精确输出,并以"忽略本地时区"约束你理解纯日期与带时区时间对象的本质区别。配合 官方解法 与仓库中整套双语配对、种子、API、前端链路,你既能通过本题,也能以此为一个完整样本,理解 freeCodeCamp 课程内容从 Markdown 到线上可做题环境的全流程。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
docsdocs
暂无描述
Markdown
900
5.83 K
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
927
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.89 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
602
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
396
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.04 K
526