news 2026/9/7 17:40:20

d3 时间格式化完全指南:d3-time-format 的 strftime/strptime 指令、解析器与 Locale 定制

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
d3 时间格式化完全指南:d3-time-format 的 strftime/strptime 指令、解析器与 Locale 定制

d3 时间格式化完全指南:d3-time-format 的 strftime/strptime 指令、解析器与 Locale 定制

【免费下载链接】d3Bring data to life with SVG, Canvas and HTML. :bar_chart::chart_with_upwards_trend::tada:项目地址: https://gitcode.com/GitHub_Trending/d3/d3

本篇基于 D3 官方文档 d3-time-format 模块说明 展开,系统讲解该模块提供的 strftime/strptime 近似实现:如何用d3.timeFormat/d3.timeParse/d3.utcFormat/d3.utcParse在日期对象与字符串之间双向转换,如何完整掌握 30 余种格式指令与填充修饰符,以及如何通过d3.timeFormatLocale/d3.timeFormatDefaultLocale定制国际化时间表示。读完后你既能写出多尺度自适应时间格式(D3 时间轴刻度标签的底层原理),也能为图表配置自定义语言环境下的日期解析。

模块定位:JavaScript 中的 strftime 与 strptime

d3-time-format 是 D3 生态中负责"时间 ↔ 字符串"转换的独立模块,它的目标是提供 C 标准库中久经考验的 strptime 和 strftime 函数的 JavaScript 近似实现,支持将 Date 对象格式化为各种本地化(locale-specific)的字符串表示,以及反向从字符串解析出日期。

在当前 D3 仓库(d3@7.9.0,见 package.json)中,d3-time-format^4.1.0版本作为独立依赖声明,并由聚合入口 src/index.js 通过export * from "d3-time-format"统一导出,因此d3.timeFormatd3.utcParsed3.isoFormat等 API 均可直接在全局d3命名空间下使用。

使用模式分为两条基本链路:

  1. 格式化(format):从格式指令串创建格式化器,传入 Date 得到字符串;
  2. 解析(parse):从同一指令串创建解析器,传入字符串得到 Date 或null
// 格式化:Date -> 字符串 const formatTime = d3.utcFormat("%B %d, %Y"); formatTime(new Date()); // "May 31, 2023" // 解析:字符串 -> Date const parseTime = d3.utcParse("%B %d, %Y"); parseTime("June 30, 2015"); // 2023-05-31

值得注意的是,模块内区分timeFormat(本地时区)与utcFormat(UTC 时区)两套对称 API。由于浏览器本地时区不可控,官方在 d3-scale 时间刻度文档中建议"尽可能优先使用 UTC 变体"——UTC 下每一天恒为 24 小时,且行为不依赖浏览器时区,结果更可预测。这与 d3-time 模块文档中"本模块仅工作在本地时区与 UTC 之下"的设计边界是一致的。

四大顶层 API:timeFormat、timeParse、utcFormat、utcParse

文档将四个顶层函数定义为对"默认 locale"上对应方法的别名(alias):

API等价于时区语义
d3.timeFormat(specifier)locale.format本地时间
d3.timeParse(specifier)locale.parse本地时间
d3.utcFormat(specifier)locale.utcFormatUTC 时间
d3.utcParse(specifier)locale.utcParseUTC 时间
d3.timeFormat("%b %d") // 本地时间格式化器 d3.timeParse("%b %d") // 本地时间解析器 d3.utcFormat("%b %d") // UTC 格式化器 d3.utcParse("%b %d") // UTC 解析器

四个函数共享同一套格式指令(下文详述),差别仅在于各指令解释 Date 时使用的时区:本地时间版本调用date.getMonth()date.getHours()等本地 getter;UTC 版本则调用getUTCMonth()getUTCHours()等。因此对同一 Date 对象,%d%H%m等指令在本地版本与 UTC 版本下可能产出不同字符串(尤其是跨越时区边界时)。

*locale*.utcFormat*locale*.utcParse的语义在文档中明确为:"与本地版本等价,只是所有指令按协调世界时(UTC)而非本地时间解释"。这意味着指令串本身在本地/UTC 两套 API 间完全通用,切换 API 即可切换时区语义,无需改动 specifier。

