Java 日期时间(一文讲透)

更新时间:

💡一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍 ;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;

截止目前, 星球 内专栏累计输出 82w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2900+ 小伙伴加入学习 ,欢迎点击围观

在 Java 开发中,日期时间处理是一个高频且复杂的主题。无论是记录用户注册时间、计算订单有效期,还是解析日志中的时间戳,开发者都需要精准操作日期时间数据。然而,Java 早期版本中 DateCalendar 类的设计缺陷,曾让许多开发者陷入线程不安全、代码冗余的困境。随着 Java 8 的发布,全新的 java.time 包(也称为 Java 日期时间 API)彻底革新了这一领域。本文将从基础概念出发,结合实际案例,系统讲解 Java 日期时间的现代化处理方式,帮助开发者高效驾驭时间的“魔法”。


一、旧版 API 的痛点与新 API 的诞生

1.1 旧版 API 的局限性

Java 早期版本的日期时间处理依赖 java.util.Datejava.util.Calendar 类。这些类存在以下问题:

  • 线程不安全Date 是可变对象,多线程环境下易引发数据竞争。
  • 设计混乱Calendar 类方法繁多(如 get() 需要传递常量 Calendar.YEAR),且默认月份从 0 开始(1 月对应 0),容易引发越界错误。
  • 缺乏功能性:无法直观表示“2023-09-20”这样的日期片段,必须依赖时间戳(毫秒数)进行计算。

比喻:旧版 API 像是一把“瑞士军刀”,虽然功能齐全,但操作复杂且容易误伤自己。

1.2 新 API 的设计理念

Java 8 引入的 java.time 包彻底重构了日期时间处理逻辑,其核心原则是:

  • 不可变性与线程安全:所有类均为不可变对象,天然支持多线程环境。
  • 语义清晰:通过 LocalDate(日期)、LocalTime(时间)、LocalDateTime(日期时间)等类,将概念拆分为独立积木块。
  • 功能性编程友好:支持链式调用(如 plusDays())和 DateTimeFormatter 格式化工具。

关键词布局:Java 日期时间 API 的设计,让开发者能够像拼搭积木一样组合日期时间对象。


二、核心类详解:构建日期时间的基本单元

2.1 LocalDate:纯粹的日期

LocalDate 用于表示不带时区的日期(如生日、活动日期)。

LocalDate today = LocalDate.now(); // 获取当前日期  
LocalDate christmas = LocalDate.of(2023, 12, 25); // 2023年12月25日  
System.out.println(christmas.getDayOfWeek()); // 输出:FRIDAY  

关键操作

  • plusDays(3):计算三天后的日期。
  • isAfter() / isBefore():比较日期大小。

2.2 LocalTime:独立的时间片段

LocalTime 处理不包含日期的时间值(如会议开始时间)。

LocalTime now = LocalTime.now(); // 14:30:22.123  
LocalTime workStart = LocalTime.of(9, 0); // 09:00  
System.out.println(workStart.plusHours(8)); // 17:00  

2.3 LocalDateTime:日期与时间的结合体

LocalDateTime 同时包含日期和时间信息,是日常开发中最常用的类。

LocalDateTime event = LocalDateTime.now();  
LocalDateTime futureEvent = event.plusMonths(1).minusHours(2);  

2.4 Instant:全局时间戳

Instant 表示自 1970-01-01 UTC 以来的纳秒数,常用于记录系统时间戳。

Instant now = Instant.now(); // 如:2023-09-20T08:15:30Z  

三、格式化与解析:让日期时间“开口说话”

3.1 DateTimeFormatter 的基本用法

通过 DateTimeFormatter 可将日期时间对象转换为字符串,或反之。

// 自定义格式化模板  
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");  
LocalDateTime dateTime = LocalDateTime.now();  
String formatted = dateTime.format(formatter); // 输出类似 "2023-09-20 14:30:22"  

