日期转时间戳

选日期、选时间、选时区,用 Date.UTC(y, m-1, d, H, M, S) 显式构造出 Unix 时间戳,输出秒、毫秒、ISO 8601 串与本地时区串四格。全部口径写死在页面上,数字仅作演示。

2024-03-15 08:30:45 UTC → 秒 1710491445,毫秒 1710491445000。

Unix 秒(10 位)1710491445
Unix 毫秒(13 位)1710491445000
ISO 8601(UTC)2024-03-15T08:30:45.000Z
本地时区串(你所在时区)2024-03-15 16:30:45
该时刻在 UTC+08:00 的秒数1710462645
32 位上限 2³¹−1 = 2147483647未超上限
你所在时区UTC+08:00
该日期的星期星期五
本页主结果一律按 UTC 口径计算,与你浏览器的时区无关;结果区第 5 格单独给出 UTC+08:00 的秒数,方便和国内接口对接时对照。之所以不直接用 new Date(y, m-1, d),是因为它按本地时区解析——同一份 HTML 在 UTC 服务器与 UTC+8 访客机器上会给出差 8 小时的两个答案。

使用方法

  1. 在「日期」选日子,范围 1970-01-01 ~ 2100-12-31(示例 2024-03-15)。选 1969 年会被拒绝,因为 Unix 时间戳从 1970-01-01T00:00:00Z 起算。
  2. 在「时间」选时刻,范围 00:00:00 ~ 23:59:59,按秒递进(示例 08:30:45)。闰秒的 60 秒不设——Unix 时间戳按 POSIX 标准忽略闰秒。
  3. 在「时区口径」选你输入的时刻属于哪个时区。选 UTC 时日期时间直接当 UTC 算;选 UTC+08:00 时会先减 28800 秒换算成 UTC。
  4. 点「换算时间戳」。三个控件都是单行输入或下拉,直接按 Enter 就能算。
  5. 结果区给出 8 格,其中「Unix 秒」是主结果。点「复制结果」带走四行文本,点「导出 CSV」拿 12 行日期对照表。

这个日期转时间戳能解决什么问题

一是接口联调时对不上数。后端要 timestamp、前端给 Date.now()、数据库存 datetime——三者混用时最容易出的错就是时区。典型症状是「同一条记录,页面显示的时间比数据库早 8 小时」,根源就是有人用了 new Date('2024-03-15'):这个写法按本地时区解析,而 Date.parse('2024-03-15T00:00:00Z')(带 Z)才是 UTC。

二是秒和毫秒搞混。Java 的 System.currentTimeMillis() 与 JS 的 Date.now() 给毫秒,PHP 的 time()、MySQL 的 UNIX_TIMESTAMP() 给秒。传错时接口会返回 1970 年附近的日期,或者干脆超出范围。13 位基本都是毫秒、10 位基本都是秒,本页对两种都做判定并把两个值同时给出。

三是算「某天 00:00 的时间戳」做按天分桶。日报、签到、账期这类逻辑都需要日界线。本页把日期固定解释为 UTC 后给你秒数(2024-03-15 是 1710460800),但要按中国时区分天,得用 1710432000(= 1710460800 − 28800)——两者差的就是那 8 小时。这一列对照表里每行都给了。

四是排查 2038 问题。用 32 位有符号整数存时间戳的系统(大量老旧嵌入式设备、部分数据库列)在 2038-01-19T03:14:07Z 之后会溢出回绕。本页在秒数超过 2147483647 时会把结果格标黄并写明回绕后的日期,让你提前发现这类隐患。

本页不做这些事:不做时间戳反查(那是时间戳转换工具的事)、不做时区、夏令时与夏令时切换的完整 tzdata 推算(本页面板只给四档固定偏移,不含历史时区变更)、不做闰秒补偿。要做完整时区换算请用时区转换器;要判断某年是否闰年请用闰年判断工具。

原理和变量

Unix 时间戳的定义是:从 UTC 1970-01-01T00:00:00Z 起经过的秒数,闰秒按 POSIX 标准被忽略。所以 new Date(0).toISOString() 必然等于 1970-01-01T00:00:00.000Z——这不是巧合,是定义。

关键在于怎么把「2024-03-15 08:30:45」变成一个毫秒数。JavaScript 有两条路,本页只走第一条:

时区面板的四个档位对应固定偏移分钟数:0(UTC)、+480(UTC+08:00 = 28800 秒)、−300(UTC−05:00)、−420(UTC−07:00)。选非零档时,计算式是 Date.UTC(...) − 偏移分钟 × 60000——因为「UTC+08:00 的 08:30」对应的 UTC 时刻是 00:30。

输出为什么固定四格?因为往返一致性是检验口径是否自洽的硬判据:把 ISO 8601 串再解析回毫秒,必须得到完全相同的值。本页的「反解校验」输入框就是干这个的——Date.UTC 路线在 t −60 ~ 60 °C… 更正:在 1970 ~ 2100 年范围内的任意合法输入,往返偏差都是 0 毫秒(双精度浮点逐位相同)。而如果换成本地时区串,往返就会差 8 小时——这正是「为什么必须全程用 UTC 串」的直接证据。