完整格式指令参考表(locale.format)

locale.format(specifier)返回一个新的格式化函数,specifier 是包含以%开头的"指令"(directive)的字符串。以下是文档给出的完整指令表,其中带*的指令会受 locale 定义影响:

指令含义
%a*星期缩写(abbreviated weekday name)
%A*星期全称(full weekday name)
%b*月份缩写(abbreviated month name)
%B*月份全称(full month name)
%c*该 locale 的日期与时间组合格式,如%x, %X
%d月份中的日,零填充十进制[01,31]
%e月份中的日,空格填充[ 1,31];等价于%_d
%f微秒,十进制[000000, 999999]
%gISO 8601 周纪年(不带世纪),十进制[00,99]
%GISO 8601 周纪年(带世纪),十进制
%H小时(24 小时制),十进制[00,23]
%I小时(12 小时制),十进制[01,12]
%j年中的第几天,十进制[001,366]
%m月份,十进制[01,12]
%M分钟,十进制[00,59]
%L毫秒,十进制[000, 999]
%p*AM 或 PM
%q年第几季度,十进制[1,4]
%Q自 UNIX 纪元起经过的毫秒数
%s自 UNIX 纪元起经过的秒数
%S秒,十进制[00,61](含闰秒上限)
%u以周一为首(ISO 8601)的星期,十进制[1,7]
%U以周日为首的周序号,十进制[00,53]
%VISO 8601 周序号,十进制[01, 53]
%w以周日为首的星期,十进制[0,6]
%W以周一为首的周序号,十进制[00,53]
%x*该 locale 的日期格式,如%-m/%-d/%Y
%X*该 locale 的时间格式,如%-I:%M:%S %p
%y年份(不带世纪),十进制[00,99]
%Y年份(带世纪),如1999
%Z时区偏移,如-0700-07:00-07Z
%%字面量百分号%

周序号的三种口径:%U / %W / %V

文档对周序号的边界规则做了专门说明:

  • %U:以周日为周首。新年中第一个周日之前的所有天都算第 0 周;
  • %W:以周一为周首。新年中第一个周一之前的所有天都算第 0 周;
  • 两者的周号计算均基于 d3-time 的interval.count。举例:2015-52 与 2016-00 同时指向 2015 年 12 月 28 日(周一),而 2015-53 与 2016-01 指向 2016 年 1 月 4 日(周一);
  • %V / %g / %G则遵循 strftime man page 的 ISO 周定义:

在该系统中,周从周一开始,编号从第一周的 01 直到最后一周的 52 或 53。第 1 周是新年中首个满足"至少 4 天落在新年内"的周(同义表述:第 1 周是包含星期四的第一个周;或等价于包含 1 月 4 日的那一周)。若 ISO 周号归属于上一年或下一年,则使用那一年作为年份。

也就是说%V的周号与%g/%G的"周纪年"是配套的:%V给出周号时,%G/%g给出该 ISO 周所属的年份(可能与%Y不同)。仓库的 CHANGES.md 记录了这一特性的引入历史:%G%g(ISO 8601 "week year")是在 5.0 版本中为 d3-time-format 新增的指令。

填充修饰符:0_-

%指示符后可以紧跟一个填充修饰符:

修饰符含义
0零填充(zero-padding)
_空格填充(space-padding)
-禁用填充(disable padding)

若不指定修饰符,默认是:所有指令默认为0(零填充),唯一例外%e默认_(空格填充)。文档同时指出:部分 strftime/strptime 实现支持字段宽度或精度参数,但 d3-time-format 尚未实现该特性。

locale.format的返回值是"格式化函数",接收一个 Date 并返回对应字符串:

const formatMonth = d3.timeFormat("%B"), formatDay = d3.timeFormat("%A"), date = new Date(2014, 4, 1); // Thu May 01 2014 00:00:00 GMT-0700 (PDT) formatMonth(date); // "May" formatDay(date); // "Thursday"

locale.parse:严格解析与 null 语义

