datetimeformatter 是不可变的,每次换格式需新建实例;预定义常量和自定义实例均线程安全;注意 pattern 大小写(如 mm/yy vs mm/yy)、时区语义、locale 显式指定及静态复用以提升性能。

DateTimeFormatter 是 Java 8+ 的不可变格式化工具,不能直接修改
它不接受 setPattern 或类似方法——每次换格式都得新建实例。很多人误以为能复用同一个 formatter 改 pattern,结果抛出 UnsupportedOperationException 或得到旧格式输出。
正确做法是按需创建:用 DateTimeFormatter.ofPattern("yyyy-MM-dd") 或预定义常量如 DateTimeFormatter.ISO_LOCAL_DATE。
- 预定义常量(如
ISO_LOCAL_DATE、BASIC_ISO_DATE)线程安全,可全局静态复用 - 自定义 pattern 实例也线程安全,但别试图“重设”它——新建更快更可靠
- pattern 字符大小写敏感:
MM是月,mm是分;YYYY是周基准年,yyyy才是日历年
LocalDateTime 和 ZonedDateTime 格式化前必须注意时区语义
LocalDateTime 没有时区信息,用 DateTimeFormatter 格式化没问题;但若你拿 ZonedDateTime 直接套用只含日期时间的 pattern(比如 "yyyy-MM-dd HH:mm"),会丢掉时区或偏移——不是 bug,是设计如此。
要保留时区,得用带时区字段的 pattern,例如:
ZonedDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSXXX"))
-
XXX输出+08,ZZZZ输出GMT+08:00,zzzz输出China Standard Time - 如果源对象是
Instant,必须先转成带时区的类型(如atZone(ZoneId.systemDefault())),否则format()会抛DateTimeException - 跨系统传输建议优先用
ISO_INSTANT(输出形如"2024-05-20T08:12:34.567Z"),避免解析歧义
中文星期/月份显示需要显式指定 Locale
默认 pattern 中的 EEEE(星期全称)或 MMM(月份缩写)走的是 JVM 默认 Locale,服务器环境可能不是中文,导致输出英文。
解决方式是在构建 formatter 时传入 Locale.CHINA:
DateTimeFormatter.ofPattern("yyyy年MM月dd日 EEEE", Locale.CHINA)
- 不要依赖
ResourceBundle或系统属性去“动态切 locale”,formatter 创建后 locale 就固定了 -
Locale.US和Locale.CHINA对数字分隔符、AM/PM 标记也有影响,测试时最好显式覆盖 - 若需运行时切换语言,应缓存多个 locale 版本的 formatter,而不是临时改 pattern
性能敏感场景下避免重复创建 formatter
虽然 DateTimeFormatter 创建开销不大,但在高频日志打点或循环内调用时,反复 ofPattern 仍会触发正则解析和内部状态初始化。
- 把常用 formatter 声明为
static final字段,例如:private static final DateTimeFormatter LOG_FMT = DateTimeFormatter.ofPattern("HH:mm:ss.SSS") - 避免在 lambda 或匿名内部类里现场 new,尤其在 Stream 处理中
- 注意:
DateTimeFormatter.ofLocalizedDateTime()这类工厂方法内部会 new 新实例,不适合高频复用
YYYY 而非 yyyy)。