秒与毫秒的判定规则是:13 位通常是毫秒、10 位通常是秒。经验分界是 1e9:1e9 秒 = 2001-09-09,而 1e9 毫秒 = 1970-01-12。但要注意单看数量级并不保险——实测 1e11 毫秒是 1973-03-03(合法),1e11 秒是 5138-11-16(也在 Date 范围内)。所以稳妥做法是「两种都算,看哪个落在 2001~2099 的业务窗口内」。

完整示例

默认这组:2024-03-15 08:30:45、时区 UTC(数值仅作演示)。

换两组验证:1970-01-01 的秒是 0(纪元本身);2038-01-19 的秒是 2147472000,仍未超 32 位上限(2040-01-01 才是 2208988800,已超)。下面表格里这四行可以逐位对照。

输入范围与对照表

三个控件的约束:日期 min="1970-01-01" max="2100-12-31" step="1",时间 min="00:00:00" max="23:59:59" step="1",时区四档固定偏移。日期格式由 <input type="date"> 保证为 YYYY-MM-DD,但 JS 侧仍会做一次回读校验——因为 Date.UTC 对不存在的日期会「顺延」而不是报错。

日期秒(UTC 00:00)毫秒秒(UTC+08:00)超 32 位上限
1970-01-0100-28800否
2000-01-01946684800946684800000946656000否
2010-01-01126230400012623040000001262275200否
2020-02-29158293440015829344000001582905600否
2024-03-15171046080017104608000001710432000否
2024-06-30171970560017197056000001719676800否
2025-01-01173568960017356896000001735660800否
2026-10-03179098560017909856000001790956800否
2033-01-01198815040019881504000001988121600否
2038-01-19214747200021474720000002147443200否
2040-01-01220898880022089888000002208960000是
2100-03-01410754240041075424000004107513600是
1970-01-02864008640000057600否
1999-12-31946598400946598400000946569600否
2001-01-01978307200978307200000978278400否
2024-02-29170916480017091648000001709136000否
2024-12-31173560320017356032000001735574400否
2025-06-15174994560017499456000001749916800否
2026-02-28177223680017722368000001772208000否
2030-05-20190546560019054656000001905436800否
2037-11-19214220160021422016000002142172800否
2038-01-20214755840021475584000002147529600是
2099-12-31410235840041023584000004102329600是
2100-02-28410745600041074560000004107427200是

全部 12 行服务端直出,不依赖 JS 执行。默认示例那行标蓝,超 32 位上限的行标黄。

几个容易搞混的对照:

边界与精度

1. Date 能表示的毫秒范围是 ±8,640,000,000,000,000(±8.64e15)。这等于 ±100,000,000 天,约 ±273,790 年。new Date(8.64e15) 还能用,new Date(8.64e15 + 1) 就是 Invalid Date,此时调 toISOString() 会抛 RangeError 让整页 JS 崩掉。所以本页在格式化前一律先判 Number.isFinite(d.getTime())。顺带说明:Number.MAX_SAFE_INTEGER 是 9007199254740991,但 new Date(9007199254740991) 本身就是 Invalid Date——安全整数上限并不在 Date 的有效范围内。

2. 闰秒不存在于时间戳里。POSIX 标准规定 Unix 时间戳忽略闰秒,所以 2016-12-31 23:59:60 UTC 不对应任何整数秒。历史上共发生过 27 次闰秒(1972-06-30 到 2016-12-31),累计 27 秒——这就是 NTP 与 Unix 系统之间的真实偏差量级。任何声称「精确到闱秒」的时间戳转换都不成立,因为时间戳本身没有这一位。

3. Date.UTC 对不存在的日期会顺延,必须回读校验。实测:Date.UTC(2024, 1, 30)(2 月 30 日)被顺延成 2024-03-01,Date.UTC(2023, 1, 29)(平年 2 月 29 日)顺延成 2023-03-01,Date.UTC(2024, 12, 1)(13 月)顺延成 2025-01-01,Date.UTC(2024, -1, 10)(0 月)顺延成 2023-12-10。不报错是最大的坑——所以判合法的正确做法是「Date.UTC 之后用 getUTCFullYear / getUTCMonth / getUTCDate 读回来比一遍」。

4. 闰年规则会影响 2 月 29 日。格里高利历规则是「能被 4 整除,且(不能被 100 整除,或能被 400 整除)」。实测:1900 平年、2000 闰年、2024 闰年、2023 平年、2100 平年、2400 闰年。所以 2100-02-29 是非法日期,2024-02-29 合法。日期输入框用浏览器原生控件时会自动屏蔽非法日期,但如果你要用代码批量生成,仍要自己判。

5. 2038 年上限的准确数值。2^31 − 1 = 2147483647 秒,对应 2038-01-19T03:14:07Z(UTC+08:00 机器上是 2038-01-19 11:14:07)。溢出一个单位即 2147483648 秒,32 位有符号会绕回 −2147483648,对应 1901-12-13T20:45:52Z——这就是「时间突然变成 1901 年」的成因。从 2026-01-01 算起还有 4401 天(约 12.05 年)。