locale.parse(specifier)返回一个解析器,接受字符串,返回对应的 Date;若字符串无法按该 specifier 精确匹配,则返回null。几个关键规则:

  • 解析时可使用与locale.format相同的指令集;
  • %d%e在解析时被视为等价(一个接受零填充、一个接受空格填充,解析端不做区分);
  • 解析是严格的:字符串必须与 specifier 精确匹配。以%Y-%m-%dT%H:%M:%SZ为例:
    • "2011-07-01T19:15:28Z"→ 正常解析(注意这里的Z是指令串中的字面字符,与%Z指令不同);
    • "2011-07-01T19:15:28""2011-07-01 19:15:28""2011-07-01"→ 均返回null

如果需要更宽松的解析策略,官方建议的顺序是:依次尝试多个格式,直到某个返回非 null 值(try multiple formats sequentially)。这是典型的"多格式回退"解析模式,适用于来源格式不统一的 CSV/JSON 数据。

ISO 8601 快捷通道:isoFormat 与 isoParse

除了通用指令系统,模块还提供了一对完整的 ISO 8601 UTC 快捷方法:

d3.isoFormat(new Date()); // "2023-05-31T18:17:36.788Z" d3.isoParse("2023-05-31T18:17:36.788Z"); // Date 对象
  • d3.isoFormat是完整 ISO 8601 UTC 格式化器,可用时会走Date.toISOString快路径;
  • d3.isoParse是完整 ISO 8601 UTC 解析器,可用时会直接交给 Date 构造函数。

需要特别注意的是d3.isoParse不保证严格的 ISO 8601 校验——它可能接受一些不符合规范的输入。文档明确指出:如果你依赖严格校验,应自行构造 UTC 解析器:

const strictIsoParse = d3.utcParse("%Y-%m-%dT%H:%M:%S.%LZ");

实战:多尺度自适应时间格式(multi-scale time format)

文档给出的进阶用法是条件时间格式:根据日期落在哪个时间粒度边界上,选择不同的指令串。这是 D3 时间刻度默认刻度标签生成逻辑的同款模式(时间刻度文档 中*time*.tickFormat的默认行为正是按 %Y / %B / %b %d / %a %d / %I %p / %I:%M / :%S / .%L 逐级选择)。

实现上,多尺度格式组合了 d3-time 的时间区间(interval)API(d3-time 文档 中的 d3.utcSecond、d3.utcMinute 等):interval(date) < date表示"该区间边界早于 date",即 date 携带了比该粒度更细的时间信息。

const formatMillisecond = d3.utcFormat(".%L"), formatSecond = d3.utcFormat(":%S"), formatMinute = d3.utcFormat("%I:%M"), formatHour = d3.utcFormat("%I %p"), formatDay = d3.utcFormat("%a %d"), formatWeek = d3.utcFormat("%b %d"), formatMonth = d3.utcFormat("%B"), formatYear = d3.utcFormat("%Y"); function multiFormat(date) { return (d3.utcSecond(date) < date ? formatMillisecond : d3.utcMinute(date) < date ? formatSecond : d3.utcHour(date) < date ? formatMinute : d3.utcDay(date) < date ? formatHour : d3.utcMonth(date) < date ? (d3.utcWeek(date) < date ? formatDay : formatWeek) : d3.utcYear(date) < date ? formatMonth : formatYear)(date); }

这段代码的判定链自下而上逐级检查:

  1. d3.utcSecond(date) < date→ 秒内有毫秒成分 → 用.%L(毫秒);
  2. d3.utcMinute(date) < date→ 分钟内有秒成分 → 用:%S
  3. d3.utcHour(date) < date→ 小时内有分钟成分 → 用%I:%M
  4. d3.utcDay(date) < date→ 日内有时分成分 → 用%I %p
  5. d3.utcMonth(date) < dated3.utcWeek(date) >= date→ 落在周边界上 → 用%b %d(周粒度);否则落在日边界上 → 用%a %d
  6. d3.utcYear(date) < date→ 落在月边界 → 用%B
  7. 其余 → 落在年边界 → 用%Y

这种"局部 + 全局上下文兼得"的表示方式正是 D3 时间轴的招牌风格:连续刻度被格式化为[11 PM, Mon 07, 01 AM]时,读者能同时看到小时、星期和日期信息,而不是三个孤立的小时[11 PM, 12 AM, 01 AM]。d3-scale 时间刻度文档明确推荐:若想自建这种条件格式,请参考 d3-time-format——即本文主题模块。

