关于 web-utilities-date-fns
面向使用 date-fns 处理日期的开发者的模式指南,涵盖格式化、解析、运算、比较、区域设置和时区。内容强调纯函数、按需导入和解析后验证,并区分 v3 与 v4 的时区方案。
主要功能
- 01按 Unicode TR35 规则格式化
- 02解析 ISO 与自定义日期并验证
- 03不可变日期运算与周期边界
- 04比较日期及处理区间
- 05区域化显示与版本对应的时区方案
适用场景
- 01格式化多语言界面中的日期
- 02验证用户输入或 API 日期
- 03计算账期、日历范围和时间间隔
- 04处理含时区的日期值
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ agents-inc/ skills --skill web-utilities-date-fns安装后在 Claude Code 中输入/web-utilities-date-fns确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
metadata.yamlreference.mdexamples/comparison.mdcore.mdi18n.mdrelative.mdtimezone.md详细介绍
date-fns 日期工具模式
快速指南: date-fns 是一组可进行摇树优化的纯函数,每个函数都会返回一个新的
Date,不会修改任何内容。ISO 字符串使用parseISO解析,自定义格式使用parse解析,然后检查isValid,因为 v4 返回 Invalid Date,而 v3 会抛出错误。格式化令牌遵循 Unicode TR35(yyyy、dd、EEEE),与 Moment 的令牌不同。时区支持由配套软件包提供,软件包名称取决于主版本。
详细资源:
- examples/core.md — 格式化、带验证的解析、算术运算、边界、预设范围
- examples/timezone.md — TZDate、
in选项、transpose、夏令时检测、UTC 类 - examples/i18n.md — 区域设置映射、区域化的月份和星期名称、每周起始日
- examples/relative.md — 距离、时长、相对与绝对显示
- examples/comparison.md — 比较、区间生成、重叠检测、验证
- reference.md — 格式化令牌表、函数索引、v4 重大变更、Moment 迁移表
适用的路径
- v4,且日期带有时区 —
@date-fns/tz提供TZDate和tz(),@date-fns/utc提供UTCDate,transpose则包含在核心软件包中。请参阅 examples/timezone.md。 - v3.x,且日期带有时区 —
date-fns-tz提供formatInTimeZone、toZonedTime和fromZonedTime。同一文件的后半部分。 - 时区无关紧要 — 存储并传输 ISO UTC,使用
parseISO读取,使用startOfDay规范化仅含日期的值,并跳过时区文件。
<critical_requirements>
编写 date-fns 代码前
使用 parseISO 解析 ISO 8601 字符串。 new Date(string) 的解析行为取决于引擎,因此
new Date("01/15/2026") 可能成功、失败,也可能因运行时不同而表示另一天。
对解析得到的任何值调用 isValid() 检查。 v4 会返回 Invalid Date 和 NaN,而 v3 会抛出异常,因此
未经检查的解析结果会一直悄无声息,直到后续首次调用 format。
按名称导入每个函数。 import * as dateFns 会破坏 tree-shaking 并引入整个
库;单独导入命名的 format 大约只需 2 KB。
使用 add* / sub* / startOf* 创建新日期,而不是使用 Date 的 setter。
date.setDate(...) 会修改该对象,所有持有该引用的地方都会共享此修改;date-fns
的每个函数都会返回一个新对象。
</critical_requirements>
自动检测: date-fns、format、parseISO、addDays、subMonths、differenceInDays、 formatDistance、formatDistanceToNow、isAfter、isBefore、eachDayOfInterval、startOfWeek、 weekStartsOn、date-fns-tz、@date-fns/tz、@date-fns/utc、TZDate、TZDateMini、UTCDate、UTCDateMini、 tz()、transpose、tzName、tzScan、tzOffset、withTimeZone
适用范围:
- 格式化日期以供显示,无论是否使用区域设置
- 解析日期字符串并验证解析结果
- 日期运算——增加或减少天、月、年和工作日
- 周期边界,以及为日历生成日期范围
- 比较、判断是否处于区间内,以及检测区间重叠
- 相对时间和时长表述(“2 小时前”、“5 个月 14 天”)
其他部分处理:
- 仅用于显示、不涉及日期运算的格式化——平台自带的
Intl.DateTimeFormat无需增加任何打包体积即可满足需求;如果运行时提供Temporal标准,那么本库的大部分功能都可以省去 - 重复规则——将重复事件展开为多个实例属于单独的工作;date-fns 处理的是展开后生成的时间点
- tz 数据库本身——配套包读取运行时的 IANA 数据,而不是自行打包
函数,而非方法。没有可链式调用的包装对象:format(date, "PP") 接收一个 Date
并返回字符串,addDays(date, 7) 接收一个 Date 并返回新的 Date。这让
库可以进行 tree-shaking——打包工具能够移除你未导入的 200 个函数——也让
每项操作都可以单独测试。
有两点值得牢记:组合时从内向外读取(format(startOfMonth(addDays(d, 7)), F)),
而且不存在隐式全局配置,因此只有作为选项传入的区域设置或时区才会影响调用。
核心模式
模式 1:格式化标记遵循 Unicode TR35
标记遵循 Unicode Technical Standard #35,与 Moment 不同:使用 yyyy 而非 YYYY,dd 而非
DD,EEEE 而非 dddd。Moment 标记不会报错,而是会生成不同的值。
import { format } from "date-fns";
const date = new Date(2026, 0, 15, 14, 30, 0);
format(date, "yyyy-MM-dd"); // "2026-01-15"
format(date, "MMMM d, yyyy 'at' h:mm a"); // "January 15, 2026 at 2:30 PM"
format(date, "EEEE"); // "Thursday"
完整标记表:reference.md。完整代码:examples/core.md
模式 2:适应区域设置的格式快捷方式
P、PP、PPP、PPPP(以及用于时间的 p、pp)会按照区域设置的惯例进行格式化,因此同一个
调用在每个地区都能正确显示。硬编码的 MM/dd/yyyy 只适用于美国。
import { format } from "date-fns";
import { enUS, de, ja } from "date-fns/locale";
format(date, "P", { locale: enUS }); // "01/15/2026"
format(date, "P", { locale: de }); // "15.01.2026"
format(date, "P", { locale: ja }); // "2026/01/15"
format(date, "PPPP", { locale: de }); // "Donnerstag, 15. Januar 2026"
完整代码:examples/i18n.md
模式 3:先解析,再验证
使用 parseISO 解析 ISO 8601,使用 parse 解析自定义格式,并将参考日期作为第三个参数传入。
这两者都不会抛出异常——使用前先验证。通过 format 进行往返转换,还可以额外排除解析成功但实际不存在的日期,例如 2 月 30 日。
import { parseISO, parse, isValid, format } from "date-fns";
const fromApi = parseISO("2026-01-15T14:30:00Z");
const fromUser = parse("15/01/2026", "dd/MM/yyyy", new Date());
if (!isValid(fromUser)) return null;
if (format(fromUser, "dd/MM/yyyy") !== "15/01/2026") return null; // Feb 30 etc.
完整代码:examples/core.md
模式 4:日期运算会返回新日期
每个 add* 和 sub* 函数都不会修改输入值,因此存储在状态中或作为 prop 传入的日期都可以安全地用于计算。
import { addDays, addMonths, subDays } from "date-fns";
const trialEnd = addDays(signupDate, 14);
const nextBilling = addMonths(signupDate, 1);
const warnAt = subDays(trialEnd, 7);
// signupDate is unchanged
完整代码:examples/core.md
模式 5:周期边界
startOf* / endOf* 会处理月份天数、闰年和每周起始日约定,并提供 >= start AND <= end 筛选器所需的闭区间。endOfDay 是 23:59:59.999,而不是午夜。
import {
startOfDay,
endOfDay,
startOfWeek,
startOfMonth,
endOfMonth,
} from "date-fns";
const today = { start: startOfDay(date), end: endOfDay(date) };
const thisMonth = { start: startOfMonth(date), end: endOfMonth(date) };
const isoWeekStart = startOfWeek(date, { weekStartsOn: 1 }); // Monday
完整代码:examples/core.md
模式 6:使用比较函数,而非时间戳运算
isSameDay、isWithinInterval 等函数能清晰表达意图,并处理手动实现容易出错的单位截断。
import { isAfter, isSameDay, isWithinInterval, isWeekend } from "date-fns";
isAfter(end, start);
isSameDay(a, b); // ignores time-of-day
isWithinInterval(candidate, { start, end }); // endpoints sorted, so order is not checked
isWeekend(date);
<red_flags>
注意事项
运行时会出错:
- 格式化未经验证的解析结果——对无效日期调用
format会抛出异常,而在 v4 中,解析本身不会发出警告。请先用isValid检查。 - 使用两个参数调用
parse——必须提供参考日期,其年份和月份会补充格式字符串中省略的内容。 - 对
UTCDate调用format(date, "z")——z令牌需要原生Date。
行为出乎意料:
start晚于end的区间不会被拒绝——从 v3 起,isWithinInterval和areIntervalsOverlapping会对端点排序,而eachDayOfInterval会按反向顺序返回日期。因此,用户颠倒顺序的范围会给出看似合理的结果,而不是报错。请自行排列区间端点。- 粘贴的 Moment 格式字符串会生成错误的值,而不是报错——这是迁移后的代码库中最常见的缺陷。
new Date("2026-01-15")表示 UTC 午夜,因为仅包含日期的 ISO 字符串被规定为 UTC;其他所有字符串格式的行为均由实现定义,因此相同输入在不同运行时可能相差整整一天。addMonths(jan31, 1)的结果是 Feb 28,addYears(feb29, 1)的结果也是 Feb 28——日期会调整为目标月份的最后一天。differenceIn*会截断:45 天对应的differenceInMonths是 1,而不是 1.5。v4 向零截断,而 v3 向下取整,因此负数结果会有所不同。- 每周起始日因地区而异,默认值为星期日——面向用户时,请传入
weekStartsOn或区域设置。 daysInYear等常量应从date-fns/constants导入,而不是从包根目录导入。
</red_flags>