freeCodeCamp Daily Coding Challenge 315(Summer Solstice)题解:用 JavaScript 根据纬度计算昼夜时长
【免费下载链接】freeCodeCampfreeCodeCamp.org's open-source codebase and curriculum. Learn math, programming, and computer science for free.项目地址: https://gitcode.com/GitHub_Trending/fr/freeCodeCamp
本篇技术指南围绕 freeCodeCamp 开源仓库中每日编码挑战(Daily Coding Challenge)第 315 题「Summer Solstice(夏至)」展开,完整还原题目要求、输出约定、全部测试断言与官方参考解法,并逐行拆解公式推导与边界处理。读完本文,你将掌握"基于纬度计算昼长、将小时数四舍五入到偶数、以 24 字符串对称排布昼夜符号"这类模拟/数学类题目的完整解题范式,同时了解该挑战在 freeCodeCamp 每日挑战体系(challengeType 28)中的真实运行机制。
一、挑战背景与题目定位
这道题位于curriculum/challenges/english/blocks/daily-coding-challenges-javascript/6a15cadf5f240d05a2649559.md,是 JavaScript 每日编码挑战块(block)中的第 315 道,标题为 "Challenge 315: Summer Solstice",其challengeType: 28表示这是一个每日编码挑战类型的题目。
题目的现实背景是:夏至是北半球一年中白昼最长的一天(南半球则是最短的一天)。本题要求写一个函数getDaytimeHours(latitude),给定一个纬度值,返回一个 24 字符的字符串,用符号直观地展示该纬度在夏至当天的昼夜分布。
二、题目要求与输出格式
原题(--description--部分)给出的完整规范如下:
输入纬度取值范围为90(北极)到 -90(南极),闭区间;
白天小时数的计算公式为:
白天小时数 = 12 + (latitude / 90) * 12将计算出的白天小时数四舍五入到最近的偶数;
返回一个 24 字符的字符串,其中
"☀️"代表白天的小时,"🌑"代表夜晚的小时,且:- 每个字符代表一个小时,从午夜(第 0 小时)开始;
- 日出与日落围绕正午对称。
原题给出的示例:纬度为0(赤道)时白昼为 12 小时,日出 6:00 AM、日落 6:00 PM,返回值应为:
"🌑🌑🌑🌑🌑🌑☀️☀️☀️☀️☀️☀️☀️☀️☀️☀️☀️☀️🌑🌑🌑🌑🌑🌑"三、数学建模:从纬度到昼夜字符串
解题的第一步是把自然语言描述转成可计算的数学模型。
3.1 昼长公式
按题目公式,给定纬度latitude,理论昼长(小时)为:
daylight = 12 + (latitude / 90) * 12验证两个极端:
latitude = 90(北极):daylight = 12 + 12 = 24,即极昼,全天 24 小时都是白天;latitude = -90(南极):daylight = 12 - 12 = 0,即极夜,全天 24 小时都是夜晚。
3.2 四舍五入到最近偶数
题目要求将昼长取整到最近的偶数。例如-33度时:
daylight = 12 + (-33 / 90) * 12 = 12 - 4.4 = 7.67.6 到最近的偶数是 8(距离 7.6 更近的偶数是 8,因为 6 与 7.6 的差为 1.6,而 8 与 7.6 的差为 0.4)。因此得到 8 小时白昼。
官方参考解法中使用的小技巧是:先除以 2 再四舍五入,最后乘回 2:
const daylightHours = Math.round((12 + (latitude / 90) * 12) / 2) * 2;Math.round(x / 2) * 2是"四舍五入到最近偶数"的标准实现:任何数除以 2 后四舍五入为整数,再乘 2,结果必为偶数,且是最接近原值的那个偶数。
3.3 日出/日落对称排布
由于日出与日落围绕正午(第 12 小时)对称,可得:
sunrise = 12 - daylightHours / 2 sunset = 12 + daylightHours / 2以赤道(latitude = 0)为例:daylightHours = 12,则sunrise = 6、sunset = 18,与题目示例完全一致。
四、官方参考解法逐行解析
原文档--solutions--部分给出的参考实现如下:
function getDaytimeHours(latitude) { const daylightHours = Math.round((12 + (latitude / 90) * 12) / 2) * 2; const sunrise = 12 - daylightHours / 2; const sunset = 12 + daylightHours / 2; let result = ""; for (let hour = 0; hour < 24; hour++) { result += hour >= sunrise && hour < sunset ? "☀️" : "🌑"; } return result; }逐行拆解:
- 第 2 行:按公式计算昼长并四舍五入到最近偶数,得到
daylightHours(必为 0~24 之间的偶数); - 第 3~4 行:由对称性计算日出小时与日落小时;
- 第 6~9 行:遍历 0~23 共 24 个小时,凡是落在
[sunrise, sunset)区间(含日出、不含日落)的记为白天"☀️",其余记为夜晚"🌑"; - 第 11 行:返回 24 字符的结果串。
注意边界语义:hour < sunset采用左闭右开区间,确保昼长恰好等于daylightHours个白天符号。当daylightHours = 24时sunrise = 0、sunset = 24,24 个小时全部为"☀️";当daylightHours = 0时sunrise = sunset = 12,没有任何小时满足hour >= 12 && hour < 12,24 个小时全部为"🌑"。
五、全部测试断言验证
原文档--hints--部分共给出 7 组断言,逐一演算验证如下:
| 纬度 | 理论昼长 | 取偶后昼长 | 日出 | 日落 | 结果形态 | 是否符合断言 |
|---|---|---|---|---|---|---|
0 | 12 | 12 | 6 | 18 | 6 夜 + 12 昼 + 6 夜 | ✅ |
90 | 24 | 24 | 0 | 24 | 24 昼(极昼) | ✅ |
-90 | 0 | 0 | 12 | 12 | 24 夜(极夜) | ✅ |
-33 | 7.6 | 8 | 8 | 16 | 8 夜 + 8 昼 + 8 夜 | ✅ |
66.5 | 20.867 | 20 | 2 | 22 | 2 夜 + 20 昼 + 2 夜 | ✅ |
40 | 17.333 | 18 | 3 | 21 | 3 夜 + 18 昼 + 3 夜 | ✅ |
-50 | 5.333 | 6 | 9 | 15 | 9 夜 + 6 昼 + 9 夜 | ✅ |
其中getDaytimeHours(66.5)是一个很好的取整示例:理论昼长约 20.87 小时,除以 2 得约 10.43,Math.round(10.43) = 10,乘 2 得 20,验证了"四舍五入到最近偶数"的行为(20.87 更接近 20 而非 22)。
六、种子代码与开发入口
挑战的--seed--部分提供了初始脚手架,学习者需要在此基础上补齐逻辑:
function getDaytimeHours(latitude) { return latitude; }初始代码直接返回纬度值,显然无法通过任何断言。正确的思路是严格遵循第三节的数学模型:计算昼长 → 取偶 → 求日出日落 → 按小时拼接符号。注意不要改动函数名getDaytimeHours与参数名latitude,因为测试断言(assert.equal)直接以该函数名为调用入口。
七、该挑战在 freeCodeCamp 每日挑战体系中的位置
7.1 block 结构配置
本题位于块配置文件 curriculum/structure/blocks/daily-coding-challenges-javascript.json 中,编号为 315(challengeOrder第 315 项,id 为6a15cadf5f240d05a2649559,紧随其后的第 316 题是 "1337 Speak")。整个 block 共 365 道题,对应一年中的每一天;块配置中usesMultifileEditor: true、helpCategory: "JavaScript",说明该系列以 JavaScript 为求解语言,并在多功能编辑器中作答。
7.2 前端如何消费 challengeType 28
在 client/src/client-only-routes/show-daily-coding-challenge.tsx 中可以看到,前端在组织每日挑战数据时显式使用challengeType: 28,对应类型定义 client/src/redux/prop-types.ts 中的challengeType: 28 | 29。也就是说,这类题目的渲染与提交走的是独立的每日编码挑战通道,而非普通的课程挑战。
7.3 每日挑战的种子机制
365 道挑战并非手工录入数据库,而是通过脚本批量生成的。在 tools/daily-challenges/seed-daily-challenges.ts 中:
- 从 "Dev Playground" superblock 通过 GraphQL 拉取 JavaScript 与 Python 两套挑战;
- 校验数量必须为
EXPECTED_CHALLENGE_COUNT = 365,且 JS/Python 两套数量一致; - 以
2025-08-11为起始日期,每道挑战向后顺延一天(START_DATE + i * ONE_DAY_IN_MS)写入 MongoDB 的DailyCodingChallenges集合。
具体的运行流程记录在 tools/daily-challenges/README.md 中:复制sample.env为.env、确保依赖已安装、以"显示 upcoming changes"模式启动主客户端以便脚本通过 GraphQL 获取挑战,然后在tools/daily-challenges目录下执行pnpm seed-daily-challenges完成入库。
7.4 后端 API 路由
挑战内容由 api/src/daily-coding-challenge/routes/daily-coding-challenge.ts 对外提供,包括:
GET /daily-coding-challenge/date/:date:按YYYY-MM-DD日期取当日挑战(不会返回晚于美国中部时间今天的挑战);GET /daily-coding-challenge/day/:day:按MM-DD(如09-30)取挑战,闰日02-29会被特殊处理;GET /daily-coding-challenge/today、GET /daily-coding-challenge/month/:month、GET /daily-coding-challenge/all、GET /daily-coding-challenge/newest等。
日期边界逻辑(美国中部时区、UTC 午夜换算)实现在该目录的utils/helpers.ts中,前端日历组件则位于 client/src/components/daily-coding-challenge/calendar.tsx。
7.5 JS/Python 双语言一致性校验
仓库还提供了专门的测试 curriculum/src/test/daily-challenges.test.js,用于保证每日挑战的 JavaScript 与 Python 两个版本:数量相等、id 一一对应、标题与描述一致、测试断言数量一致。这道 Summer Solstice 题同样遵循该约定,说明此类题目在两种语言下共享同一套题目语义与判题断言。
八、总结与举一反三
Challenge 315 表面上是一道"天文计算"题,内核其实是三个通用技能的叠加:
- 公式化建模:把自然语言("夏至昼最长""围绕正午对称")翻译成确定的数学表达式;
- 取整技巧:
Math.round(x / 2) * 2是实现"四舍五入到最近偶数"的简洁写法,可迁移到任何需要偶数取整的场景; - 区间遍历与符号拼接:用左闭右开区间
[sunrise, sunset)精确控制 24 个字符中白天符号的个数,天然规避了"多一个少一个"的边界错误。
掌握这三点后,同类问题(如按月份绘制昼夜图、按时区计算日出日落表、甚至前文提到的 Challenge 133 "Daylight Hours" 与 Challenge 222 "Equinox Shadows" 等天文主题兄弟题)都可以套用相同的分析路径快速求解。
关键源码索引:
- 挑战原文档:curriculum/challenges/english/blocks/daily-coding-challenges-javascript/6a15cadf5f240d05a2649559.md
- block 配置:curriculum/structure/blocks/daily-coding-challenges-javascript.json
- 每日挑战种子脚本:tools/daily-challenges/seed-daily-challenges.ts
- 每日挑战 API 路由:api/src/daily-coding-challenge/routes/daily-coding-challenge.ts
- 前端展示组件:client/src/client-only-routes/show-daily-coding-challenge.tsx
- JS/Python 一致性测试:curriculum/src/test/daily-challenges.test.js
【免费下载链接】freeCodeCampfreeCodeCamp.org's open-source codebase and curriculum. Learn math, programming, and computer science for free.项目地址: https://gitcode.com/GitHub_Trending/fr/freeCodeCamp
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考