1. 概述
在本文中,我们将学习如何在 Java 中获取某一天的起始和结束时间,并通过简洁明了的代码示例覆盖多种常见场景。
我们使用的是 Java 8 引入的日期时间 API(java.time),这是目前处理时间最推荐的方式。如果你对 Java 8 的时间库还不太熟悉,可以先阅读相关入门文章打个基础。
✅ 使用 java.time
包下的类是现代 Java 开发的标准做法
❌ 避免使用过时的 Date
和 Calendar
类,容易踩坑且代码难维护
2. 从 LocalDate 对象获取
假设我们有一个 LocalDate
对象:
LocalDate localDate = LocalDate.parse("2018-06-23");
目标是获取这一天的“开始时刻”或“结束时刻”,通常用于数据库查询的时间范围条件(比如统计当天数据)。
2.1. 使用 atStartOfDay()
最简单直接的方法就是调用 atStartOfDay()
:
LocalDateTime startOfDay = localDate.atStartOfDay();
这会返回当天的 00:00:00,类型为 LocalDateTime
。
⚠️ 注意:这个方法有重载版本。如果你需要带时区的信息,可以传入 ZoneId
:
ZonedDateTime startOfDay = localDate.atStartOfDay(ZoneId.of("Europe/Paris"));
这样得到的就是巴黎时区下的当天零点。
2.2. 使用 LocalDateTime.of()
另一种方式是组合 LocalDate
和 LocalTime
:
LocalDateTime startOfDay = LocalDateTime.of(localDate, LocalTime.MIDNIGHT);
LocalTime
提供了几个常用的静态常量:
MIDNIGHT
→ 00:00:00MIN
→ 同MIDNIGHT
NOON
→ 12:00:00MAX
→ 23:59:59.999999999(注意不是整秒)
所以,如果你想获取“一天的最后时刻”,可以用:
LocalDateTime endOfDay = LocalDateTime.of(localDate, LocalTime.MAX);
✅ 推荐用于构造精确到纳秒的结束时间
⚠️ 注意 MAX
不是 23:59:59
,而是接近 24:00:00
的最大值,避免边界遗漏
2.3. 使用 atTime()
LocalDate
还提供了 atTime()
方法,可以传入一个 LocalTime
实例:
LocalDateTime endOfDay = localDate.atTime(LocalTime.MAX);
效果和上面一样,代码更简洁。
你也可以手动指定时间:
LocalDateTime endOfDay = localDate.atTime(23, 59, 59);
但这种方式不够精确,**推荐优先使用 LocalTime.MAX
**。
2.4. 使用 LocalTime.atDate()
反过来,也可以用 LocalTime
的 atDate()
方法:
LocalDateTime endOfDate = LocalTime.MAX.atDate(localDate);
虽然不常用,但在某些函数式编程或流操作中可能更顺手。
3. 从 LocalDateTime 对象获取
如果你拿到的是一个 LocalDateTime
,比如:
LocalDateTime localDateTime = LocalDateTime.parse("2018-06-23T05:55:55");
想把它“归零”成当天的开始或结束时间,怎么办?
最简单的做法是先转成 LocalDate
,再用前面的方法:
LocalDateTime startOfDay = localDateTime.toLocalDate().atStartOfDay();
LocalDateTime endOfDay = localDateTime.toLocalDate().atTime(LocalTime.MAX);
但这需要两次对象转换。有没有更高效的方式?
3.1. 使用 with() 方法
✅ 所有实现 Temporal
接口的类都支持 with()
方法,可以直接修改时间字段。
我们可以直接设置“一天中的纳秒数”:
LocalDateTime startOfDay = localDateTime.with(ChronoField.NANO_OF_DAY, 0);
LocalDateTime endOfDay = localDateTime.with(ChronoField.NANO_OF_DAY, LocalTime.MAX.toNanoOfDay());
✅ 优点:无需创建中间对象,性能更好,一行搞定
⚠️ 注意:LocalTime.MAX.toNanoOfDay()
返回的是 86399999999999
,即 23:59:59.999999999 的纳秒值
这个方法适用于需要高性能或频繁操作时间的场景。
4. 从 ZonedDateTime 对象获取
如果时间带有时区信息(ZonedDateTime
),同样可以使用 with()
方法:
ZonedDateTime startOfDay = zonedDateTime.with(ChronoField.HOUR_OF_DAY, 0)
.with(ChronoField.MINUTE_OF_HOUR, 0)
.with(ChronoField.SECOND_OF_MINUTE, 0)
.with(ChronoField.NANO_OF_SECOND, 0);
或者更简单粗暴一点:
ZonedDateTime startOfDay = zonedDateTime.toLocalDate().atStartOfDay(zonedDateTime.getZone());
✅ 第二种写法更清晰,不易出错
⚠️ 第一种写法虽然灵活,但容易漏掉字段(比如忘了设纳秒),建议封装成工具方法
5. 总结
我们总结了几种获取一天开始和结束时间的常用方式:
来源类型 | 推荐方法 | 适用场景 |
---|---|---|
LocalDate |
atStartOfDay() / atTime(MAX) |
简单直接,最常用 |
LocalDateTime |
with(NANO_OF_DAY, ...) |
高性能,避免对象转换 |
ZonedDateTime |
toLocalDate().atStartOfDay(zone) |
带时区场景,语义清晰 |
📌 核心要点:
- ✅ 优先使用 Java 8 的
java.time
API - ✅ 获取“结束时间”时用
LocalTime.MAX
,避免23:59:59
的精度问题 - ✅ 带时区的操作务必保留
ZoneId
,防止夏令时等问题 - ❌ 不要手动拼字符串或用
Calendar
,容易出 Bug
所有示例代码均已上传至 GitHub 仓库,可随时查阅:
https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-8-datetime