ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

JavaScript时间函数全解:Date对象、时间戳与格式化实战

JavaScript时间函数全解:Date对象、时间戳与格式化实战 这次我们彻底把时间函数讲透。不管你是刚学 JavaScript 的前端新手还是写了好几年业务代码但一碰到时间格式化、时区换算、倒计时就会卡壳的开发这篇内容都值得从头看到尾。时间函数不是什么黑魔法它的问题多半出在几个固定的陷阱上时间戳与 Date 对象的换算、UTC 和本地时区的混淆、格式化输出时月份和星期的起点差异以及年月日时分秒的边界计算。把这几个点理清楚再配合一套自己用着顺手的封装函数绝大多数业务场景都能直接覆盖。这篇文章不是简单罗列 API我会从 Date 对象的底层行为讲起然后逐步拆解时间戳、格式化、解析、时间差、时区换算、倒计时和定时器每部分都会给出可直接运行的代码示例和验证方法。最后还会整理一份高频问题排查清单以及工程上更稳妥的时间处理方案。适合作为本地笔记收藏写业务代码之前翻一遍能省不少排查时间。1. 时间函数核心能力速览能力项说明核心技术对象JavaScript 内置 Date 对象运行环境浏览器控制台、Node.js、任何支持 ES5 的环境核心功能时间戳换算、日期解析、格式化、时间差计算、倒计时、时区处理入门门槛无第三方依赖原生 API 即可完成多数功能常用替代dayjs、date-fns、Temporal未来标准典型场景日志时间、订单时间、倒计时、活动有效日期、时区展示易错点月份从 0 开始、getMonth 和 setMonth 的坑、UTC 与本地时间混用是否支持批量任务不涉及批量但函数可循环处理多条时间记录时间函数最大的特点就是“看似简单细节极多”。下面每一节都会给出可复制的代码建议你在浏览器控制台里跟着跑一遍比只看不练有效得多。2. 适用场景与使用边界时间函数几乎贯穿所有前端项目但不同场景的侧重点完全不同。2.1 适合的场景列表页展示发布时间、更新时间例如“2025-06-01 14:30”这种固定格式。消息系统展示相对时间例如“3 分钟前”“昨天 18:00”。电商活动页做倒计时计算剩余秒数并动态渲染。数据报表中按天、按周、按月聚合需要计算时间区间起点和终点。日志记录与服务端交互时需要把时间戳转换为可读格式或把前端时间转成时间戳传给后端。国际化项目需要根据用户所在地时区展示对应时间。2.2 不合适的场景对时间精度要求极高、需要纳秒级操作的场景不应该依赖 JS Date建议用后端语言或专门的时序服务处理。需要频繁进行复杂时区转换和历法计算的场景原生 Date 会比较吃力建议直接引入 date-fns 或 dayjs。低版本浏览器环境需要注意 API 兼容性例如Date.parse对特定格式的解析在不同浏览器中结果不一致。2.3 使用边界时间函数的输入输出都是数据不涉及用户隐私但在做日志记录、用户行为采集时要避免采集超出业务必要的精确时间信息并遵守所在地区的数据合规要求。涉及跨时区的业务系统要明确时间是 UTC 存储还是本地时间存储并在接口文档里写清楚。3. 环境准备与验证工具时间函数是 JavaScript 原生能力不需要安装任何包。最方便的验证环境有两个。3.1 浏览器控制台打开 Chrome 或 Edge按 F12 进入开发者工具切到 Console 面板直接输入代码回车即可看到输出。这是验证时间函数最直接的方式尤其适合观察getMonth、getDay这类方法的返回值。3.2 Node.js 环境如果你在本地有 Node.js可以直接创建一个测试文件运行。node -v确认 Node.js 版本后创建一个测试脚本。touch time-test.js然后在time-test.js里写代码并用下面的命令运行node time-test.js不管用哪种方式这里建议始终把输出结果和预期值对照着看因为时间函数的很多返回值并不符合日常直觉比如getMonth()返回的是 0 到 11而不是 1 到 12。4. 时间戳换算与 Date 对象基础时间函数的核心是时间戳。JavaScript 中的时间戳指的是从 1970 年 1 月 1 日 00:00:00 UTC 开始经过的毫秒数这是一个绝对时间点与所在时区无关。4.1 获取当前时间戳当前时间戳是开发中最常用的操作。// 方式一Date.now()推荐 const now Date.now(); console.log(当前时间戳毫秒:, now); // 方式二new Date().getTime() const now2 new Date().getTime(); console.log(当前时间戳毫秒:, now2); // 方式三new Date()类型转换写法 const now3 new Date(); console.log(当前时间戳毫秒:, now3);三种方式得到的结果相同但在循环中建议使用Date.now()因为它不创建新的 Date 对象性能更好。4.2 时间戳与 Date 对象的互相转换后端接口经常返回秒级时间戳而 JavaScript 内部是毫秒级这是最常见的坑之一。// 假设后端返回秒级时间戳 const secondsTimestamp 1717223400; // 秒转毫秒 const date new Date(secondsTimestamp * 1000); console.log(date.toString()); // Date 对象转时间戳毫秒 const ms date.getTime(); console.log(ms); // 毫秒转秒注意向下取整 const backToSeconds Math.floor(ms / 1000); console.log(backToSeconds);如果直接把秒级时间戳传给new Date()得到的结果会显示为 1970 年附近的日期因为那个数值表示的是“1970 年 1 月 1 日 00:00:00 之后 17 亿秒”而实际应该乘以 1000 换算成毫秒。4.3 时间部分提取拿到 Date 对象后可以用内置方法提取年月日时分秒。const date new Date(2025-06-01T14:30:45); console.log(年份:, date.getFullYear()); console.log(月份:, date.getMonth()); // 注意0 表示 1 月 console.log(日期:, date.getDate()); console.log(小时:, date.getHours()); console.log(分钟:, date.getMinutes()); console.log(秒钟:, date.getSeconds()); console.log(星期几:, date.getDay()); // 0 表示周日6 表示周六从上面的输出可以清楚看到两个关键细节getMonth()返回的是 0 到 11getDay()返回的是 0 到 6其中 0 代表周日。这是新手最容易踩的坑也是在写格式化函数时最容易出错的地方。5. 日期格式化与解析格式化是时间函数中被问得最多的问题。原生 JavaScript 没有现成的format方法需要自己封装或者使用第三方库。这里先给出一个稳定可靠的格式化函数然后讲解不同方案的差异。5.1 手写格式化函数最常见的需求是输出YYYY-MM-DD HH:mm:ss格式。function formatDate(date, fmt YYYY-MM-DD HH:mm:ss) { const pad (n) String(n).padStart(2, 0); const map { YYYY: date.getFullYear(), MM: pad(date.getMonth() 1), DD: pad(date.getDate()), HH: pad(date.getHours()), mm: pad(date.getMinutes()), ss: pad(date.getSeconds()) }; return fmt.replace(/YYYY|MM|DD|HH|mm|ss/g, (match) map[match]); } const date new Date(); console.log(formatDate(date)); console.log(formatDate(date, YYYY/MM/DD)); console.log(formatDate(date, HH:mm:ss));这个函数思路不复杂核心做法就是把年、月、日、时、分、秒替换到模板里。注意getMonth()需要加 1否则 6 月会显示成 5 月。5.2 使用 toLocaleString 快速格式化如果只需要本地化展示不要求固定分隔符toLocaleString可以少写不少代码。const date new Date(); // 默认格式跟系统设置有关 console.log(date.toLocaleString()); // 指定日期时间样式 console.log(date.toLocaleString(zh-CN, { year: numeric, month: 2-digit, day: 2-digit, hour: 2-digit, minute: 2-digit, second: 2-digit, hour12: false }));toLocaleString的优势是内置本地化规则劣势是不同环境下的输出格式有差异不适合作为与后端约定的标准格式。5.3 解析字符串日期把字符串转成 Date 对象时最稳妥的方式是手动拆分而不是直接依赖Date.parse。function parseDateString(str) { const match str.match(/^(\d{4})-(\d{2})-(\d{2})[T\s](\d{2}):(\d{2}):(\d{2})$/); if (!match) { throw new Error(日期格式不合法); } const [, year, month, day, hour, minute, second] match; return new Date( Number(year), Number(month) - 1, Number(day), Number(hour), Number(minute), Number(second) ); } const d parseDateString(2025-06-01 14:30:45); console.log(d.toString());这里有一个重要的设计决策用new Date(2025, 5, 1, 14, 30, 45)这种方式创建时间得到的是本地时区时间。如果想按 UTC 解析应该使用Date.UTC()。这一点在下文时区部分会继续展开。6. 时间计算与时间差业务中大量需求是“计算某个时间之前/之后”“计算两个时间相差多久”这两类操作都可以用时间戳运算解决。6.1 日期的加减时间的本质是毫秒数所以加减某个时间段就是在时间戳上做算术运算。function addDays(date, days) { const result new Date(date); result.setDate(result.getDate() days); return result; } function addHours(date, hours) { const result new Date(date); result.setHours(result.getHours() hours); return result; } const now new Date(2025-06-01T12:00:00); console.log(加 1 天:, formatDate(addDays(now, 1))); console.log(减 3 天:, formatDate(addDays(now, -3))); console.log(加 2 小时:, formatDate(addHours(now, 2)));使用setDate和setHours的优点是 Date 对象会自动处理跨月、跨年的情况。例如 1 月 31 日加 1 天会自动变成 2 月 1 日不需要手动判断每月的天数。6.2 计算两个时间的时间差时间差在实现倒计时、统计时长时非常常用。function diffTimes(date1, date2) { const diffMs Math.abs(date2.getTime() - date1.getTime()); const days Math.floor(diffMs / (1000 * 60 * 60 * 24)); const hours Math.floor((diffMs % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); const minutes Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60)); const seconds Math.floor((diffMs % (1000 * 60)) / 1000); return { days, hours, minutes, seconds }; } const start new Date(2025-06-01T08:00:00); const end new Date(2025-06-03T10:30:20); console.log(diffTimes(start, end)); // 输出: { days: 2, hours: 2, minutes: 30, seconds: 20 }核心思路是把毫秒差值逐级拆分成天、时、分、秒。这里用Math.floor不是四舍五入原因是时间差逻辑上应该向下取整避免出现“0.5 天”这种不符合展示习惯的结果。6.3 获取某天起始时间和结束时间很多报表和筛选场景需要“某天的开始”“某天的结束”。function getDayStart(date) { const result new Date(date); result.setHours(0, 0, 0, 0); return result; } function getDayEnd(date) { const result new Date(date); result.setHours(23, 59, 59, 999); return result; } const date new Date(2025-06-15T16:20:30); console.log(当天开始:, formatDate(getDayStart(date))); console.log(当天结束:, formatDate(getDayEnd(date), YYYY-MM-DD HH:mm:ss.SSS));setHours(0, 0, 0, 0)这一行同时设置了小时、分钟、秒和毫秒是生成起始时间最经典的写法。如果要排除毫秒干扰也可以统一用getDayStart之后再getTime()比较。6.4 倒计时实现倒计时的核心是每秒刷新一次剩余时间然后重新计算时间差。function startCountdown(targetDate) { const timer setInterval(() { const now new Date(); const diff targetDate.getTime() - now.getTime(); if (diff 0) { clearInterval(timer); console.log(倒计时结束); return; } const { days, hours, minutes, seconds } diffTimes(now, targetDate); console.log(${days}天 ${hours}小时 ${minutes}分钟 ${seconds}秒); }, 1000); } const target new Date(); target.setSeconds(target.getSeconds() 10); startCountdown(target);注意这里不能直接用Math.ceil处理差值因为每秒的刷新有延迟会导致最后几秒显示不准确。更严谨的做法是用一个固定的结束时间戳每次用当前时间戳和结束时间戳作差而不是在每次回调里减 1。7. 时区问题与跨时区处理时区是时间函数里最让人头疼的部分。JavaScript 的 Date 对象本身存储的是绝对时间点但所有getFullYear、getMonth、getDate等方法都返回本地时区的值。如果你的用户不在同一个时区展示逻辑就要特别小心。7.1 获取 UTC 时间当需要跨时区传输时间时通常使用 UTC 字段。const date new Date(2025-06-01T14:30:45); console.log(本地年份:, date.getFullYear()); console.log(UTC 年份:, date.getUTCFullYear()); console.log(本地小时:, date.getHours()); console.log(UTC 小时:, date.getUTCHours());从输出可以清楚看到本地时间和 UTC 时间相差的数值取决于运行环境所在的时区。在中国时区UTC8本地时间会比 UTC 时间快 8 个小时。7.2 生成 ISO 字符串接口传输时间时推荐使用 ISO 8601 格式。const date new Date(); console.log(date.toISOString()); // 输出类似: 2025-06-01T06:30:45.000ZtoISOString()输出的时间始终是 UTC 时间末尾的Z表示零时区。如果后端需要接收带时区信息的字符串这是最标准的格式。但要注意这个字符串不能直接当作本地时间展示否则会少了时区偏移量。7.3 把 UTC 时间转换为指定时区的时间JavaScript 原生的toLocaleString支持指定时间时区。const date new Date(); const options { timeZone: America/New_York, year: numeric, month: 2-digit, day: 2-digit, hour: 2-digit, minute: 2-digit, second: 2-digit, hour12: false }; console.log(date.toLocaleString(en-US, options));如果要在代码里统一处理时区换算可以自己写一个带上偏移量的函数。function formatInTimeZone(date, timeZone) { return new Intl.DateTimeFormat(zh-CN, { timeZone, year: numeric, month: 2-digit, day: 2-digit, hour: 2-digit, minute: 2-digit, second: 2-digit, hour12: false }).format(date); } const date new Date(); console.log(北京时间:, formatInTimeZone(date, Asia/Shanghai)); console.log(纽约时间:, formatInTimeZone(date, America/New_York));使用Intl.DateTimeFormat的好处是时区逻辑由运行时处理不需要手动计算偏移量适合做国际化项目。7.4 一个容易踩坑的解析问题当使用new Date(2025-06-01T14:30:45)时这个字符串没有时区信息不同浏览器会把它当作本地时间解析但如果字符串是2025-06-01T14:30:45Z则会被当作 UTC 时间解析。这一点在实际开发中经常引发 bug因为后端返回的时间字符串可能带Z也可能不带需要在前端入口统一处理。// 不带 Z 的 ISO 字符串按本地时间解析 console.log(new Date(2025-06-01T14:30:45).toString()); // 带 Z 的 ISO 字符串按 UTC 时间解析 console.log(new Date(2025-06-01T14:30:45Z).toString());稳妥的做法是在项目里约定后端统一返回带时区信息的 ISO 字符串前端收到后立即转换成 Date 对象之后的所有展示逻辑都基于这个 Date 对象不做二次字符串解析。8. 常见问题与排查方法问题现象可能原因排查方式解决方案月份显示少一个月忘记getMonth()从 0 开始输出date.getMonth()看原始值格式化时getMonth() 1星期显示错位getDay()返回 0 到 60 表示周日输出date.getDay()看原始值自己维护[周日,周一,...]数组秒级时间戳转换成时间后显示 1970 年没有把秒转成毫秒打印时间戳数值判断位数new Date(timestamp * 1000)Date.parse解析字符串结果不一致不同浏览器对非标准格式支持不同检查传入字符串格式手动拆分字符串再创建 Date倒计时最后几秒不准确使用累减而不是固定结束时间打印每次回调的时间戳用固定结束时间戳与当前时间戳作差跨时区展示时间错误直接拿本地时间字段展示对比getHours()和getUTCHours()使用Intl.DateTimeFormat指定时区时间格式化函数输出NaN传入了无法解析的字符串打印new Date(value)的结果添加参数校验使用isNaN(date.getTime())判断额外补充一个判断 Date 是否有效的通用方法。function isValidDate(date) { return date instanceof Date !isNaN(date.getTime()); } console.log(isValidDate(new Date(2025-06-01))); console.log(isValidDate(new Date(not a date)));9. 最佳实践与使用建议时间函数的代码量不大但工程上的坑不少。下面这些建议来自实际业务经验可以直接拿来用。9.1 项目内统一封装时间工具不建议在业务代码里散落大量new Date()和手写格式化逻辑。建议建立一个time.js工具文件统一导出格式化、解析、时间差、倒计时等方法方便维护和测试。// time.js export function formatDate(date, fmt YYYY-MM-DD HH:mm:ss) { // ... 实现略参考上文 } export function parseDateString(str) { // ... 实现略参考上文 } export function diffTimes(date1, date2) { // ... 实现略参考上文 }这样做的另一个好处是如果团队决定引入第三方库如 dayjs只需要修改这个文件内部实现业务代码不用大面积改动。9.2 时间戳与格式化必须区分接口传输和存储时尽量使用时间戳或 ISO 字符串界面展示时才做格式化。不要在请求参数里传2025-06-01 14:30:45这种非标准字符串容易产生解析歧义。9.3 使用第三方库的时机如果时间处理逻辑越来越复杂涉及多时区、计算自然月、处理自然周等场景原生 Date 的代码可读性会明显下降这时建议引入 dayjs 或 date-fns。它们的体积通常只有几 KB支持按需引入缺点是会增加依赖管理成本。判断标准很简单原生代码超过 50 行且可读性差或者需要处理多个时区时就该考虑换库。# 使用 npm 安装 dayjs 示例 npm install dayjsimport dayjs from dayjs; import utc from dayjs/plugin/utc; import timezone from dayjs/plugin/timezone; dayjs.extend(utc); dayjs.extend(timezone); console.log(dayjs().format(YYYY-MM-DD HH:mm:ss)); console.log(dayjs().tz(America/New_York).format(YYYY-MM-DD HH:mm:ss));9.4 避免用字符串拼接替代时间格式化有些开发为了省事直接手动拼年月日const date new Date(); const result date.getFullYear() - (date.getMonth() 1) - date.getDate();这种写法在月份和日期小于 10 时直接输出2025-6-1格式不统一且和YYYY-MM-DD不一致。如果必须手写务必做补零处理。9.5 测试时注意本地时区开发环境在中国测试用例里如果写死了new Date(2025-06-01T12:00:00)在 CI 或服务器上运行时会因为服务器时区不同而出现差异。建议测试用例统一使用时间戳构造时间或使用带Z的 ISO 字符串。9.6 不要忽略性能问题在循环或渲染大量列表时频繁创建 Date 对象会有一定的性能损耗。对于列表页的发布时间可以在数据请求完成后统一格式化一次而不是在渲染函数里每次都调用格式化方法。10. 总结与下一步时间函数最值得搞懂的核心点是三个时间戳是绝对毫秒数、月份从 0 开始、本地时间与 UTC 时间的区别。只要把这三个点刻在脑子里几乎 80% 的时间相关 bug 都能提前避免。建议你先在控制台跑一遍格式化函数确认getMonth() 1和补零逻辑没有问题再跑一遍时间差计算验证跨天、跨小时的边界最后可以尝试把接口返回的 ISO 字符串完整解读一遍观察带Z和不带Z的解析差异。最容易踩的坑是后端返回秒级时间戳前端没乘 1000 直接渲染成 1970 年。这个坑值得在项目里加一个基础工具函数去兜底。后续可以继续扩展的方向包括dayjs 的按需接入、Temporal 新标准的跟进、以及基于时间函数封装一套业务级日期选择器组件。如果这篇文章对你有帮助建议收藏备用写时间处理代码之前翻一遍。
返回列表