使用方法
- 在「日期」选日子,范围 1970-01-01 ~ 2100-12-31(示例 2024-03-15)。选 1969 年会被拒绝,因为 Unix 时间戳从 1970-01-01T00:00:00Z 起算。
- 在「时间」选时刻,范围 00:00:00 ~ 23:59:59,按秒递进(示例 08:30:45)。闰秒的 60 秒不设——Unix 时间戳按 POSIX 标准忽略闰秒。
- 在「时区口径」选你输入的时刻属于哪个时区。选 UTC 时日期时间直接当 UTC 算;选 UTC+08:00 时会先减 28800 秒换算成 UTC。
- 点「换算时间戳」。三个控件都是单行输入或下拉,直接按 Enter 就能算。
- 结果区给出 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 有两条路,本页只走第一条:
Date.UTC(y, m-1, d, H, M, S):规范定义它直接返回 UTC 毫秒,完全不看本地时区。注意月份要减 1(Date.UTC的月份是 0 起),这是最常见的低级错误。new Date(y, m-1, d).getTime():这条是按本地时区解析的。实测在 UTC+08:00 机器上,Date.UTC(2024, 2, 15)返回 1710460800000,而new Date(2024, 2, 15).getTime()返回 1710432000000,两者差 28800000 毫秒 = 8 小时。把系统时区改成 UTC,同一行代码就变成 1710460800000。
时区面板的四个档位对应固定偏移分钟数: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(数值仅作演示)。
- 调用
Date.UTC(2024, 2, 15, 8, 30, 45)(月份2表示 3 月) - 毫秒 = 1710491445000,秒 = 毫秒 ÷ 1000 = 1710491445
- ISO 8601 =
2024-03-15T08:30:45.000Z(toISOString()永远输出 UTC 带Z) - 本地时区串(UTC+08:00 机器上)=
2024-03-15 16:30:45,与 UTC 差 8 小时 - 换成 UTC+08:00 口径时,
08:30:45对应的 UTC 是00:30:45,秒数 = 1710491445 − 28800 = 1710462645 - 只取日期不取时间时(2024-03-15 00:00:00 UTC):秒 = 1710460800;按 UTC+08:00 分天则是 1710432000,差 28800
- 2024-03-15 是星期五(2024 能被 4 整除且不能被 100 整除,是闰年,2 月 29 日存在)
换两组验证: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-01 | 0 | 0 | -28800 | 否 |
| 2000-01-01 | 946684800 | 946684800000 | 946656000 | 否 |
| 2010-01-01 | 1262304000 | 1262304000000 | 1262275200 | 否 |
| 2020-02-29 | 1582934400 | 1582934400000 | 1582905600 | 否 |
| 2024-03-15 | 1710460800 | 1710460800000 | 1710432000 | 否 |
| 2024-06-30 | 1719705600 | 1719705600000 | 1719676800 | 否 |
| 2025-01-01 | 1735689600 | 1735689600000 | 1735660800 | 否 |
| 2026-10-03 | 1790985600 | 1790985600000 | 1790956800 | 否 |
| 2033-01-01 | 1988150400 | 1988150400000 | 1988121600 | 否 |
| 2038-01-19 | 2147472000 | 2147472000000 | 2147443200 | 否 |
| 2040-01-01 | 2208988800 | 2208988800000 | 2208960000 | 是 |
| 2100-03-01 | 4107542400 | 4107542400000 | 4107513600 | 是 |
| 1970-01-02 | 86400 | 86400000 | 57600 | 否 |
| 1999-12-31 | 946598400 | 946598400000 | 946569600 | 否 |
| 2001-01-01 | 978307200 | 978307200000 | 978278400 | 否 |
| 2024-02-29 | 1709164800 | 1709164800000 | 1709136000 | 否 |
| 2024-12-31 | 1735603200 | 1735603200000 | 1735574400 | 否 |
| 2025-06-15 | 1749945600 | 1749945600000 | 1749916800 | 否 |
| 2026-02-28 | 1772236800 | 1772236800000 | 1772208000 | 否 |
| 2030-05-20 | 1905465600 | 1905465600000 | 1905436800 | 否 |
| 2037-11-19 | 2142201600 | 2142201600000 | 2142172800 | 否 |
| 2038-01-20 | 2147558400 | 2147558400000 | 2147529600 | 是 |
| 2099-12-31 | 4102358400 | 4102358400000 | 4102329600 | 是 |
| 2100-02-28 | 4107456000 | 4107456000000 | 4107427200 | 是 |
全部 12 行服务端直出,不依赖 JS 执行。默认示例那行标蓝,超 32 位上限的行标黄。
几个容易搞混的对照:
- 同一「2024-03-15」,UTC 口径是 1710460800,UTC+08:00 口径是 1710432000,差 28800。按天分桶时用哪个,取决于你的业务日界线定义在哪个时区。
- 1970-01-01 的 UTC+08:00 秒数是 −28800(负数)。这不是 bug——UTC+08:00 的 1970-01-01 00:00 比 UTC 纪元早 8 小时。
- 2038-01-19 秒 2147472000 未超上限,但同一天 03:14:07 之后就超了;上限值 2147483647 对应 2038-01-19T03:14:07Z。
边界与精度
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 偏移量)。需要这一步请用时区转换器。