DateTimeFormatter.ofPattern() 大小写敏感且字母含义固定,如Y(周年度)与y(年度)、D(年中日)与d(月中日)、mm(分钟)与MM(月份)易混淆;预定义格式器更安全但不灵活;解析时需按类型选LocalDateTime或ZonedDateTime;自定义格式器应声明为static final以保证线程安全。

DateTimeFormatter.ofPattern() 是最常用也最容易出错的入口
直接用 DateTimeFormatter.ofPattern() 定义格式时,大小写敏感且字母含义固定,比如 "yyyy-MM-dd" 合法,但写成 "YYYY-MM-DD" 就会出问题——Y 是「基于周的年」,D 是「一年中的第几天」,和日常直觉不符。
常见错误现象:DateTimeParseException 报错提示「Text '2024-05-10' could not be parsed」,往往是因为用了 Y 或 D 而非 y 和 d;或者把 mm(分钟)误当 MM(月份)用。
- 月份必须用
MM(数字)或MMM(缩写,如 "May")或MMMM(全称),mm永远是分钟 - 小时要注意 12 小时制(
hh)和 24 小时制(HH)的区别,hh范围是 01–12,HH是 00–23 - 毫秒用
SSS,不是sss;时区偏移推荐用XXX(如 "+08")或ZZZZ(如 "GMT+08:00")
预定义格式器比自定义 pattern 更安全,但灵活性受限
DateTimeFormatter.ISO_LOCAL_DATE、DateTimeFormatter.RFC_1123_DATE_TIME 这类静态常量,不依赖字符串解析,无大小写风险,也天然兼容时区与本地化逻辑。适合标准场景,比如 API 返回 ISO 格式时间、日志打点用 RFC 格式。
但它们不能微调——比如想把 ISO_LOCAL_DATE 的短横线换成斜杠,或在后面加个中文“日”,就必须切回 ofPattern()。
- 如果只要「年月日」且接受默认分隔符,优先用
DateTimeFormatter.ISO_LOCAL_DATE - 需要带时分秒+时区且符合 HTTP 头规范?直接用
DateTimeFormatter.RFC_1123_DATE_TIME - 想加中文或特殊符号(如
"yyyy年MM月dd日"),只能手写 pattern,别试图拼接预定义格式器
LocalDateTime.parse() 和 ZonedDateTime.parse() 对 formatter 的要求不同
LocalDateTime 不含时区信息,所以用 DateTimeFormatter 解析时,pattern 里不能出现时区相关符号(Z、X、z、V 等),否则抛 DateTimeParseException;而 ZonedDateTime 必须能识别时区字段,否则解析结果丢失时区,变成纯 LocalDateTime。
典型踩坑:拿到一个带 "+08:00" 的字符串,却用 LocalDateTime.parse(str, formatter),结果时间数值对,但时区被丢弃,后续转 UTC 时出错。
- 输入含时区(如 "2024-05-10T14:30:00+08:00")→ 用
ZonedDateTime.parse()+ 含XXX或XX的 pattern - 输入纯日期时间无时区(如 "2024-05-10 14:30")→ 用
LocalDateTime.parse(),pattern 别写时区字段 - 不确定输入是否有时区?先用
CharSequence做字符串检测,再分路径处理,别硬塞一个 formatter 到两种 parse 方法里
自定义 pattern 的线程安全性常被忽略
DateTimeFormatter 实例是不可变且线程安全的,但很多人习惯每次格式化都 new 一个:new DateTimeFormatter.ofPattern("yyyy-MM-dd")——这不仅浪费对象,还可能触发重复编译 pattern 的开销(内部用 java.time.format.DateTimeFormatterBuilder 编译为状态机)。
更隐蔽的问题是:在 Spring Bean 或工具类里,如果把 DateTimeFormatter 声明为非 final 的实例变量,又没加 static,多个请求并发时可能因初始化时机引发 NPE 或不一致行为。
- 所有自定义 pattern 都应声明为
public static final DateTimeFormatter MY_FORMAT = DateTimeFormatter.ofPattern("..."); - 避免在方法体内反复调用
ofPattern(),尤其在循环或高频接口中 - 如果 pattern 来自配置(如 yml),务必在应用启动时一次性构建并缓存,不要每次读取配置再 new
M 和 m 的错位,可能让整个时间计算逻辑在生产环境静默偏移数小时。

















