DateTimeFormatter & 时区处理:时间格式不再头疼(AI 辅助学习 Java 8)

37 阅读4分钟

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

注意:Md 不带前导零,MMdd 带前导零。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 / HH24小时制(不带/带前导零)9 / 09
h / hh12小时制(不带/带前导零)9 / 09
m / mm分钟5 / 05
s / ss30 / 30
a上午/下午上午 / 下午
E / EEEE星期(短/全)周六 / 星期六
z时区名称CST
Z时区偏移+0800

十一、下篇预告

周六我们将用 Java 8 新时间 API 重构博客项目中的时间字段,从旧的 Date 全面迁移到 LocalDateTime,并实现"今日发布"、"本周发布"、"按月份统计"等实用功能。


系列文章导航

更多请关注我的微信公众号,感谢!

微信图片_20260427132414_18_737.jpg