1. 概述

在本文中,我们将学习如何在 Java 中获取某一天的起始和结束时间,并通过简洁明了的代码示例覆盖多种常见场景。

我们使用的是 Java 8 引入的日期时间 API(java.time),这是目前处理时间最推荐的方式。如果你对 Java 8 的时间库还不太熟悉,可以先阅读相关入门文章打个基础。

✅ 使用 java.time 包下的类是现代 Java 开发的标准做法
❌ 避免使用过时的 DateCalendar 类,容易踩坑且代码难维护

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

另一种方式是组合 LocalDateLocalTime

LocalDateTime startOfDay = LocalDateTime.of(localDate, LocalTime.MIDNIGHT);

LocalTime 提供了几个常用的静态常量:

  • MIDNIGHT → 00:00:00
  • MIN → 同 MIDNIGHT
  • NOON → 12:00:00
  • MAX → 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()

反过来,也可以用 LocalTimeatDate() 方法:

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


原始标题:How to Get the Start and the End of a Day using Java