在 D3 生态中的角色:时间刻度的刻度标签来源

d3-time-format 并不是孤立模块,它在 D3 渲染管线中承担"刻度标签文本生成"的职责:

  • 时间刻度d3.scaleTime/d3.scaleUtc)生成ticks后,由*time*.tickFormat产出一个"合适的格式化器"用于轴标签。当显式传入 specifier 时,tickFormat等价于本文的locale.format;不传时则返回上述默认多尺度格式(时间刻度文档);
  • 坐标轴d3.axisBottom等)在tickFormat缺省时会调用*scale*.tickFormat,文档明确引导:"如需创建格式化器,参见 d3-format 与 d3-time-format"(见 d3-axis 文档)。

从源码组织看,D3 主仓库(本仓库)本身不直接包含 d3-time-format 的实现文件,它通过 package.json 中的依赖d3-time-format: ^4.1.0引入,并由 src/index.js 统一 re-export;文档则由 VitePress 站点维护(docs:dev/docs:build脚本,见 package.json),且 test/docs-test.js 中的测试会爬取docs/下所有 Markdown,校验文档内部链接指向存在的锚点——这解释了本文档中大量{#anchor}显式锚点的存在意义,也是文档内锚点链接(如本文引用的各小节)能够稳定跳转的原因。

Locale 定制:timeFormatLocale 与 timeFormatDefaultLocale

*的指令(%a%A%b%B%c%p%x%X)依赖 locale 定义。d3.timeFormatLocale(definition)接收一个定义对象,返回一个 locale 对象,该对象暴露formatparseutcFormatutcParse四个方法。definition必须包含以下 8 个属性:

属性含义
dateTime日期与时间(%c)的格式 specifier,如"%a %b %e %X %Y"
date日期(%x)的格式 specifier,如"%m/%d/%Y"
time时间(%X)的格式 specifier,如"%H:%M:%S"
periods上午/下午标记,如["AM", "PM"]
days星期全称数组,从周日开始
shortDays星期缩写数组,从周日开始
months月份全称数组,从 January 开始
shortMonths月份缩写数组,从 January 开始

完整示例(美式英语 locale):

const enUs = d3.timeFormatLocale({ dateTime: "%x, %X", date: "%-m/%-d/%Y", time: "%-I:%M:%S %p", periods: ["AM", "PM"], days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], shortDays: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], shortMonths: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] });

之后调用enUs.format("%a %b %e")enUs.utcParse("%B %d, %Y")等,即可得到基于该语言环境的格式化器/解析器。关键区别timeFormatLocale只返回一个独立的 locale 对象,不影响全局;而d3.timeFormatDefaultLocale(definition)与之等价,但会额外重定义全局的d3.timeFormatd3.timeParsed3.utcFormatd3.utcParse为新 locale 的对应方法:

const enUs = d3.timeFormatDefaultLocale({ dateTime: "%x, %X", date: "%-m/%-d/%Y", time: "%-I:%M:%S %p", periods: ["AM", "PM"], days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], shortDays: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], shortMonths: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] });

若不显式设置默认 locale,d3.timeFormat等顶层 API 默认使用美式英语locale(文档注明其来源为 d3-time-format 仓库中的locale/en-US.json)。因此对于非英文界面,推荐做法是:数据解析端用timeFormatLocale定义目标 locale 的parse方法(避免污染全局),仅当整站统一语言时再用timeFormatDefaultLocale

速查与常见陷阱小结

场景推荐用法
展示本地时区日期d3.timeFormat("%Y-%m-%d")
展示/解析 UTC 日期d3.utcFormat/d3.utcParse(行为更可预测,不依赖浏览器时区)
序列化/交换 ISO 8601d3.isoFormat/d3.isoParse
严格 ISO 校验解析d3.utcParse("%Y-%m-%dT%H:%M:%S.%LZ")
解析格式不统一的来源数据依次尝试多个parse,取首个非null结果
时间轴刻度标签参考多尺度格式,或直接使用scaleUtc().tickFormat()默认行为
多语言界面d3.timeFormatLocale定义独立 locale;全站统一时再用d3.timeFormatDefaultLocale

