时间怎么算?cron 五个字段、时区与日期校验

2026-11-05约 7 分钟开发辅助涉及 3 个工具

时间处理分三个层面:绝对时刻(时间戳)、未来规则(cron)、以及被写进字符串里的日期(身份证号里的生日)。三者的坑不一样,但都会导致「差几个小时」或「日期不存在」。

核心要点
  • cron 的日与周同时被限定时按「或」处理,0 0 1 * 1 表示每月 1 号或每周一,而各家 cron 实现对此有分歧
  • 时间戳是距 1970-01-01 00:00:00 UTC 的秒数,本身没有时区,只有换算成日期时才涉及时区
  • 秒级时间戳塞进 32 位有符号整数会在 2038-01-19 03:14:07 之后溢出成负数
  • 10 位数值是秒、13 位是毫秒,11 位才是真正的歧义区间,会被当成秒也就是约公元 2286 年
  • 身份证号里的生日要排除 2 月 30 日这类不存在的日期,而 15 位老号码没有校验位、只能核格式

#三个层面,三种坑

时间问题之所以难查,是因为「时间」在系统里以三种完全不同的形态存在:一个数字、一条规则、一段字符串。 三者的错误表现完全不同。

形态例子典型错误
绝对时刻1780000000秒当毫秒、时区换算差 8 小时
未来规则0 9 * * 1-5日与周的语义、时区与展示端不同
字符串日期19900230日期根本不存在,但格式合法

#cron:五个字段与「或」的语义

cron 表达式是五段:分、时、日、月、周。日与周同时被限定时,标准 Vixie cron 按「或」处理。 这条最容易记反。

0 0 1 * 1 的意思是「每月 1 号或每周一」,而不是「1 号且周一」。写成「且」的话,一年只会有几次同时满足,看起来像定时任务没生效。

用 Cron 表达式生成 时,几条边界要知道:

  • 只认数字和 *(任意)、/(步进)、-(范围)、,(枚举)四种语法
  • 带字母的写法(MON-FRI)、Quartz 的 ? L W、@daily 这类宏都不被接受 —— 它们本来就不是同一种方言
  • 「下次执行时间」用浏览器本地时区搜索,不是服务器时区
注意

排程跑偏最常见的原因就是时区。工具按你浏览器的时区算,服务器按它自己的时区执行。两边不一致时,这里显示的时间要按差值换算 —— 差 8 小时的话,早上 9 点的任务会在你下午 5 点跑。

另外,0 0 29 2 *(闰日)这类表达式在两年内可能一次都不触发,工具会显示找不到下次执行时间,这不是 bug。

#时间戳:它没有时区

Unix 时间戳是「距 1970-01-01 00:00:00 UTC 的秒数」,它本身不含时区信息。 所以同一串数字在任何国家代表同一个瞬间 —— 时区只在换算成日期时才介入。

用 Unix 时间戳 时,输出会同时给出本地时间与 UTC、北京、东京、伦敦、纽约五地对照。伦敦和纽约会随夏令时自动偏移,所以它们的偏移量标注成 UTC+0/+1 与 UTC-5/-4;北京时间不实行夏令时,恒定 UTC+8。

反向换算取的是浏览器所在时区的时间,没有时区选择器。想算某个 UTC 时刻对应的秒数,需要先把时差折进去:比如把 UTC 12:00 按本地 UTC+8 填成 20:00,否则会差几个小时。

技巧

复制按钮复制的是 13 位毫秒值。需要 10 位秒级时去掉后三位即可 —— 反过来把 10 位当毫秒用,会得到一个 1970 年的日期,这是「时间显示成 1970 年」的唯一原因。

#2038 与 11 位的歧义

把秒级时间戳塞进 32 位有符号整数,上限是 2147483647,对应 2038-01-19 03:14:07 UTC,之后会溢出成负数。 这就是「2038 问题」。

