☰
JS Tips:在 JavaScript 中简单获取 Unix 时间戳(Date.now / getTime / 一元 `+` 全解析)
2026/10/8 8:07:30 网站建设 项目流程
  • 教程

【免费下载链接】jstips

This is about useful JS tips!

项目地址:https://gitcode.com/gh_mirrors/js/jstips
点击查看免费下载

Unix 时间戳在日志记录、接口签名、缓存过期、埋点上报等场景中无处不在。本文以 JS Tips 仓库第 49 期中文文档(_posts/zh_CN/javascript/2016-02-26-extract-unix-timestamp-easily.md)为核心骨架,系统讲解获取当前时刻与指定日期 Unix 时间戳的多种写法、底层运行原理、时区与精度陷阱,以及实战选型建议。读完本文,你将掌握一套可读、可维护、可复用的时间戳获取方案,并理解Date.now()、getTime()、一元+操作符之间的等价关系与细微差别。

一、先明确概念:Unix 时间戳与 JavaScript Date 的单位

Unix 时间戳(Unix timestamp / Epoch time)定义为自 1970 年 1 月 1 日 00:00:00 UTC 以来经过的秒数,是一个整数(或含小数的实数)。

而 JavaScript 的Date对象在底层以毫秒为单位存储时间——Date内部保存的是自同一纪元以来的毫秒数。因此,从Date相关 API 拿到的原始数值是毫秒,要得到 Unix 时间戳(秒),必须除以 1000。这一单位差异是整篇文章所有代码里Math.floor(dateTime / 1000)一行的由来。

二、获取当前时刻的 Unix 时间戳:三种写法

原文档给出了一组等价的写法,核心套路都是:先拿到毫秒值,再除以 1000 取整。

1.Date.now()—— 最简单直接

const dateTime = Date.now(); const timestamp = Math.floor(dateTime / 1000);

Date.now()是 ES5 引入的静态方法,直接返回当前时间的毫秒值,无需先构造Date实例。从实现层面可以推断:相比需要 new 对象的写法,它省去了对象分配的开销,因此原文档称之为"最简单最快的方式"(fastest way)是有依据的——它同时做到了写法最简与路径最短。

2.new Date().getTime()—— 传统等价写法

const dateTime = new Date().getTime(); const timestamp = Math.floor(dateTime / 1000);

getTime()是Date.prototype上的实例方法,返回与Date.now()完全相同的毫秒值。二者的返回值等价,区别仅在于new Date()先创建了一个当前时刻的实例。若要追求极致的性能与更少的对象分配,优先选Date.now();在必须持有Date对象(例如后续还要调用其它Date方法)的场景下,再用getTime()。

3.+new Date()—— 一元加号技巧

const dateTime = +new Date(); const timestamp = Math.floor(dateTime / 1000);

这是原文档强调的一个简洁技巧:在new Date()前面加一个一元+操作符,直接把Date对象"一元化"为毫秒数值,省去了显式调用.getTime()。

一元+也适用于指定日期:

const dateTime = +new Date('2012-06-08'); const timestamp = Math.floor(dateTime / 1000);

4. 等价但更啰嗦的变体(了解即可)

以下写法在语义上与上述等价,但由于需要额外的方法调用或解析,并不推荐日常使用:

// 显式调用 valueOf()(一元 + 内部最终也是走到 valueOf) const timestamp = Math.floor(new Date().valueOf() / 1000); // Date.parse() 只解析字符串,不构造对象 const timestamp = Math.floor(Date.parse(new Date().toISOString()) / 1000);

其中Date.parse()更适合解析已存在的日期字符串,而不是"取当前时间"——拿当前时间再序列化再解析,绕了一圈且存在精度损耗,属于反模式。

5. 统一换算:为什么用Math.floor

dateTime是毫秒,除以 1000 后可能产生小数(例如当前毫秒数不是整秒时)。Math.floor用于向零方向取整,得到秒级整数:

Math.floor(Date.now() / 1000); // 秒级整数 Date.now(); // 毫秒级整数(精度更高)

若你还需要亚秒精度,可直接保留毫秒值或使用Date.now() % 1000获取毫秒余数,无需经过除法取整。

三、获取指定日期的 Unix 时间戳

1. 用字符串解析:YYYY-MM-DD与YYYY-MM-DDT00:00:00Z

原文档指出,将yyyy-mm-dd或YYYY-MM-DDT00:00:00Z形式的字符串传给Date构造函数即可解析指定日期:

const dateTime = new Date('2012-06-08').getTime(); const timestamp = Math.floor(dateTime / 1000);

两种字符串形态的差异在于时区信息:

  • YYYY-MM-DD:纯日期形式。按 ECMAScript 规范的日期-时间字符串格式(ISO 8601 子集),date-only 形式按UTC解释,即视为 UTC 当天 00:00:00。
  • YYYY-MM-DDT00:00:00Z:带T分隔符并显式声明Z(UTC 时区标记),含义完全确定,不存在解释歧义。

由于各版本规范对"无时区信息的字符串"处理存在差异,且历史上部分运行时实现并不完全一致,最稳妥的做法是显式携带时区信息(Z或+08:00),让解析结果与运行环境无关。

2. 时区陷阱:同样写"2012-06-08",结果可能不同

new Date('2012-06-08')与new Date('2012-06-08T00:00:00')(带时间但无时区标记)的解析基准可能不同:前者按 UTC,后者按本地时区解释。在非 UTC 时区(如东八区)下运行,二者会相差 8 小时,换算成秒就是 28800 秒的偏差。如果你的业务要求"某天在服务器时区下的零点",请务必明确时区语义,否则会出现时间戳偏移的隐性 bug。

3. 更可控的写法:数字参数构造函数与Date.UTC()

字符串解析受规范版本与运行时实现影响,若要完全确定,可用数字参数构造:

// 本地时区:2012-06-08 00:00:00(本地) const local = new Date(2012, 5, 8); // 注意:月份从 0 开始,6 月是 5 const timestampLocal = Math.floor(local.getTime() / 1000); // UTC:2012-06-08 00:00:00 UTC,与 '2012-06-08T00:00:00Z' 等价 const timestampUTC = Math.floor(Date.UTC(2012, 5, 8) / 1000);

Date.UTC()是静态方法,接受与Date构造函数相同的数字参数(年、月从 0 计、日、时、分、秒),但始终按 UTC 计算并直接返回毫秒值,无需先构造实例——这是获取"某具体时刻 UTC 时间戳"最无歧义的方式。

四、底层原理:一元+、valueOf()与toNumber()

原文档专门解释了+new Date()的底层机制:运行时调用Date对象的valueOf()方法,然后一元+操作符对该返回值执行toNumber()(即规范中的 ToNumber 抽象操作)。

拆解如下:

  1. new Date()产生一个Date实例;
  2. 一元+操作符要求把操作数转为数值,触发ToNumber抽象操作;
  3. 对对象类型,ToNumber会先执行ToPrimitive(hint 为number),此时Date对象优先调用valueOf();
  4. Date.prototype.valueOf返回该日期自纪元以来的毫秒数;
  5. 数值转换完成,得到毫秒值。

因此+new Date()、new Date().valueOf()、new Date().getTime()三者最终殊途同归,返回的都是同一个毫秒数。原文档引用了 ES5 规范中的Date.prototype.valueOf、一元+操作符与toNumber()相关章节,读者如有兴趣可进一步查阅 ECMAScript 规范对应条目(这些外部规范链接在原文档 _posts/zh_CN/javascript/2016-02-26-extract-unix-timestamp-easily.md 中有列出)。

五、精度与边界:毫秒换算、取整方式与 Y2038

1.Math.floor与一元+的组合是安全的

