DateTimeFormatter & 时区处理:时间格式不再头疼(AI 辅助学习 Java 8)
上周讲了 LocalDateTime 基础。今天深入两个实战场景:自定义格式化和时区转换,微信公众号文章时间和全球用户时间展示都能搞定。
一、DateTimeFormatter 线程安全
SimpleDateFormat 不是线程安全的,多线程环境下必须每次 new 或加锁。DateTimeFormatter 是不可变对象,天然线程安全,可以定义为常量复用。
// 安全:定义为全局常量
private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
private static final DateTimeFormatter FULL_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
二、常见格式一览
LocalDateTime now = LocalDateTime.of(2026, 6, 13, 10, 30, 45);
// 日期 / 时间 / 完整
DateTimeFormatter dateOnly = DateTimeFormatter.ofPattern("yyyy-MM-dd");
DateTimeFormatter timeOnly = DateTimeFormatter.ofPattern("HH:mm:ss");
DateTimeFormatter full = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
System.out.println(now.format(dateOnly)); // 2026-06-13
System.out.println(now.format(timeOnly)); // 10:30:45
System.out.println(now.format(full)); // 2026-06-13 10:30:45
三、中文格式
// 中文日期格式
DateTimeFormatter chineseDate = DateTimeFormatter.ofPattern("yyyy年M月d日");
DateTimeFormatter chineseFull = DateTimeFormatter.ofPattern("yyyy年M月d日 a h:mm");
System.out.println(now.format(chineseDate)); // 2026年6月13日
System.out.println(now.format(chineseFull)); // 2026年6月13日 上午 10:30
注意:
M和d不带前导零,MM和dd带前导零。a是上午/下午标记,h是 12 小时制。
四、DateTimeFormatterBuilder:可选部分
有些场景中时间部分是可选的(比如 "2026-06-13" 和 "2026-06-13 10:30" 都要能解析),可以用 DateTimeFormatterBuilder:
DateTimeFormatter flexible = new DateTimeFormatterBuilder()
.appendPattern("yyyy-MM-dd")
.optionalStart()
.appendPattern(" HH:mm")
.optionalEnd()
.toFormatter();
// 有时间和无时间都能解析
LocalDateTime.parse("2026-06-13 10:30", flexible); // 2026-06-13T10:30
LocalDate.parse("2026-06-13", flexible); // 2026-06-13
五、时区基础:ZoneId
// 当前时区时间
ZonedDateTime beijing = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
ZonedDateTime tokyo = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));
ZonedDateTime newyork = ZonedDateTime.now(ZoneId.of("America/New_York"));
六、时区转换:withZoneSameInstant
// 北京时间 10:30
LocalDateTime local = LocalDateTime.of(2026, 6, 13, 10, 30, 0);
ZonedDateTime beijingTime = local.atZone(ZoneId.of("Asia/Shanghai"));
// 同一时刻的全球时间
List<String> zones = Arrays.asList("Asia/Tokyo", "America/New_York", "Europe/London");
for (String zoneId : zones) {
ZonedDateTime zdt = beijingTime.withZoneSameInstant(ZoneId.of(zoneId));
System.out.println(zoneId + ": " + zdt.format(DateTimeFormatter.ofPattern("MM-dd HH:mm")));
}
// Tokyo: 06-13 11:30
// New York: 06-12 22:30
// London: 06-13 03:30
withZoneSameInstant保持同一物理时刻,只改变时区显示。不要用withZoneSameLocal,它会改变物理时刻。
七、获取城市名
String zoneId = "America/New_York";
String city = zoneId.substring(zoneId.lastIndexOf('/') + 1).replace('_', ' ');
// city = "New York"
八、博客项目中的时间格式化
// 文章列表页面:显示 "2026-06-13 10:30"
public String formatPublishTime(LocalDateTime publishTime) {
return publishTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
}
// 文章详情页:显示完整时间 "2026年6月13日 上午 10:30"
public String formatDetailTime(LocalDateTime publishTime) {
return publishTime.format(DateTimeFormatter.ofPattern("yyyy年M月d日 a h:mm"));
}
// RSS 订阅:ISO 8601 格式
public String formatRssTime(LocalDateTime publishTime) {
return publishTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
}
九、运行结果
=== 高级格式化 ===
日期: 2026-06-13
时间: 10:30:45
完整: 2026-06-13 10:30:45
中文: 2026年6月13日
中文完整: 2026年6月13日 上午 10:30
灵活解析 (有): 2026-06-13T10:30
灵活解析 (无): 2026-06-13
=== 时区转换 ===
北京: 2026-06-13T10:30+08:00[Asia/Shanghai]
同一时刻全球时间:
Tokyo: 06-13 11:30
New York: 06-12 22:30
London: 06-13 03:30
Sydney: 06-13 12:30
可用时区数量: 604
十、格式模式速查表
| 模式 | 含义 | 示例 |
|---|---|---|
yyyy | 四位年 | 2026 |
yy | 两位年 | 26 |
M / MM | 月(不带/带前导零) | 6 / 06 |
d / dd | 日(不带/带前导零) | 13 / 13 |
H / HH | 24小时制(不带/带前导零) | 9 / 09 |
h / hh | 12小时制(不带/带前导零) | 9 / 09 |
m / mm | 分钟 | 5 / 05 |
s / ss | 秒 | 30 / 30 |
a | 上午/下午 | 上午 / 下午 |
E / EEEE | 星期(短/全) | 周六 / 星期六 |
z | 时区名称 | CST |
Z | 时区偏移 | +0800 |
十一、下篇预告
周六我们将用 Java 8 新时间 API 重构博客项目中的时间字段,从旧的 Date 全面迁移到 LocalDateTime,并实现"今日发布"、"本周发布"、"按月份统计"等实用功能。
系列文章导航:
更多请关注我的微信公众号,感谢!