Java 8 日期时间 API 全面指南:使用技巧与场景实践

关注我的公众号:【编程朝花夕拾】,可获取首发内容。

01 引言

日期类在日常开发中很常见,我们习惯了使用各种DateUtils工具包,直接处理java.util.Date类,包括计算、格式化、解析等操作。但是,在使用中存在线程安全等问题。

Java 8 引入的 java.time 包彻底解决了旧版 java.util.Date 和 Calendar 的设计缺陷,提供了线程安全**、**直观易用且功能全面的日期时间处理方案。

习惯了以前的工具类,反而觉得Java 8中的日期类用起来不怎么顺手,本文将深入解析核心类、使用技巧及实际场景应用。

02 核心类解析

类名 描述 示例
LocalDate 仅含日期(年-月-日) 生日、会议日期
LocalTime 仅含时间(时:分:秒.纳秒) 打卡时间、营业时间
LocalDateTime 日期+时间(无时区) 本地事件记录
ZonedDateTime 带时区的完整日期时间 跨时区会议、航班时刻
Instant 时间戳(Unix 时间,精确到纳秒) 日志记录、性能计时
Period 日期区间(年/月/日) 计算年龄、项目周期
Duration 时间间隔(秒/纳秒) 程序执行时间、倒计时
DateTimeFormatter 日期格式化/解析 日期字符串转换

03 关键使用技巧

因为都是关于日期的类,所以他们之间都用几乎相同的API,我们以LocalDateTime为代表介绍其中的用法。

3.1 日期的创建和获取

java 复制代码
@Test
void test01() {
    // 获取当前日期
    LocalDateTime now = LocalDateTime.now();
    System.out.println(now); 
    //2025-07-17T18:19:45.927848800

    // 自定义日期
    LocalDateTime localDateTime = LocalDateTime.of(2025, 7, 17, 12, 30, 20);
    System.out.println(localDateTime);
    // 2025-07-17T12:30:20

    // 通过秒和偏移量获取日期:偏移8小时
    LocalDateTime localDateTime1 = LocalDateTime.ofEpochSecond(System.currentTimeMillis()/1000, 8888, ZoneOffset.ofHours(8));
    System.out.println(localDateTime1);
    // 2025-07-17T18:19:45.000008888

    // 通过秒和偏移量获取日期:使用当前时区时间
    LocalDateTime localDateTime2 = LocalDateTime.ofEpochSecond(System.currentTimeMillis()/1000, 8888, ZoneOffset.from(ZonedDateTime.now()));
    System.out.println(localDateTime2);
    // 2025-07-17T18:19:45.000008888
}

需要说明的是LocalDateTime.ofEpochSecond()方法,通过当前的秒数+纳秒数+时区的偏移/时区时间来构建LocalDateTime对象。

通过LocalDateTime可以获取日期的年、月、日、时、分、秒、纳秒等参数。如图:

3.2 日期的运算和调整

java 复制代码
@Test
void test02() {
    // 获取当前日期
    LocalDateTime now = LocalDateTime.now();
    System.out.println(now);
    // 2025-07-17T18:19:45.927848800
    
    // 时间计算:- 时间段
    LocalDateTime minus = now.minus(Duration.ofDays(1));
    System.out.println(minus);
    // 2025-07-16T18:19:45.927848800

    // 时间计算:+ 时间段
    LocalDateTime plus = now.plusDays(1);
    System.out.println(plus);
    // 2025-07-18T18:19:45.927848800
    
    // 获取本月第一天
    LocalDateTime with = now.with(TemporalAdjusters.firstDayOfMonth()).withHour(0).withMinute(0).withSecond(0).withNano(0);
    // LocalDateTime with = LocalDateTime.of(now.with(TemporalAdjusters.firstDayOfMonth()).toLocalDate(), LocalTime.MIN);
    System.out.println(with);
    // 2025-07-01T00:00

    // 获取本月最后一天
    LocalDateTime with2 = now.with(TemporalAdjusters.lastDayOfMonth()).withHour(23).withMinute(59).withSecond(59).withNano(999999999);
    // LocalDateTime with2 = LocalDateTime.of(now.with(TemporalAdjusters.firstDayOfMonth()).toLocalDate(), LocalTime.MAX);
    System.out.println(with2);
    // 2025-07-31T23:59:59.999999999
  
}

日期的调整需要用到调整器TemporalAdjusters

3.3 日期的比较

日期的比较和之前的差别不大

java 复制代码
@Test
void test03() {
    System.out.println(now.compareTo(now.plusSeconds(2)));
    // -1
    System.out.println(now.isEqual(now));
    // true
    System.out.println(now.isAfter(now.plusSeconds(2)));
    // false
    System.out.println(now.isBefore(now.plusSeconds(2)));
    // true
}

3.4 时区的处理

java 复制代码
@Test
void test04() {
    // 时区转化
    ZonedDateTime now = ZonedDateTime.now();
    System.out.println(now);
    // 2025-07-17T18:40:30.202283400+08:00[Asia/Shanghai]

    ZonedDateTime zonedDateTime = now.withZoneSameInstant(ZoneId.of("America/New_York"));
    System.out.println(zonedDateTime);
    // 2025-07-17T06:40:30.202283400-04:00[America/New_York]
}