Math.floor(dateTime / 1000)对任意毫秒值都成立,因为毫秒值恒为正数,Math.floor等价于"去掉小数"。这一组合不存在负数方向的问题。

2. 警惕~~位运算取整:Y2038 隐患

部分开发者为了"更快"会写成~~(+new Date() / 1000),用双按位非替代Math.floor。仓库内另一篇 tip(_posts/zh_CN/javascript/2016-01-18-rounding-the-fast-way.md)专门警告过这条路的风险:~操作符会先把操作数截断为32 位有符号整数,因此~~对超出 ±2^31 范围的数值会溢出回绕。

Unix 时间戳恰好会踩中这个雷区:当时间超过2038 年 1 月 19 日 03:14:07 UTC(即著名的 Y2038 limit,32 位有符号秒数上限)后,~~会算出完全错误的负数。该文档给出了一个 2040 年的例子:

epoch = +new Date('2040-01-01') / 1000 + 0.123 // -> 2208988800.123 epoch_int = ~~epoch // -> -2085978496(回绕成负数!) console.log(new Date(epoch_int * 1000)) // -> Wed Nov 25 1903 17:31:44 UTC // 用 Math.floor 才是正确结果 epoch_flr = Math.floor(epoch) // -> 2208988800 console.log(new Date(epoch_flr * 1000)) // -> Sun Jan 01 2040 00:00:00 UTC

结论很明确:获取 Unix 时间戳请坚持用Math.floor(dateTime / 1000),不要用~~。这既是原文档的写法,也规避了 32 位截断与 Y2038 风险。

3. 关于性能的理性态度

Date.now()不构造对象、不触发额外抽象操作,理论上开销最小;原文档也将其描述为最简单最快的途径。但"快"的程度取决于具体运行时与调用频次,若在极端性能敏感的循环中大量取时间戳,应以实测(如performance.now()对照基准)为准,而不是盲目迷信某一种写法。

六、实战场景与选型建议

  • 日志与埋点打点:用Date.now(),配合new Date().toISOString()输出人类可读时间。
  • 缓存过期 / 会话有效期:秒级时间戳足够,用Math.floor(Date.now() / 1000),并注意与后端约定的单位保持一致(秒 vs 毫秒是常见事故源)。
  • 接口签名 / 防重放:需要"当前时刻"与"指定时刻"两种能力,分别用Date.now()与new Date('2024-01-01T00:00:00Z').getTime()。
  • 展示层格式化:拿到毫秒值后不要急着转秒,new Date(ms)可以继续用于getFullYear()、getMonth()等日期分解。
  • 跨时区确定性要求高:优先Date.UTC(...)或带Z的 ISO 字符串,避免隐式本地时区解析。

七、小结

获取 Unix 时间戳在 JavaScript 中有多条等价路径,本文围绕 JS Tips 第 49 期文档(英文原版见 _posts/en/javascript/2016-02-26-extract-unix-timestamp-easily.md,另有 西班牙语版 与 繁体中文版)总结出三条准则:

  1. 当前时刻:首选Math.floor(Date.now() / 1000),其次Math.floor(new Date().getTime() / 1000);
  2. 指定日期:字符串用YYYY-MM-DD或显式带Z的YYYY-MM-DDT00:00:00Z,追求绝对确定用Date.UTC();
  3. 底层一致:+new Date()的魔法来自valueOf()与一元+的ToNumber转换,理解原理即可放心使用,但取整务必用Math.floor而非~~,以远离 Y2038 位溢出陷阱。
  • 教程

【免费下载链接】jstips

This is about useful JS tips!

项目地址:https://gitcode.com/gh_mirrors/js/jstips
点击查看免费下载
上一篇:5步掌握gInk:Windows上最轻量的免费屏幕标注工具完整教程
下一篇:如何通过Qwerty Learner快速提升英语打字效率:面向程序员的终极打字练习指南

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询