无需上传, 100% 本地处理, 无需账户

文章

Unix 时间戳与 cron 表达式详解

Unix 时间戳不过是一个整数。cron 表达式不过是五个字段。两者在后端工作中频繁出现,也以可预见的方式让人踩坑:前者因时区而起,后者因单位而起。

什么是 Unix 时间戳

Unix 时间戳计算自 1970-01-01 00:00:00 UTC(即 Unix 纪元这一参考点)以来经过的秒数。这个定义有两个重要推论。第一,计数以 UTC 为基准:数字本身不包含任何时区信息。1718400000 在地球上任何地方都表示同一个瞬间,无论存储它的机器位于何处。第二,纪元以秒定义,不以其他单位。1718400000 这样的数值始终是自 1970 年 1 月 1 日 UTC 午夜起的秒数,除非某个系统或协议明确做出了不同的选择。时间戳在设计上不含时区,这正是它在日志记录、数据库和跨系统通信中有用的原因,因为不同地区的时钟必须明确地指向同一时刻。

时间轴显示左端为 Unix 纪元 1970-01-01 00:00:00 UTC,中间为当前时间戳,右端为标注有 32 位有符号整数最大值的 2038 年溢出边界。

秒与毫秒,以及 2038 年问题

许多现代运行时和 API 使用毫秒而非秒。JavaScript 的 Date.now() 返回毫秒,所以 1718400000000 与秒精度值 1718400000 是同一个瞬间。当你收到一个不熟悉的时间戳时,数量级能告诉你单位:10 位数字几乎总是秒,13 位数字几乎总是毫秒。2038 年问题源于另一种选择:将秒精度时间戳存储在有符号 32 位整数中。有符号 32 位整数的最大值为 2147483647,对应 2038-01-19 03:14:07 UTC。那一秒之后,计数器溢出为一个大负数,在不处理该边界的系统上,日期会回滚到 1901 年。修复方法是使用 64 位存储。大多数当前操作系统和数据库已经如此,但几十年前固定了字段宽度的嵌入式系统、网络设备和旧文件格式仍面临风险。

将纪元值转换为人类可读日期:时区步骤

将纪元值转换为可读日期需要两步:从整数中提取 UTC 日期组件,然后应用 UTC 偏移量得到本地时间。时间戳本身是 UTC 的。当你问 1718325000 代表什么日期时,正确答案是 UTC 下的 2024-06-14 00:30。在纽约(夏季 UTC-4),同一秒是前一天晚上,2024-06-13 20:30:变化的不只是小时,日期本身也变了。这个被跳过的步骤正是产生错误结果的原因:开发者以 UTC 记录原始时间戳,报表工具未经转换就以本地时间渲染,日期看起来偏差了数小时或整整一天。Sunasty 上的时间戳转换工具明确处理这一点:你输入纪元值,它会把 UTC 日期时间和你设备自身的本地日期时间并排显示,因此像这样跨越日期边界的情况一眼就能看清。转换完全在浏览器中运行,你粘贴的内容不会发送到任何地方。

同一个纪元值的两个日历网格并排显示:UTC 网格高亮显示某一天,纽约(UTC-4)网格高亮显示前一天,展示了本地日期因时差而向前推移一天的情况。

Cron 语法:五个字段、范围、步长和列表

cron 表达式用于在类 Unix 系统上调度重复任务。标准语法有五个以空格分隔的字段:分钟 (0-59)、小时 (0-23)、日期 (1-31)、月份 (1-12)、星期 (0-7,其中 0 和 7 都表示星期日)。字段中的星号 (*) 表示「每个有效值」。表达式 30 8 * * 1-5 表示在周一至周五的 08:30 运行。范围使用连字符:1-5 包含值 1、2、3、4、5。列表使用逗号:1,15 表示 1 日和 15 日。步长使用斜杠:分钟字段中的 */15 表示每 15 分钟(0、15、30、45)。这些组合符可以一起使用:0-30/10 表示 0、10、20、30。cron 没有时区字段。守护进程读取所运行机器的时钟,该时钟设置为服务器的本地时区。如果服务器使用 UTC 而开发者期望本地时间,任务会在错误的时钟小时触发。实际规则是将 cron 计划视为服务器本地时间表达式,并在 cron 行旁边记录服务器的时区。

在本地使用时间戳转换工具

Sunasty 上的时间戳转换工具支持双向转换:纪元值转人类可读日期,以及人类可读日期转回纪元值。你可以输入秒精度或毫秒精度的数值;工具根据数量级自动判断单位。它会把 UTC 结果和你设备自身所在时区的本地结果并排显示,并附上可直接复制的秒值和毫秒值。一个「现在」按钮可以一键填入当前时间戳,方便你在不打开终端的情况下快速查询当前的 Unix 时间。这里没有单独的时区选择器:本地列始终反映浏览器当前设置的时区。所有内容都在浏览器中运行。你粘贴到字段中的值由客户端 JavaScript 读取,永远不会离开设备。

本文涉及的工具

常见问题

为什么 JavaScript 使用毫秒而非秒?

JavaScript 的 Date 对象从一开始就被设计为支持亚秒级精度,毫秒在不需要小数点的情况下提供了秒的一千倍分辨率。代价是数值更大,这就是为什么 Date.now() 产生 13 位数字,而大多数服务端时间戳只有 10 位。

所有运行 cron 的服务器都需要设置为 UTC 吗?

不需要,但将服务器设置为 UTC 是一种常见实践,正是因为这样可以消除歧义。当服务器在 UTC 上运行时,像 0 2 * * * 这样的 cron 表达式表示 UTC 02:00,该时间全年稳定。在实行夏令时的时区上,同一任务在夏季和冬季触发的 UTC 瞬间不同,并可能在时钟变化边界处触发两次或跳过一次。

存储 2038 年之后时间戳的安全类型是什么?

有符号 64 位整数可以存储精确到秒的时间戳,最高可到 292277026596 年,远超任何实际需求。大多数当前数据库默认将时间戳存储为 64 位值。如果你正在使用使用 32 位字段的遗留 schema 或二进制文件格式,在 2038 年边界之前迁移到 64 位存储是正确的解决方案。