1. 概述
在 Java 8 之前,java.util.Date
是表示日期时间最常用的类之一。
从 Java 8 开始,引入了 java.time.LocalDateTime
和 java.time.ZonedDateTime
,同时也提供了 java.time.Instant
来更精确地表示时间轴上的某个瞬间。
本文将介绍如何在 Java 中为指定的日期时间增加或减少 n 个小时。我们会先介绍 Java 自带的主流日期时间类,再看看一些第三方库的实现方式。
如果你对 Java 8 的日期时间 API 还不熟悉,建议先阅读 Java 8 日期时间入门。
2. 使用 java.util.Date
如果你还在使用 Java 7 或更低版本,处理日期时间主要依赖 java.util.Date
和 java.util.Calendar
类。
✅ 要为 Date
对象增加小时数,可以通过 Calendar
实现:
public Date addHoursToJavaUtilDate(Date date, int hours) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.add(Calendar.HOUR_OF_DAY, hours);
return calendar.getTime();
}
⚠️ 注意:这里使用的是 Calendar.HOUR_OF_DAY
,表示 24 小时制。如果用 Calendar.HOUR
,则是 12 小时制,容易踩坑。
这个方法会返回一个新的 Date
对象,增加或减少的小时数取决于你传入的 hours
是正数还是负数。
2.1 在 Java 8+ 中兼容 java.util.Date
即使你的项目已经是 Java 8+,但旧代码中仍大量使用 java.util.Date
,你可以借助新的时间 API 来简化操作:
- 调用
Date.toInstant()
转为Instant
- 使用
plus()
方法增加Duration
- 再通过
Date.from()
转回Date
示例:
@Test
public void givenJavaUtilDate_whenUsingToInstant_thenAddHours() {
Date actualDate = new GregorianCalendar(2018, Calendar.JUNE, 25, 5, 0)
.getTime();
Date expectedDate = new GregorianCalendar(2018, Calendar.JUNE, 25, 7, 0)
.getTime();
assertThat(Date.from(actualDate.toInstant().plus(Duration.ofHours(2))))
.isEqualTo(expectedDate);
}
❌ 但注意:**对于 Java 8+ 项目,强烈建议直接使用新的 java.time
API,而不是继续依赖 Date
和 Calendar
**,后者设计存在诸多缺陷,易出错且线程不安全。
3. 使用 java.time.LocalDateTime
/ ZonedDateTime
Java 8 的新时间 API 设计更清晰,操作也更直观。
✅ 无论是 LocalDateTime
还是 ZonedDateTime
,都提供了 plusHours()
方法,直接加小时:
@Test
public void givenLocalDateTime_whenUsingPlusHours_thenAddHours() {
LocalDateTime actualDateTime = LocalDateTime
.of(2018, Month.JUNE, 25, 5, 0);
LocalDateTime expectedDateTime = LocalDateTime
.of(2018, Month.JUNE, 25, 10, 0);
assertThat(actualDateTime.plusHours(5)).isEqualTo(expectedDateTime);
}
✅ 如果要减小时,推荐使用 minusHours()
,语义更清晰:
@Test
public void givenLocalDateTime_whenUsingMinusHours_thenSubtractHours() {
LocalDateTime actualDateTime = LocalDateTime
.of(2018, Month.JUNE, 25, 5, 0);
LocalDateTime expectedDateTime = LocalDateTime
.of(2018, Month.JUNE, 25, 3, 0);
assertThat(actualDateTime.minusHours(2)).isEqualTo(expectedDateTime);
}
⚠️ ZonedDateTime
的用法完全一致,注意它会自动处理时区相关的夏令时等问题,比 LocalDateTime
更适合跨时区场景。
4. 使用 java.time.Instant
Instant
表示时间轴上的一个瞬时点(UTC 时间),常用于日志打点、时间戳存储等场景。
✅ 要为 Instant
增加小时,可以使用 plus()
方法配合 ChronoUnit.HOURS
:
@Test
public void givenInstant_whenUsingAddHoursToInstant_thenAddHours() {
Instant actualValue = Instant.parse("2018-06-25T05:12:35Z");
Instant expectedValue = Instant.parse("2018-06-25T07:12:35Z");
assertThat(actualValue.plus(2, ChronoUnit.HOURS))
.isEqualTo(expectedValue);
}
✅ 同理,减小时用 minus()
:
instant.minus(3, ChronoUnit.HOURS);
⚠️ 注意:Instant
不包含时区信息,所有计算都基于 UTC,适合做时间差计算,但不适合直接展示给用户。
5. 使用 Apache Commons DateUtils
如果你的项目已经引入了 Apache Commons Lang,可以直接使用 DateUtils.addHours()
,简单粗暴:
public static Date addHours(Date date, int amount)
示例:
@Test
public void givenJavaUtilDate_whenUsingApacheCommons_thenAddHours() {
Date actualDate = new GregorianCalendar(2018, Calendar.JUNE, 25, 5, 0)
.getTime();
Date expectedDate = new GregorianCalendar(2018, Calendar.JUNE, 25, 7, 0)
.getTime();
assertThat(DateUtils.addHours(actualDate, 2)).isEqualTo(expectedDate);
}
✅ 优点:代码简洁,兼容老项目。
❌ 缺点:依赖第三方库,且操作对象仍是 Date
,不如直接升级到 java.time
。
最新版本可通过 Maven 引入:
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.12.0</version> </dependency>
6. 使用 Joda-Time
Joda-Time 是 Java 8 之前最受欢迎的日期时间库,设计直接影响了 java.time
API。
它的 DateTime
类也提供了 plusHours()
和 minusHours()
方法:
@Test
public void givenJodaDateTime_whenUsingPlusHoursToDateTime_thenAddHours() {
DateTime actualDateTime = new DateTime(2018, 5, 25, 5, 0);
DateTime expectedDateTime = new DateTime(2018, 5, 25, 7, 0);
assertThat(actualDateTime.plusHours(2)).isEqualTo(expectedDateTime);
}
⚠️ 注意:Joda-Time 官方已宣布归档,建议新项目直接使用 java.time
。老项目可逐步迁移。
Maven 依赖:
<dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>2.12.5</version> </dependency>
7. 总结
方式 | 适用场景 | 推荐程度 |
---|---|---|
java.util.Calendar |
Java 7 及以下 | ⚠️ 仅维护老项目 |
java.time.LocalDateTime/ZonedDateTime |
Java 8+ 新项目 | ✅ 强烈推荐 |
java.time.Instant |
时间戳、UTC 计算 | ✅ 推荐 |
Apache Commons DateUtils |
老项目快速开发 | ⚠️ 可用但不推荐长期依赖 |
Joda-Time | 遗留系统 | ❌ 不建议新项目使用 |
✅ 最佳实践:Java 8+ 项目统一使用 java.time
包下的类,避免使用 Date
和 Calendar
,代码更清晰,不易出错。
所有示例代码已上传至 GitHub:https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-date-operations-1