datetimeformatter 是线程安全的,应定义为 static final 常量复用;它不可变、无共享状态,pattern 大小写敏感且不兼容 simpledateformat;默认严格解析,需显式设置 lenient 模式;解析带时区字符串应使用 zoneddatetime/offsetdatetime。

DateTimeFormatter 是线程安全的,别再 new 它了
它内部状态不可变,所有方法返回新实例,没有共享可变字段。这意味着你可以放心地把一个 DateTimeFormatter 实例设为 static final,在多线程环境下复用,完全不用加锁或每次重建。
常见错误是每次格式化都写 new DateTimeFormatter.ofPattern("yyyy-MM-dd") —— 这不仅浪费对象创建开销,还掩盖了它本可复用的本质。
- 推荐做法:定义成常量,比如
public static final DateTimeFormatter YYYY_MM_DD = DateTimeFormatter.ofPattern("yyyy-MM-dd");</li> <li>不要在循环里反复调用 <code>ofPattern()或ofLocalizedDate(),它们每次都会新建对象 - 注意:
withZone()、withLocale()等方法也返回新实例,原实例不受影响,所以依然安全
pattern 字符大小写敏感,且不支持旧版 SimpleDateFormat 的全部符号
比如 yyyy 和 YYYY 含义不同:yyyy 是“年份”,YYYY 是“基于周的年”(week year),跨年时可能差一年;MM 是月,mm 是分钟,写反会导致解析出错或静默错误。
更隐蔽的问题是,DateTimeFormatter 不识别 SSS 以外的毫秒符号(比如不支持 sss),也不支持 EEEE 以外的星期全称写法(EEE 是缩写,EEEE 是全称),而且不兼容 SimpleDateFormat 的 ZZ(RFC 822 格式时区)——得用 xxx 或 XX。
立即学习“Java免费学习笔记(深入)”;
- 校验 pattern 的最简单办法:立刻调用
parse()试一个已知字符串,别等上线后报DateTimeParseException - 中文环境务必显式传
Locale.CHINA,否则ofLocalizedDate(FormatStyle.FULL)可能返回英文星期/月份 -
DateTimeFormatter.ISO_LOCAL_DATE这类预定义常量不含时区,解析带Z或+08:00的字符串会直接抛异常
解析字符串时,lenient 模式默认关闭,严格模式容易爆 DateTimeParseException
和 SimpleDateFormat 默认宽松不同,DateTimeFormatter 默认严格校验:少一位数字、多一个空格、年份超出范围(如 99999),都会直接抛 DateTimeParseException,而不是尝试容错。
这不是 bug,是设计选择。但很多老代码迁移到 Java 8 时没意识到这点,结果原来能 parse 的字符串突然失败。
- 若需宽松行为,必须显式调用
withResolverStyle(ResolverStyle.LENIENT),但要注意:LENIENT 不等于 forgiving,它只对日历计算做宽松(比如把 2 月 30 日转成 3 月 2 日),不处理格式错位 - 更稳妥的做法是先用正则或
String.trim()清洗输入,再进parse(),而不是依赖解析器兜底 - 捕获异常时别只打日志,记得看
e.getParsedString()和e.getErrorIndex(),能快速定位哪一截格式不对
自定义 formatter 要小心 withZone() 和 withLocale() 的链式调用顺序
withZone() 和 withLocale() 都返回新 formatter,但它们不改变原始实例。问题常出在链式调用时误以为“设置了时区就自动带上 locale”,其实没有。
比如 DateTimeFormatter.ofPattern("HH:mm:ss").withZone(ZoneId.of("Asia/Shanghai")).withLocale(Locale.US) 是合法的,但如果你漏掉 withLocale(),又用了 EEEE 这种依赖 locale 的 pattern,运行时就会按默认 locale(通常是系统 locale)解释星期名,导致解析失败。
- 建议把
withLocale()放在链式调用最前面,避免后续 pattern 解析依赖未设置的 locale -
withZone()只影响格式化输出(比如format()时转成指定时区时间),不影响解析 —— 解析永远以字符串自带时区或默认时区为准 - 如果要解析带时区的字符串(如
"2024-01-01T12:00:00+08:00"),别用LocalDateTime.parse(),改用ZonedDateTime.parse()或OffsetDateTime.parse()
真正难的不是记住哪些方法可用,而是理解每个配置项生效的边界:pattern 决定字符匹配规则,locale 决定文本含义,zone 决定时间上下文,resolver style 决定容错尺度——这四个维度一旦混用或遗漏,错误往往不报在调用点,而藏在下游的时间计算里。