现代实现大多已经改用 64 位或浮点秒,但老系统、嵌入式设备、部分数据库字段仍有这个隐患。检查办法:把秒级值手动加到一个远期日期上,看会不会变负。

秒与毫秒的判定规则也值得记住:

  • 10 位:按秒解释(约 2001 到 2286 年)
  • 13 位:按毫秒解释
  • 11 位:真正的歧义区间,工具按秒处理,也就是约公元 2286 年 —— 遇到 11 位请自己补三位或去掉三位
说明

早期实现用 1e12 做阈值,会把 2000-01-01 的毫秒值 946684800000 解析成公元 31969 年。现在阈值是 1e11(12 位及以上算毫秒),这类误判就消失了。

#日期字符串:为什么会「不存在」

形如 19900230 的日期格式完全合法,但 2 月没有 30 号 —— 这类「合法但不存在」的日期是字符串时间最隐蔽的坑。 范围检查(月 1-12、日 1-31)挡不住它。

用 身份证信息查询 时,生日部分除了范围检查,还会用「按年月日构造日期后回读比对」的方式排除 2 月 30 日这类情况;如果生日晚于今天,结果里也会标注出来。

身份证的时间信息还有两个容易误判的点:

  • 15 位老号码只出现在 1999 年之前,没有校验位、年份只有两位(按 19xx 补全)。所以对它只能核格式与生日是否合理,页面会明确写「无法核验」,不会给出通过结论
  • 生肖按公历年算,而农历春节前出生的人属上一年的生肖,1 月到 2 月中旬的生日需要自行核对
要点

18 位身份证的校验位有确定算法:前 17 位按固定权重加权求和、对 11 取模、再用 10X98765432 映射。但「校验通过」只代表最后一位对得上,不代表号码真实存在 —— 任何人都能按同一套算法造出合法号码。真实核验只有公安或运营商渠道能做。

#什么时候该用哪个工具

你想做的事用哪个关键约束
把秒或毫秒换成日期Unix 时间戳11 位是歧义区间;输出五地时区对照
写定时任务表达式Cron 表达式生成只支持 5 段与四种语法;日周为「或」
核对身份证里的生日身份证信息查询15 位无法核验校验位
验证两个时间字符串是否相同文本差异对比逐字符比较,2026-11-05 与 2026-11-5 不同
生成不重复的事件 IDUUID / GUID 生成v1 含时间戳,v4 纯随机

处理顺序建议:先确定这个时间的语义(绝对时刻 / 规则 / 字符串),再选工具。 三者混着看,就会在时区、闰日、2 月 30 日之间反复打转。

#常见问题

#cron 写的「1 号和周一」为什么不生效?

因为标准语义是「或」:0 0 1 * 1 会在每个 1 号以及每个周一都执行。想表达「且」(既是 1 号又是周一),标准 cron 做不到,需要在脚本里自己判断日期。注意各家实现有分歧,写运维脚本前先确认目标机的语义。

#时间戳转出来的时间差 8 小时?

因为反向换算用的是浏览器本地时区。要得到某个 UTC 时刻对应的秒数,先把时差折进去;要显示某个服务器时区的时间,先在脑子里做一次偏移。工具没有时区选择器,这是有意的简化。

#时间显示成 1970 年?

说明把秒当毫秒了。10 位秒级值乘 1000 才是毫秒;直接当毫秒用就是 1970 年 1 月。反过来 13 位当秒用会得到一个几十万年后的日期。

#2038 年之后的时间戳还能用吗?

64 位系统上没问题。有风险的是把时间戳存进 32 位有符号整数的场景:数据库 INT 字段、嵌入式设备、老的语言运行时。检查办法是看那个字段的类型是不是 INT(而不是 BIGINT)。

#身份证校验通过就说明号码是真的吗?

不是。校验位只是一次算术校验,任何人都能按同样的算法造出通过校验的号码。它只能发现「输错一位」这类错误,不能证明号码存在。真实核验需要通过公安或运营商渠道。

本文提到的工具

相关文章