freeCodeCamp Timestamp Microservice 实战指南:从课程规范解析 /api/:date? 日期转换 API 的设计与验证
本文基于 freeCodeCamp 课程仓库中「Timestamp Microservice」认证项目的官方任务文档(bd7158d8c443edefaeb5bdef.md)展开,完整梳理该微服务的 API 规范、全部测试断言的含义与容差逻辑,并结合仓库中的课程结构文件说明该项目的定位及其在新版课程体系中的演进,帮助读者按照可验收的标准独立实现一个通过全部自动测试的日期时间戳转换服务。
一、项目定位:Back-End 认证的第一个实战项目
Timestamp Microservice 是 freeCodeCamp「Back-End Development and APIs」认证(旧版)项目板块中的第一个认证项目,任务类型为 certification project(任务文档 frontmatter 中 challengeType: 4,并带有 forumTopicId: 301508 与 dashedName: timestamp-microservice)。
从课程结构文件可以确认其在课程体系中的位置:
- 在 back-end-development-and-apis-projects.json 中,
challengeOrder的第一项即为bd7158d8c443edefaeb5bdef(Timestamp Microservice),其后依次是 Request Header Parser、URL Shortener、Exercise Tracker 和 File Metadata Microservice,板块采用project-list布局; - 在客户端配置 cert-and-project-map.ts 中,该项目 ID 被映射到认证
Certification.BackEndDevApis,并关联官方示例服务的链接路径${apiMicroBase}/timestamp-microservice; - 在新版(v9)课程 back-end-development-and-apis-v9.json 中,同一主题演化为位于
rest-api-and-web-services模块之后的认证实验室lab-timestamp-microservice,其任务文档 ba104e7e4a2f6a16f3c168d3.md 要求通过全部项目测试后,将提交代码托管到个人的代码仓库并回填链接。
因此,本文所讲的旧版项目规范,实际上构成了新版实验室项目的基础能力要求:构建一个具备日期解析逻辑的 RESTful API。
二、任务目标与两种开发路径
任务文档给出的核心目标是:构建一个功能上类似于 freeCodeCamp 官方示例服务(timestamp-microservice)的全栈 JavaScript 应用。文档规定了两种可行的开发方式:
- 本地开发路径:克隆 freeCodeCamp 官方的
boilerplate-project-timestamp样板仓库,在本地完成项目开发; - 在线构建路径:使用任意站点构建平台完成项目,但必须完整引入样板仓库中的所有文件。
文档中有一条关键的时区假设说明:
时区转换不是本项目的目的,因此假设所有发送来的有效日期都会以 GMT 时间解析,即使用
new Date()解析。
这条说明直接决定了实现策略:服务端不需要处理任何时区偏移或 Intl 格式化,只要保证「输入字符串 → GMT 语义的时间戳 → 输出」这条链路正确即可。
三、API 规范逐条解析
该项目的验收方式不是人工评审,而是一组针对部署后服务的自动 HTTP 测试。任务文档的 # --hints-- 部分实际上就是完整的接口契约。以下按测试顺序逐条解析,保留原始断言代码,便于对照实现。
3.1 红线测试:禁止直接调用示例服务
assert(
!/.*\/timestamp-microservice\.freecodecamp\.rocks/.test(code)
);
第一条断言检查提交的项目代码中不得出现官方示例服务的地址。这保证了验收的是你自己的实现,而不是对示例服务的代理或转发。
3.2 有效日期输入:返回 unix 与 utc 两个字段
路由形如 /api/:date?,即日期参数可选。对于有效日期,响应必须是一个包含两个键的 JSON 对象:
| 键 | 类型 | 含义 |
|---|---|---|
unix |
Number | 输入日期对应的 Unix 时间戳,单位是毫秒 |
utc |
String | 输入日期,格式形如 Thu, 01 Jan 1970 00:00:00 GMT |
unix 字段的断言示例:
const response = await fetch(code + '/api/2016-12-25');
if (!response.ok) {
throw new Error(await response.text());
}
const data = await response.json();
assert.equal(
data.unix,
1482624000000,
'Should be a valid unix timestamp'
);
注意 1482624000000 这个值:13 位数字,正是毫秒级时间戳。utc 字段的断言则精确到字符串逐字符相等:
const response = await fetch(code + '/api/2016-12-25');
// ...
assert.equal(
data.utc,
'Sun, 25 Dec 2016 00:00:00 GMT',
'Should be a valid UTC date string'
);
这个 Sun, 25 Dec 2016 00:00:00 GMT 格式与 JavaScript Date 对象的 toUTCString() 输出完全一致,是提示了「用内建 API 而非手写格式化」的实现方向。同时它也验证了一个细节:2016-12-25 这类不带时间的日期串,应解析为该日 GMT 零点(这也呼应了文档中「一律按 GMT 解析」的假设)。
3.3 纯数字时间戳输入
如果参数本身就是毫秒时间戳,服务必须原样回显并附带对应的 UTC 字符串:
const response = await fetch(code + '/api/1451001600000');
// ...
assert(
data.unix === 1451001600000 &&
data.utc === 'Fri, 25 Dec 2015 00:00:00 GMT'
);
这要求实现对「全数字参数」与「日期字符串参数」做分支处理:数字直接走 new Date(number),字符串走 new Date(string)。
3.4 可被 new Date() 解析的任意日期字符串
const response = await fetch(code + '/api/05 October 2011, GMT');
// ...
assert(
data.unix === 1317772800000 &&
data.utc === 'Wed, 05 Oct 2011 00:00:00 GMT'
);
测试用例特意选了一个非 YYYY-MM-DD 格式的宽松日期串 05 October 2011, GMT,其验收标准只有一条:凡是被 new Date(date_string) 成功解析的输入,都必须返回正确结果。因此实现上不应写死某种日期格式的正则,而应直接依赖引擎的解析能力。
3.5 无效日期输入:返回错误对象
const response = await fetch(code + '/api/this-is-not-a-date');
if (response.ok) {
const data = await response.json();
assert.equal(data.error.toLowerCase(), 'invalid date');
} else {
const errorData = await response.json();
assert(errorData.error.toLowerCase() === 'invalid date');
}
这里断言写得非常宽容:无论返回 HTTP 状态码是 200 还是 4xx,只要响应体是 { error: "Invalid Date" }(断言做了 toLowerCase() 比较,大小写不敏感)即可。这给实现者留出了自由度——可以用 400 状态码返回错误体,也可以 200 带 error 键。
3.6 空参数:返回当前时间,容差 20 秒
const response = await fetch(code + '/api');
// ...
const data = await response.json();
var now = Date.now();
assert.approximately(data.unix, now, 20000);
utc 字段同理:
const response = await fetch(code + '/api');
// ...
const data = await response.json();
var now = Date.now();
var serverTime = new Date(data.utc).getTime();
assert.approximately(serverTime, now, 20000);
两个要点:
unix与utc两个字段都会对「空参数」场景做断言,不能只处理其一;assert.approximately(x, now, 20000)的第三参 20000 是 20 秒的容差窗口,用于吸收测试发起时刻、服务端处理时刻与网络往返之间的时间差。这也意味着服务端时间需要与测试运行环境的时钟大体同步,返回的是毫秒级时间戳而非秒级。
3.7 规范汇总表
| 请求 | 期望响应 |
|---|---|
GET /api/2016-12-25 |
{ unix: 1482624000000, utc: 'Sun, 25 Dec 2016 00:00:00 GMT' } |
GET /api/1451001600000 |
{ unix: 1451001600000, utc: 'Fri, 25 Dec 2015 00:00:00 GMT' } |
GET /api/05 October 2011, GMT(URL 编码后) |
{ unix: 1317772800000, utc: 'Wed, 05 Oct 2011 00:00:00 GMT' } |
GET /api/this-is-not-a-date |
{ error: 'Invalid Date' }(HTTP 状态码可 200 可 4xx) |
GET /api |
当前时间,unix/utc 均与 Date.now() 相差不超过 20 秒 |
| 代码中引用示例服务地址 | 直接判负(红线测试) |
四、对照规范的最小实现要点
从规范可以反推出服务端逻辑的核心链路(以 Node.js + Express 这类常见栈为例,具体框架不限):
- 路由定义:
app.get('/api/:date?', handler),用可选参数覆盖「带日期」与「空参数」两种请求; - 参数分支:
- 无参数 →
new Date()取当前时间; - 全数字参数 → 视为毫秒时间戳,
new Date(Number(param)); - 其他字符串 →
new Date(param),并保留引擎解析结果;
- 无参数 →
- 合法性判断:用
isNaN(date.getTime())判断解析失败,失败时返回{ error: 'Invalid Date' }; - 字段生成:
unix用date.getTime()(天然毫秒),utc用date.toUTCString()——其输出格式与测试期望的Sun, 25 Dec 2016 00:00:00 GMT完全吻合; - 部署前提:服务必须公网可达并支持测试方以
code + '/api/...'的形式拼接请求(测试代码中的code即提交的项目 URL),且响应必须设置 JSON 内容类型以便response.json()解析。
整个服务的状态逻辑集中在「解析 → 校验 → 序列化」三步,没有任何持久化或状态依赖,这正是它被选作 Back-End 认证第一个实战项目的合理性:它只要求最基础的 HTTP 路由与 JSON 响应能力,却完整覆盖了参数解析、错误分支、时间语义三个易错点。
五、验证方式与相关仓库文件
该项目的验收是全自动的:提交服务 URL 后,测试会依次执行 3.1~3.6 节的全部断言,任一失败即不通过。对照仓库中以下文件,可以复核本文所述的每一处依据:
- 项目任务文档与全部测试断言:curriculum/challenges/english/blocks/back-end-development-and-apis-projects/bd7158d8c443edefaeb5bdef.md
- 板块项目顺序配置:curriculum/structure/blocks/back-end-development-and-apis-projects.json
- 认证-项目映射(含示例服务链接):client/config/cert-and-project-map.ts
- 新版课程中该主题的实验室模块:curriculum/structure/superblocks/back-end-development-and-apis-v9.json、curriculum/structure/blocks/lab-timestamp-microservice.json
- 新版实验室任务文档:curriculum/challenges/english/blocks/lab-timestamp-microservice/ba104e7e4a2f6a16f3c168d3.md
适用前提说明:本文描述的是旧版 Back-End Development and APIs 认证中 challengeType: 4 的项目规范,测试断言以当前仓库中任务文档为准;若参与新版(v9)课程的 lab-timestamp-microservice 实验室,验收流程为「通过项目测试 → 提交代码仓库链接」,具体表单要求见新版实验室文档。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00