ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

时间戳与yyyy-MM-dd格式互转全解析:精度、时区与跨语言防坑指南

时间戳与yyyy-MM-dd格式互转全解析:精度、时区与跨语言防坑指南 凌晨三点我被值班电话叫醒。电话那头说今天凌晨所有订单的时间都显示成了1970-01-01 08:00:00。我打开日志一看接口返回的时间戳明明是 17 位数字代码里却按 10 位秒级时间戳去解析——毫秒被当成秒整个系统的时间线直接塌了。这是我处理过最典型的一类时间问题也是我今天想彻底讲透的主题yyyy-MM-dd HH:mm:ss这种时间格式和时间戳到底是什么关系为什么所有语言都在做这两者的互转以及这些转换里到底藏着多少坑。这篇文章我会从时间戳的起源讲起把秒、毫秒、微秒、纳秒的精度陷阱yyyy-MM-dd HH:mm:ss格式串里那些字母的暗语Java、JavaScript、Python、Excel、SQL 这些常用环境的互转实操还有时区导致的各种幺蛾子一次性拆干净。顺带把 MobaXterm、SecureCRT 日志和 Windows 事件查看器里那些非典型时间戳也一起讲掉。后端、前端、运维还是数据分析只要你的工作跟时间打交道这篇都值得收藏。1. 1970年1月1日这个起点以及它为什么成了默认坐标1.1 Unix时间戳的定义与历史时间戳Timestamp这个词在不同语境下含义不太一样但在绝大多数开发场景里说到时间戳默认指的就是 Unix 时间戳从 1970 年 1 月 1 日 00:00:00 UTC协调世界时到目标时刻经过的总秒数。为什么偏偏是 1970 年因为 Unix 操作系统在上世纪 70 年代初诞生C 语言标准库里的time_t类型选择了这个时间作为起点后来的 POSIX 标准延续了这个约定。于是 1970-01-01 00:00:00 UTC 就成了整个计算机世界的宇宙起点。1970 年之前的日期用时间戳表示就是负数比如1969-12-31 23:59:59 UTC对应-1这个边界情况很多人没注意过后面会提到。常见误区是拿时间戳位数倒推年份。时间戳是秒数它是对时长的计量不是对日期的编码。2024 年 1 月 1 日 00:00:00 UTC 的秒级时间戳是1704067200因为从 1970 年到 2024 年大约经过了 54 年折合约 17 亿秒所以现在的秒级时间戳都是 10 位数。但这个位数会随着时间增长变化——2286 年它才会变成 11 位所以靠位数判断精度其实是有一个隐含前提的。1.2 字符串时间满天飞为什么还要用时间戳你可能会问我直接用yyyy-MM-dd HH:mm:ss这种字符串不行吗人类看着多直观。真不行至少不能作为系统间传递时间的唯一方式。我给三个最核心的理由第一字符串比较大小是灾难。2024-01-02 08:00:00和2024-01-02 8:00:00这两个字符串如果直接做字典序比较结果取决于有没有前导零跨时区更麻烦2024-01-02 08:00:00到底是北京时间的早上八点还是 UTC 的早上八点没有时区后缀就是一坨混沌。第二时间戳是纯数值所有运算都是整数加减法。两个时间戳相减得到秒数差直接除以 86400 得到天数差计算机处理起来又准又快。字符串要算差值得先解析、再转成某种内部表示、再计算每一步都有出错空间。第三时间戳天然自带时区属性。它表示的是绝对时刻跟你在哪个时区看它没有关系。同一个时间戳在北京显示成2024-01-01 08:00:00在纽约显示成2023-12-31 19:00:00但它在任何地方都是同一个瞬间。这正是时间戳最值钱的地方——分清了时刻本身和某个时区下的墙上时间这两个概念时间问题就解决了一大半。2. 先别急着写转换你要分清秒、毫秒、微秒、纳秒2.1 位数一眼识精度很多人踩时间戳的坑不是不会写转换代码而是根本没搞清自己手里这个时间戳是什么精度。常见的 Unix 时间戳精度有四档精度有效数字位数截至2024年换算关系典型来源秒10 位1704067200JavaSystem.currentTimeMillis()/1000、PHPtime()毫秒13 位1704067200000JavaSystem.currentTimeMillis()、JavaScriptDate.now()微秒16 位1704067200000000Gotime.Now().UnixMicro()、Pythondatetime.now().timestamp()的部分场景纳秒19 位1704067200000000000JavaSystem.nanoTime()、Gotime.Now().UnixNano()判断方法很简单当前时间附近的时间戳10 位是秒、13 位是毫秒、16 位是微秒、19 位是纳秒。不要死记位数记住用当前时间戳除以对应数量级得到大约 17 亿这个校验逻辑就行。Java 和 Go 里有不少时间对象转时间戳的 API 返回的是纳秒级别数值但数据库字段或者接口协议可能只支持到秒。如果你不做截断直接落库轻则效率问题重则直接超范围报错。反过来从接口拿到一个毫秒级时间戳你用new Date(seconds)去解析会得到 1970 年 1 月某天的诡异时间——文章开头那个事故就是这么来的。2.2 精度错位的事故现场我印象最深的一次事故是排查一个订单完成时间比实际早 45 年的线上问题。后端服务用 Java 存了毫秒时间戳到数据库数据分析同事用 Python 拉数脚本里写的是datetime.fromtimestamp(df[create_time])。这个create_time是 13 位毫秒值但fromtimestamp默认按秒解析于是所有时间都除以了 1000全变成了 1970 年。这种问题最阴险的地方在于不是每次都报错只是数据偏移了。如果时间是刚好凑整的比如1690000000000错误解析出来的1690000000秒看起来也是一个合法时间 2023 年你根本不会发现异常直到数据对不上账才反应过来。所以我给自己定了一条死规矩任何接口、任何表结构凡是传时间戳的字段字段名或者文档里必须写清楚精度单位。比如create_time_ms、update_time_us或者统一在接口文档里约定本系统所有时间戳均为毫秒级除非字段名另有说明。团队里一旦有人破坏这个约定review 时必须拦下来。别嫌啰嗦时间戳精度错乱造成的排查成本远超这点命名成本。还有一类精度问题是溢出。32 位整数能表示的最大秒级时间戳是2147483647对应 2038 年 1 月 19 日。老系统如果还在用 32 位int存时间戳到那天会全部溢出错乱这就是著名的2038 年问题。现在新写的代码存时间戳一律用 64 位整数或字符串别给后人留雷。3. 格式串里的字母都是暗语yyyy、YYYY、HH、hh 的差异3.1 大小写不同含义天差地别yyyy-MM-dd HH:mm:ss不是一串随便写的字母每个字母在格式化规则里都有特定含义。最坑的就是大小写敏感Java 的SimpleDateFormat、Python 的strftime、MySQL 的日期函数看起来差不多实际含义有微妙差异。先说yyyyvsYYYY这是跨年 bug 的重灾区。小写yyyy是日历年份我们平时理解的自然年大写YYYY是周年Week Year它表示当前日期所在的 ISO 周所属的年份。一个日期是 2024 年 12 月 31 日从日历年份看是 2024 年但从 ISO 周的角度看它可能属于 2025 年的第 1 周所以YYYY-MM-dd格式化出来是2025-12-31——没错日期字符串显示的是明年的年份。这个问题每年 12 月 31 日前后在各大技术社区准时出现全是格式化串写错大小写导致的。再说MMvsmm。大写MM是月份小写mm是分钟写反了就会得到12:34 月这种乱七八糟的结果。HH是 24 小时制hh是 12 小时制HH:mm是 23:05hh:mm是 11:05可能带 AM/PM。还有SSS是毫秒ss是秒SS在某些库里还代表秒的小数位非常容易看走眼。更要命的是各语言不太一样Java 里YYYY表示周周年份但 Python 的strftime里%Y是四位年份%G才是 ISO 周周年份MySQL 的%Y是四位年份%m是月份%i才是分钟。同一套字母记忆不能直接跨语言复用换环境前必须查文档。3.2 一套能直接抄的格式模板与其死记每个字母不如记几套标准答案按需抄目标格式Java (DateTimeFormatter)Python (strftime)MySQL (DATE_FORMAT)年月日时分秒yyyy-MM-dd HH:mm:ss%Y-%m-%d %H:%M:%S%Y-%m-%d %H:%i:%s带毫秒yyyy-MM-dd HH:mm:ss.SSS%Y-%m-%d %H:%M:%S.%f%Y-%m-%d %H:%i:%s.%fISO 8601 基本格式yyyy-MM-ddTHH:mm:ssXXX%Y-%m-%dT%H:%M:%S%z需配合CONVERT_TZ纯日期yyyy-MM-dd%Y-%m-%d%Y-%m-%d格式化串里的固定字符比如中间的分隔符-、:、字母T在 Java 里需要用单引号包起来否则会被当成格式化指令Python 的strftime对普通字符相对宽容但为了可移植性我还是建议把非格式化字符原样保留、必要时转义。格式串尽量统一走 ISO 8601 风格日期用yyyy-MM-dd日期时间和 UTC 用yyyy-MM-ddTHH:mm:ssXXX也就是2024-01-01T08:00:0008:00这种。这个格式可读性够好解析库支持最全跨语言传参不容易出歧义。项目里没有特殊要求就别自定义yyyy/MM/dd HH-mm-ss这种花活让每个解析方都少一点猜谜成本。4. Java、JavaScript、Python、Excel、SQL 的互转实例4.1 Java 生态Date 老 API 和 java.time 新 APIJava 这块必须分两层讲老项目还在大量使用的java.util.DateSimpleDateFormat以及 Java 8 以后的java.time包。老 API 的互转就三行// 1. 时间戳(毫秒) - 时间字符串 long millis System.currentTimeMillis(); SimpleDateFormat sdf new SimpleDateFormat(yyyy-MM-dd HH:mm:ss); String timeStr sdf.format(new Date(millis)); // 2. 时间字符串 - 时间戳(毫秒) Date date sdf.parse(2024-01-01 12:00:00); long timestamp date.getTime();注意SimpleDateFormat是线程不安全的别把它定义成static常量在多线程环境下共用。高并发服务里这个坑特别隐蔽多个线程同时parse可能出现时间错乱甚至抛NumberFormatException。老项目里我见过太多这种写法了安全做法是用ThreadLocalSimpleDateFormat包一层或者干脆换新 API。新 API 的核心是Instant和LocalDateTime的分工。Instant表示的是时刻和时区无关转时间戳直接用// 当前时刻 - 毫秒时间戳 long millis Instant.now().toEpochMilli(); // 秒级时间戳 - Instant Instant instant Instant.ofEpochSecond(1704067200L); // Instant - 指定时区的本地时间 LocalDateTime beijingTime instant.atZone(ZoneId.of(Asia/Shanghai)).toLocalDateTime();LocalDateTime本身没有时区概念它是墙上时间所以要帮它补一个时区再转时间戳// 本地时间字符串 - 时间戳 LocalDateTime ldt LocalDateTime.parse(2024-01-01T12:00:00, DateTimeFormatter.ofPattern(yyyy-MM-ddTHH:mm:ss)); long ts ldt.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();DateTimeFormatter是线程安全的可以放心全局复用。项目里如果是新代码一律用java.time老代码改造时再考虑兼容问题。4.2 JavaScript 的时间戳转换前端的时间戳几乎都是毫秒级因为Date对象内部就是按毫秒存储的。核心操作就这几个// 当前时间戳(毫秒) const now Date.now(); // 秒级时间戳 const nowSec Math.floor(Date.now() / 1000); // 时间戳 - Date 对象注意是毫秒 const date new Date(1704067200000); // Date - ISO 字符串UTC 时区 date.toISOString(); // 2024-01-01T00:00:00.000Z // Date - 本地时间各部分 date.getFullYear(); // 2024本地时区 date.getMonth() 1; // 记得 1月份从 0 开始 date.getDate(); // 1前端最容易踩的坑是getMonth()从 0 开始计月份十二月才是11不加一就全错位到上个月去了。另一个坑是new Date(2024-01-01 12:00:00)这种字符串在不同浏览器里的解析行为不一致iOS 上的 Safari 尤其容易返回Invalid Date。可靠的解析方式是手动拆字符串或用Date.UTC精确构造// 手动拼一个本地时间的日期对象 const [y, M, d, h, m, s] 2024-01-01 12:00:00.split(/\D/).map(Number); const date new Date(y, M - 1, d, h, m, s); const ts date.getTime();如果你要输出yyyy-MM-dd HH:mm:ss格式原生 JavaScript 没有现成 API自己拼一个函数也不难或者直接用 Day.js、date-fns 这类库它们对本地化和时区处理已经做得很完善不用重复造轮子。4.3 Python 的三行代码与两处细节Python 的标准库datetime就够用别动不动上pandas。最基本的三组互换import time from datetime import datetime, timezone, timedelta # 1. 当前时间戳(秒浮点数) ts time.time() # 2. 时间戳 - datetime本地时区 dt datetime.fromtimestamp(ts) # 时间戳 - datetimeUTC 时区 dt_utc datetime.fromtimestamp(ts, tztimezone.utc) # 3. datetime - 格式化字符串 s dt.strftime(%Y-%m-%d %H:%M:%S) # 字符串 - datetime dt2 datetime.strptime(2024-01-01 12:00:00, %Y-%m-%d %H:%M:%S) # datetime - 时间戳 ts2 dt2.timestamp()第一个细节datetime.fromtimestamp(ts)返回的是本地时区的墙上时间如果你的服务器时区设置是 UTC而你在本地 PC 上跑同一个脚本结果会差 8 小时。要避免这种环境差异要么显式传tztimezone.utc要么先统一服务器时区。第二个细节datetime.strptime解析带毫秒的字符串时%f接收的是微秒6 位但很多日志给的是 3 位毫秒%f也能解析只是会自动把 3 位补成 6 位这是符合文档的。真正要注意的是 Python 3.12 开始不推荐datetime.utcfromtimestamp()统一用datetime.fromtimestamp(ts, tztimezone.utc)替代老代码尽快迁移。4.4 Excel 和数据库的公式级转换Excel 内部存储时间用的是日期序列号1900-01-01 是 1每过一天加 1时间用小数表示。Excel 的yyyy-MM-dd HH:mm:ss只是显示格式底层是序列号。Unix 时间戳和 Excel 序列号之间的换算关键数字是25569——1970-01-01 对应的 Excel 序列号。假设 A1 是秒级时间戳比如1704067200在中国时区UTC8想显示成本地时间公式是A1/86400 25569 8/24然后把单元格格式设置成yyyy-MM-dd HH:mm:ss。如果是毫秒级时间戳先除以 1000(A1/1000)/86400 25569 8/24。反向换算如果 A2 是一个 Excel 日期时间序列号本地时间UTC8转秒级时间戳(A2 - 25569 - 8/24) * 86400如果 A2 是字符串2024-01-01 12:00:00先用DATEVALUE和TIMEVALUE把它变成序列号(DATEVALUE(LEFT(A2,10)) TIMEVALUE(RIGHT(A2,8)) - 25569 - 8/24) * 86400数据库这边MySQL 最常用的是FROM_UNIXTIME和UNIX_TIMESTAMP-- 时间戳 - 时间字符串结果受会话时区影响 SELECT FROM_UNIXTIME(1704067200); -- 时间字符串 - 时间戳 SELECT UNIX_TIMESTAMP(2024-01-01 12:00:00);这两个函数的结果跟会话时区强相关。MySQL 的连接串里建议显式指定serverTimezoneAsia/Shanghai或serverTimezoneUTC不然 JDBC 驱动和数据库的时区不一致查出来的时间差 8 小时或 13 小时都很常见。PostgreSQL 用的是to_timestamp和EXTRACT-- 时间戳 - 带时区的 timestamp SELECT to_timestamp(1704067200); -- timestamp - 秒级时间戳 SELECT EXTRACT(EPOCH FROM TIMESTAMP 2024-01-01 12:00:0008);4.5 fastjson 序列化时间字段的隐藏逻辑热搜词里有一类高频问题fastjson 时间转时间戳。这其实是 JSON 序列化框架对java.util.Date字段的默认行为问题。早期的 fastjson 1.x 版本序列化一个 Date 字段时默认输出毫秒时间戳也就是 13 位数字。很多前端拿到这个数字一脸懵不知道这是啥有的后端在对象里定义的是Date类型想输出yyyy-MM-dd HH:mm:ss给前端直接展示就需要在字段上显式声明格式public class OrderVO { JSONField(format yyyy-MM-dd HH:mm:ss) private Date createTime; }加了format后fastjson 序列化出来就是2024-01-01 12:00:00这种字符串而不是 13 位数字。反序列化方向fastjson 对时间戳字符串和数字时间戳都有自动识别能力但当你自定义了format反序列化时也必须严格匹配这个格式否则解析失败。到了 Fastjson 2com.alibaba.fastjson2默认行为基本一致但日期格式的兼容性更好字段注解也保留。我的建议很简单对外接口响应里时间字段要么统一输出 ISO 8601 字符串要么统一输出毫秒时间戳并在接口文档里写清楚。最怕的就是不同接口一个输出字符串一个输出数字前端每次都要猜。如果你的团队对时间展示格式有强要求优先用format指定字符串如果前端要自己换算时区做各种排序输出毫秒时间戳更省事。5. 时区错乱才是时间问题的大头5.1 UTC、CST、DST 这些缩写到底代表什么时区问题比格式问题隐蔽得多因为错误不一定报错。先厘清几个缩写UTC协调世界时是事实上的全球时间标准所有时区都以 UTC 的偏移量来表示。它和 GMT格林尼治平时在民用时间上基本可以混用但标准上略有差异。DST夏令时。很多国家到了夏天会把时钟拨快一小时导致同一个本地时间在不同季节对应不同的 UTC 时刻。中国已经不用 DST 了但做欧美业务躲不开。CST这个缩写最坑它同时是中国标准时间China Standard Time, UTC8和美国中部时间Central Standard Time, UTC-6的缩写。在 Java 里TimeZone.getTimeZone(CST)返回的可能是美国中部时间坑了很多中国开发者。实际开发中你不需要记住每个时区的偏移量但必须记住一条原则存储和传输一律用 UTC 或时间戳展示时再转当地时间。你的 12:00:00 和用户的 12:00:00 不是同一个时刻如果不带时区信息就是一个有歧义的字符串。5.2 服务器、数据库、前端三层时区如何配合一次请求从日志到数据库到前端展示时间字段要过三层服务器层JVM 默认时区是操作系统时区很多容器镜像默认 UTC本地开发机默认 Asia/Shanghai。代码里用new Date()打印出来的是 JVM 默认时区下的本地时间如果日志采集系统统一按 UTC 处理就会有偏差。数据库层MySQL 的NOW()返回会话时区的时间CURRENT_TIMESTAMP也一样。表结构里TIMESTAMP类型存的是 UTC 时间戳查询出来会按会话时区转换DATETIME类型则纯粹存墙上时间不带时区语义。不少人在这里搞混导致同一套数据在不同环境查出来不一样。前端层浏览器里的new Date()用的是用户本机时区。你从后端拿到一个 UTC 字符串应该先解析成 Date 对象再渲染绝对不要直接拼字符串展示。三层之间只要有一层时区设置不一致最终展示就会差几小时。最稳妥的约定服务器、数据库统一 UTC 或统一 Asia/Shanghai所有接口传输的时间字符串只允许带时区后缀的 ISO 8601 格式比如2024-01-01T12:00:0008:00或者干脆传毫秒时间戳。这样前端拿到后无论用户在哪个时区都能正确换算成本地时间。5.3 一次跨年跨时区问题的排查链路我处理过的一个真实案例可以完整展示时区问题排查思路。告警显示某个报表里的昨日订单数在每天早上 9 点跑批时总是少算一部分。第一反应是 SQL 里的日期条件写错了但检查WHERE create_time 2024-01-01 00:00:00 AND create_time 2024-01-02 00:00:00看起来没问题。继续追发现应用服务器时区是 UTC数据库会话时区是 Asia/Shanghai。应用代码里把今天零点用LocalDate.now().atStartOfDay()生成然后转成Date传给 MyBatisMyBatis 再传给数据库。LocalDate.now()用的是 JVM 默认时区UTC生成的是 UTC 零点数据库却按 Asia/Shanghai 来比较所以昨天的窗口整体偏移了 8 小时。每天早晨 9 点跑批正好把昨天 08:00 到今天 08:00 这个错误窗口之外的单子漏掉。排查链路是先看报错数据的时间窗口上下界再逐层确认每个环节用的时区最后定位到LocalDate.now()没有显式指定时区。修复也简单改成LocalDate.now(ZoneId.of(Asia/Shanghai)).atStartOfDay(ZoneId.of(Asia/Shanghai))并且明确 MyBatis 查询参数传的是绝对时刻而非本地时间字符串。这个案例的教训是凡是生成今天零点本周一月初这类边界时间必须显式传时区依赖默认时区就是埋雷。因为你的本地开发环境、测试服务器、生产服务器很可能时区不一致代码在本地能跑对上生产就错。6. 日志工具和系统事件里的时间戳和开发里的不是一回事6.1 MobaXterm 和 SecureCRT 的日志时间戳配置运维排查问题经常要看远程会话日志。MobaXterm 和 SecureCRT 的日志时间戳设置总是有人问。MobaXterm 里打开会话设置Session settings找到 Terminal 分类下的 Logging 选项可以指定日志文件路径并勾选Log timestamps或在日志行前加时间戳的功能。勾选后每行日志都会带上前缀时间格式类似[2024-01-01 12:00:00]这在你追踪一句话是什么时候敲的、命令输出是什么时候回来的时候特别管用。注意 MobaXterm 的免费版和付费版功能略有差异但基本的日志时间戳功能都有。SecureCRT 的日志配置在 Session Options - Terminal - Log File。它默认支持在日志文件名里使用时间戳变量比如把日志文件名设为log_%Y%M%D_%h%m%s.txt每次连接都会生成带日期时间的新文件。部分版本支持在每行日志前加时间戳具体看 Log File 页签里有没有 Prefix each line with a timestamp 一类的选项这个功能在不同版本里位置和文案差别较大如果你找不到优先考虑用文件名时间戳来做会话维度的时间标记。这些工具的时间戳本质上是录制时打印的本地时间字符串跟 Unix 时间戳没有换算关系。但在分析故障时它们是和业务日志对齐的关键锚点。6.2 Windows 事件查看器里的“时间戳:0x57c44efe”怎么读Windows 事件查看器里经常能看到类似这样一行错误应用程序名称: explorer.exe版本: 6.1.7601.23537时间戳: 0x57c44efe错误模块名称: KERNELBASE.dll这里的时间戳: 0x57c44efe和 Unix 时间戳是两回事。它是 PE 文件头里的TimeDateStamp字段表示这个exe 或 dll 的链接时间通常可以理解为编译/构建时间而不是崩溃发生的时刻。格式是十六进制需要先转成十进制再按 Unix 时间戳换算成日期。换算方法可以直接用 Python 一行算import datetime # 0x57c44efe 转为十进制是 1472453374 print(datetime.datetime(1970, 1, 1) datetime.timedelta(seconds0x57c44efe)) # 输出2016-08-29 06:49:34UTC 时间算出来是 2016 年 8 月 29 日说明这个explorer.exe是那段时间编译的版本。在看 Windows 崩溃转储或事件日志时这个时间戳能帮你判断不同机器上跑的模块是不是同一版本——同一文件名的 exe如果TimeDateStamp不同说明版本或编译参数不一样问题可能只在特定版本上复现。这是 Windows 事件日志分析里一个非常实用的小技巧。7. 我把这几条时间处理经验写进了团队规范7.1 传输和存储层的统一约定时间处理踩过足够多的坑之后我意识到零散的注意点救不了团队必须形成几条硬性规范存储层数据库一律用DATETIME存本地业务时间的场景必须明确时区如果是绝对时刻优先存毫秒级时间戳或TIMESTAMP类型并统一 UTC。字段名带单位后缀如create_time_ms。传输层对外接口的时间字段统一输出 ISO 8601 字符串带时区偏移或毫秒时间戳二选一全项目统一。禁止输出yyyy-MM-dd HH:mm:ss这种无时区字符串除非接口文档明确约定默认 Asia/Shanghai。代码层Java 新代码禁用SimpleDateFormat、java.util.Date的setTime等老 API统一java.time前端统一用 Day.js 做格式化和时区转换禁止手写日期拼接字符串。日志层日志里的时间统一 UTC并且带毫秒方便跨系统对齐。2024-01-01T00:00:00.123Z比2024-01-01 08:00:00更容易做全文检索和时序分析。7.2 必须写进单测的三个用例时间代码的单元测试很多人只测了今天结果没覆盖边界。我要求团队至少写这三个用例第一个是跨年边界测2024-12-31 23:59:59和2025-01-01 00:00:00这两个时间点在格式化、时间戳互转、周周年份三个维度上的行为。YYYY和yyyy的差别只有这种用例能真实验证。第二个是时区边界测 UTC 和 Asia/Shanghai 两个时区下同一个Instant转本地时间的显示是否一致。最好再加一个America/New_York覆盖带夏令时的时区验证 DST 切换当天的行为。第三个是精度边界分别传入 10 位、13 位、16 位、19 位时间戳断言函数能识别精度并给出预期结果或者至少不静默出错。老代码如果无法处理所有精度要有明确报错而不是生成一个 1970 年的幽灵时间。这三个用例写下来能挡住绝大多数我在线上见过的时间 bug。测试代码比业务代码啰嗦一点没关系时间问题的排查成本动辄按小时计提前用测试锁住行为是性价比最高的投资。我个人体会是时间戳和yyyy-MM-dd HH:mm:ss格式的互转表面上是个查表就能解决的问题但真正让你翻车的从来不是不会写转换代码而是精度没看清、时区没对齐、格式串大小写抄错。把这四件事记牢再配合上面这些规范时间和你就不会再互相折磨了。
返回列表