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

01 引言

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

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

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

02 核心类解析

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

03 关键使用技巧

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

3.1 日期的创建和获取

@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 日期的运算和调整

@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 日期的比较

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

@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 时区的处理

@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 时间间隔计算

@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)

@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.Datejava.time.LocalDateTime的桥梁。

3.7 日期之间的转化

@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 日期格式化和解析

@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()
  • 明确语义LocalDateZonedDateTime 等命名清晰
  • 强大工具:内置 TemporalAdjustersChronoUnit

关键建议

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

智_永无止境

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值