常见陷阱与文档明示的规则对照:

  1. %d/%e解析等价:格式化端二者输出不同(零填充 vs 空格填充),但解析端视为同一指令;
  2. 严格解析parse要求字符串与 specifier 精确匹配,差一个字符即返回null,不存在"尽力而为"模式;
  3. 字面量Z%Z%Y-%m-%dT%H:%M:%SZ末尾的Z是指令串中的普通字符;
  4. 周号三口径不可混用%U(周日起始,零周规则)、%W(周一起始,零周规则)、%V(ISO 周,配%G/%g周纪年)三者对同一日期可能给出不同结果;
  5. 字段宽度/精度参数未实现:部分 strftime 实现支持%05d之类的宽度语法,d3-time-format 目前不支持。

参考文档索引

  • 模块完整 API 文档:docs/d3-time-format.md
  • 时间区间 API(多尺度格式依赖):docs/d3-time.md
  • 时间刻度与默认刻度格式:docs/d3-scale/time.md
  • 坐标轴刻度格式化入口:docs/d3-axis.md
  • 聚合导出入口:src/index.js
  • 依赖版本与文档站点脚本:package.json
  • 版本变更历史(%G/%g新增等):CHANGES.md
  • 文档链接完整性测试:test/docs-test.js

【免费下载链接】d3Bring data to life with SVG, Canvas and HTML. :bar_chart::chart_with_upwards_trend::tada:项目地址: https://gitcode.com/GitHub_Trending/d3/d3

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 17:39:27

Linux容器中文件描述符限制问题排查与优化

1. 问题现象与背景定位上周深夜收到服务器告警&#xff0c;某核心业务容器突然出现大量"Too many open files"错误&#xff0c;导致服务雪崩。登录节点检查发现容器内进程的FD&#xff08;文件描述符&#xff09;使用量已经突破1024的默认限制。这个看似简单的限制背…

作者头像 李华
网站建设 2026/9/7 17:38:58

矫平机技术解析:从残余应力控制到薄板厚板工艺差异

我入行干板材矫平这些年&#xff0c;最常被问的一句话就是&#xff1a;“矫平机不就是个把板压平的机器吗&#xff1f;”每次听到我都想摇头。如果只是压平&#xff0c;那模具压力机比矫平机压得狠多了&#xff0c;为什么还得专门做一台矫平设备&#xff1f;因为矫平这件事&…

作者头像 李华
网站建设 2026/9/7 17:37:56

单片机毕设选题推荐:基于 STM32 或 51 单片机的危险投放检测智能垃圾桶设计与实现 基于 STM32 或 51 单片机的容量监测智能垃圾桶硬件系统设计

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机&#xff0c;Java、小程序技术领域和毕业项目实战 ✌️…

作者头像 李华
网站建设 2026/9/7 17:37:26

没有固定公网IP?一条NAT Server命令搞定内网服务对外访问

前阵子一个朋友问我&#xff1a;家里放了台服务器&#xff0c;人在公司想连回去&#xff0c;但宽带没有固定公网IP&#xff0c;是不是只能租云服务器或者搞内网穿透&#xff1f;我让他登进路由器看了一眼WAN口地址&#xff0c;发现还是100.64开头的运营商CGNAT段&#xff0c;这…

作者头像 李华
网站建设 2026/9/7 17:36:54

【单片机课设毕设项目】基于 STM32 或 51 单片机的环境感知智能风扇硬件系统设计 基于 STM32 或 51 单片机的手机 APP 控制智能风扇系统设计与实现(025506)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机&#xff0c;Java、小程序技术领域和毕业项目实战 ✌️…

作者头像 李华
网站建设 2026/9/7 17:35:31

Python机器学习零基础到实战:3个月高效学习路径

1. Python机器学习&#xff1a;从零基础到项目实战最近两年Python在机器学习领域的应用呈现爆发式增长&#xff0c;根据Stack Overflow开发者调查报告&#xff0c;Python已经连续五年成为最受欢迎的编程语言。我完整带过7个从零开始的机器学习实战项目&#xff0c;发现大多数初…

作者头像 李华