6. 精度与显示位数。毫秒时间戳在 2^53 = 9007199254740992 之内都是精确整数,而有效毫秒上限是 8.64e15 < 2^53,所以整个 Date 有效范围内的毫秒值都是精确的,没有浮点误差。秒值是毫秒除 10000 得到的「整秒」时才恰好等于原始毫秒的 1/1000——本页只在秒本身不带毫秒余量时才输出,否则会在括号里注明。这是 Math.floor(ms / 1000) 与 Math.round(ms / 1000) 的区别所在。

常见问题

同一个日期,同一份代码,为什么服务器算出来和我的浏览器差 8 小时?

因为有人用了 new Date('2024-03-15') 或 new Date(2024, 2, 15)。这两个写法都按本地时区解析:在 UTC+08:00 的机器上它们代表 UTC 的前一天 16:00,与 Date.UTC(2024, 2, 15) 差 28800000 毫秒。服务器通常是 UTC,访客机器是 UTC+8,同一份代码自然给出两个答案。改成 Date.UTC() 或给字符串加 Z 后缀就能固定下来。

时间戳多了几位我就乘 1000,少了几位我就除 1000,为什么还是不对?

因为单位判断不能只看位数。实测 1710491445 这个数:当秒是 2024-03-15 08:30:45(正确),当毫秒是 1970-01-20 19:08:11.445(差了两千多万天)。而 1e11 这个数量级,当毫秒是 1973 年(合法),当秒是 5138 年(也在 Date 范围内)。所以稳妥做法是两种解释都算一遍,看哪个落在 2001~2099 这个业务窗口里;位数只是经验提示,不是判据。

本地时区串和 ISO 8601 串,选哪个存进数据库?

存 ISO 8601 带 Z(如 2024-03-15T08:30:45.000Z)或存 Unix 秒/毫秒整数,不要存本地时区串。原因在往返一致性:ISO 串解析回去必然得到同一个毫秒值,而本地串(实测 2024-03-15 16:30:45)按 UTC 解析会差 28800000 毫秒。如果一定要存可读时间,加一个独立的时区字段,并在查询时统一按 UTC 解释。

为什么我把日期设成 2024-02-30 也能算出结果?

因为 Date.UTC 对不存在的日期顺延而不报错:2024-02-30 被当成 3 月 1 日,2023-02-29(平年)被当成 3 月 1 日,2024-13-01 被当成 2025-01-01,2024-00-10 被当成 2023-12-10。这是 JavaScript 日期 API 的历史行为。本页的做法是算完之后把 getUTCFullYear / getUTCMonth / getUTCDate 读回来跟输入比一遍,不一致就报「这个日期不存在」并指出正确范围。

2100 年闰年吗?为什么我的代码在那一年 2 月 29 日出问题了?

不是闰年。格里高利历规则要求「被 4 整除,且不能被 100 整除,或者能被 400 整除」。2100 能被 100 整除但不能被 400 整除,所以是平年,2100-02-29 不存在。对照:1900 也是平年,2000 和 2400 因为能被 400 整除而是闰年。这是每 400 年闰 97 天的规则导致的,不是简单的「被 4 整除」。很多简单的闰年判断只写了 % 4 === 0,在 2100 这类年份上就会出错。

这个 2038 年问题真的会影响我吗?

取决于你的存储与传输链路。JavaScript 的 Date 用双精度浮点,不受影响;Java 8 之后的 long 类型也不受影响。真正受影响的是:32 位有符号整数存时间戳(C 的 time_t、老式数据库 INT 列、部分嵌入式设备与路由器固件)。判断方法很直接:看你的字段类型是 4 字节还是 8 字节。本页在秒数超过 2147483647 时把结果格标黄,就是为了让这类隐患在联调阶段暴露,而不是等到 2038 年。

秒和毫秒两个值都要输出,实际用哪个会不会搞混?

不会,本页把两个值并排显示并标清位数:秒是 10 位、毫秒是 13 位。实践中 10 位到 11 位基本都是秒,12 位到 14 位基本都是毫秒;边界情况(比如 Unix 早期的负数时间戳,或 2286 年之后的秒数)会超出这个习惯,所以判断时要看「转成日期后是否落在业务区间」,不要只看位数。另外提醒一句:带小数部分的毫秒(1710491445123)除 1000 会得到小数,转成秒时要么截断要么四舍五入,必须和后端约定一致,否则会出现 1 秒的偏差。

夏令时切换那天的时间戳怎么算?

本页不算。原因是夏令时依赖完整的 IANA tzdata(含历史变更),浏览器的 getTimezoneOffset() 只反映当前偏移,不会告诉你某个历史日期是否处于夏令时。要精确处理历史时区变更,必须把「本地时间 + IANA 时区名(如 Asia/Shanghai、America/New_York)」交给 tzdata 解析,或直接用带时区信息的 ISO 8601 串(带 Z 或 +08:00 偏移量)。需要这一步请用时区转换器。

相关工具