// 解析字符串  
LocalDate parsedDate = LocalDate.parse("2023-12-25", DateTimeFormatter.ISO_LOCAL_DATE);  

3.2 预定义格式化器

Java 提供了多种预定义格式器,如 DateTimeFormatter.ISO_LOCAL_DATE,避免重复编写模板。

3.3 时区敏感的格式化

若需处理带时区的日期时间(如 ZonedDateTime),需结合 ZoneId

ZonedDateTime shanghaiTime = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));  
String formattedWithZone = shanghaiTime.format(DateTimeFormatter.RFC_1123_DATE_TIME);  

四、时区与偏移量:跨越时区的“时空旅行”

4.1 ZoneId:时区标识符

时区通过 ZoneId 类管理,常用值如 "UTC""Asia/Shanghai""America/New_York"

ZoneId beijing = ZoneId.of("Asia/Shanghai");  
LocalDateTime localDateTime = LocalDateTime.now();  
ZonedDateTime zdt = ZonedDateTime.of(localDateTime, beijing);  

4.2 时区转换案例

假设需要将 UTC 时间转换为北京时间:

Instant utcInstant = Instant.now();  
ZonedDateTime beijingTime = utcInstant.atZone(ZoneId.of("Asia/Shanghai"));  

五、常见操作实战:从计算年龄到日历逻辑

5.1 计算年龄

public static int calculateAge(LocalDate birthDate) {  
    LocalDate today = LocalDate.now();  
    return today.getYear() - birthDate.getYear();  
    // 注意:需补充月份和日期的边界判断  
}  

5.2 判断是否为周末

LocalDate date = LocalDate.of(2023, 10, 1);  
if (date.getDayOfWeek() == DayOfWeek.SATURDAY || date.getDayOfWeek() == DayOfWeek.SUNDAY) {  
    System.out.println("周末!");  
}  

5.3 时间间隔计算

LocalDateTime start = LocalDateTime.of(2023, 1, 1, 0, 0);  
LocalDateTime end = LocalDateTime.now();  
Duration duration = Duration.between(start, end);  
System.out.println("总秒数:" + duration.getSeconds());  

六、进阶技巧:时钟模拟与自定义逻辑

6.1 Clock:模拟时间流

在单元测试中,可通过 Clock 固定时间:

Clock fixedClock = Clock.fixed(Instant.parse("2023-01-01T00:00:00Z"), ZoneId.of("UTC"));  
LocalDateTime fixedTime = LocalDateTime.now(fixedClock);  

6.2 自定义日期逻辑

通过 TemporalAdjusters 实现复杂日期计算(如下个月的最后一个工作日):

LocalDate lastWorkingDay = LocalDate.now().with(temporal -> {  
    LocalDate candidate = temporal.with(TemporalAdjusters.lastDayOfMonth());  
    if (candidate.getDayOfWeek() == DayOfWeek.SATURDAY) {  
        return candidate.minusDays(1);  
    }  
    return candidate;  
});  

结论

Java 日期时间 API 的革新,不仅解决了旧版本的痛点,更通过清晰的设计让时间操作变得直观高效。开发者应优先使用 LocalDateLocalDateTime 等不可变类,善用 DateTimeFormatter 进行格式化,并结合 ZoneId 管理时区。对于复杂场景,可通过 DurationPeriod 和自定义调整器扩展功能。

掌握这些知识后,开发者能更从容地处理订单有效期、用户活跃时段分析等业务需求。尽管旧代码中仍可能存在 DateCalendar 的遗留问题,但新 API 已成为 Java 日期时间处理的黄金标准。建议读者通过实际项目逐步替换旧代码,拥抱更现代、更安全的日期时间编程范式。


关键词布局回顾:本文通过案例与讲解,系统解析了 Java 日期时间 API 的核心概念与实践技巧,帮助开发者在复杂场景中精准掌控时间逻辑。

最新发布