3.5 时间间隔计算

java 复制代码
@Test
void test05() {
    // 不会向上取整
    long between = ChronoUnit.HOURS.between(LocalDateTime.now(), LocalDateTime.now().plusMinutes(40));
    System.out.println(between);
    // 0

    Period between1 = Period.between(LocalDate.now(), LocalDate.now().plusDays(2));
    System.out.println(between1.getDays());
    // 2
    System.out.println(between1.getYears());
    // 0

    long minutes = Duration.ofSeconds(59).toMinutes();
    System.out.println(minutes);
    // 0
}

这里需要注意的是,时间间隔的计算不会向上取整

3.6 高精度时间

java.time.Instant该采用的时间格式是yyyy-MM-ddTHH:mm:ss.SSSSSSSZ,比LocalDateTime多了一个后缀Z,表示该时间采用 ‌协调世界时(UTC)。

java 复制代码
@Test
void test06() {
    Instant now = Instant.now();
    System.out.println(now);
    // 2025-07-17T10:47:08.873287Z

    // 转毫秒
    long epochMilli = now.toEpochMilli();
    System.out.println(epochMilli);
    // 1752749228873

    // 转秒
    System.out.println(now.getEpochSecond());
    // 1752749228
}

为什么这类要单独说呢,因为该类将是java.util.Date和java.time.LocalDateTime的桥梁。

3.7 日期之间的转化

java 复制代码
@Test
void test07() {
    // 转化
    Instant instant = LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant();
    System.out.println(instant);
    // 2025-07-17T10:49:36.742740400Z

    LocalDate localDate = LocalDate.ofInstant(instant, ZoneId.systemDefault());
    System.out.println(localDate);
    // 2025-07-17

    LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
    System.out.println(localDateTime);
    // 2025-07-17T18:49:36.742740400

    Date from = Date.from(instant);
    System.out.println(from);
    // Thu Jul 17 18:49:36 CST 2025

    Instant instant2 = from.toInstant();
    System.out.println(instant2);
    // 2025-07-17T10:49:36.742Z
}

3.8 日期格式化和解析

java 复制代码
@Test
void test06() {
    DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    LocalDateTime now = LocalDateTime.now();
    System.out.println(now.format(dateTimeFormatter));
    // 2025-07-17

    TemporalAccessor parse = dateTimeFormatter.parse("2025-02-04");
    System.out.println(LocalDate.from(parse));
    // 2025-02-04

    LocalDate parse1 = LocalDate.parse("2025-02-04", dateTimeFormatter);
    System.out.println(parse1);
    // 2025-02-04
}

04 小结

Java 8 日期时间 API 的核心优势:

  • 线程安全:所有类均为不可变对象
  • 链式调用 :流畅的 API 设计(如 plusDays().withHour())
  • 明确语义 :LocalDate、ZonedDateTime 等命名清晰
  • 强大工具 :内置 TemporalAdjusters 和 ChronoUnit

关键建议:

  • 生产环境统一使用 java.time.format.DateTimeFormatter 替代 SimpleDateFormat
  • 复杂日期逻辑优先使用 TemporalAdjusters 而非手动计算
  • 分布式系统强制采用 UTC 时间存储(Instant)

通过合理运用这些工具,可显著提升日期时间处理的准确性 和可维护性 ,彻底告别 Calendar.set(Calendar.MONTH, -1) 式的"魔法数字"陷阱!

相关推荐
驭渊的小故事1 小时前
MyBatis XML 开发实战:从配置到 CRUD 全解析
xml·java·mybatis
哦虎!6 小时前
【JavaEE】网络初识
java
for_ever_love__6 小时前
MySQL ORDER BY 排序优化:从 Using filesort 到索引有序扫描(含双路 / 单路排序原理)
java·mysql·优化·存储·by·order·filesort
大鸡腿同学7 小时前
手心向上,手心向下
后端
我是人✓7 小时前
Java文件上传三方式对比:从传统到SpringBoot
java·开发语言
欣栀♡7 小时前
JVM 频繁 Full GC 先看日志括号里的 cause,jstat 的 FGC 在 JDK 8 上会骗人
java·jvm
CCblandy8 小时前
令牌桶在 0.9 倍负载超发 19%、固定窗口边界放行 20 个:四种限流算法同构实测
后端·python·算法·性能优化·架构
IT研究室8 小时前
最新计算机毕业设计选题推荐-基于spring boot的葡萄酒文化宣传网站-网站-文档指导-Java-springboot
java·spring boot·课程设计
慧都小项8 小时前
MyEclipse 中创建和测试 JAX-RS REST 接口:从项目配置到 REST Explorer
java·restful·myeclipse·jax-rs
Wang's Blog8 小时前
Java 项目实战: 交友项目 - 入门案例:编写 Dubbo 服务提供者
java·dubbo·交友