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

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)
式的"魔法数字"陷阱!