推荐新项目使用DateTimeFormatter+Instant/LocaleDateTime,旧项目用SimpleDateFormat但需注意线程安全和时区问题;常见错误为大小写混淆、null未判空、时区不一致及SimpleDateFormat非线程安全。

用 SimpleDateFormat 格式化 Date 为字符串
这是最直接、最常用的写法,适合 JDK 7 及更早版本,也仍在大量旧项目中使用。核心是创建一个 SimpleDateFormat 实例,调用其 format() 方法。
常见错误现象:java.lang.IllegalArgumentException: Illegal pattern character 'Y' —— 混淆了大小写(比如把 y 写成 Y),或用了不支持的字母;NullPointerException —— 忘记检查 Date 是否为 null。
-
y表示年(推荐用yyyy,yy在跨世纪时易出错) -
M表示月(MM输出 01–12,M输出 1–12) -
d表示日(同理,dd补零) -
H是 24 小时制小时,h是 12 小时制(别混用) -
SimpleDateFormat不是线程安全的,多线程环境下必须每个线程新建实例,或加锁,或改用ThreadLocal
示例:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");<br>String str = sdf.format(new Date()); // 输出类似 "2024-06-12 15:30:45"
用 DateTimeFormatter + Instant / LocalDateTime(推荐新项目)
JDK 8 引入的 java.time API 更可靠、不可变、线程安全。但注意:Date 本身不是 java.time 类型,需先转换。
立即学习“Java免费学习笔记(深入)”;
使用场景:新项目、需要时区控制、要求线程安全、或已用 LocalDateTime 等类型。
- 从
Date转换:先用date.toInstant(),再根据时区转成ZonedDateTime或LocalDateTime -
DateTimeFormatter是线程安全的,可复用静态实例(如DateTimeFormatter.ofPattern("yyyy-MM-dd")) - 默认时区是系统时区,若需固定时区(如 UTC),必须显式指定:
instant.atZone(ZoneId.of("UTC")) - 忽略时区直接转
LocalDateTime会丢失时区信息,可能造成时间偏移(例如服务器在东八区,但业务要求按 UTC 解析)
示例:
Date date = new Date();<br>String str = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")<br> .format(date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime());
为什么不能直接用 Date.toString()?
Date.toString() 返回的是固定格式字符串(如 "Wed Jun 12 15:30:45 CST 2024"),不可控、不国际化、无法自定义分隔符或精度。
常见错误现象:上线后发现日志里时间格式不一致,或前端解析失败;测试环境和生产环境时区不同导致显示偏差。
- 该方法输出依赖 JVM 启动时的默认时区,且无法配置
- 它包含星期、英文月份等本地化内容,不适合做数据交换或存储
- 没有毫秒、时区偏移等细粒度控制能力
容易被忽略的时区和线程安全问题
绝大多数线上 Date → String 的 bug 都集中在这两点:时区没对齐,或 SimpleDateFormat 被多个线程共享。
- 前后端时间显示不一致?先查双方用的时区是否一致,再查格式化代码是否隐式用了系统默认时区
- 高并发下偶尔格式化出错或返回空字符串?大概率是
SimpleDateFormat实例被共用了 - 用
DateTimeFormatter也不能完全躺平:如果从Date转Instant后没指定时区就转LocalDateTime,等于默认用了系统时区,部署到不同时区服务器就会漂移
真正稳的做法是:明确时区来源(数据库存的是 UTC?用户本地时间?)、统一格式化入口、避免裸用 Date 和 SimpleDateFormat。


















