1. 概述

在 Java 8 之前,java.util.Date 是表示日期时间最常用的类之一。

从 Java 8 开始,引入了 java.time.LocalDateTimejava.time.ZonedDateTime,同时也提供了 java.time.Instant 来更精确地表示时间轴上的某个瞬间。

本文将介绍如何在 Java 中为指定的日期时间增加或减少 n 个小时。我们会先介绍 Java 自带的主流日期时间类,再看看一些第三方库的实现方式。

如果你对 Java 8 的日期时间 API 还不熟悉,建议先阅读 Java 8 日期时间入门


2. 使用 java.util.Date

如果你还在使用 Java 7 或更低版本,处理日期时间主要依赖 java.util.Datejava.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 来简化操作:

  1. 调用 Date.toInstant() 转为 Instant
  2. 使用 plus() 方法增加 Duration
  3. 再通过 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,而不是继续依赖 DateCalendar**,后者设计存在诸多缺陷,易出错且线程不安全。


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 包下的类,避免使用 DateCalendar,代码更清晰,不易出错。

所有示例代码已上传至 GitHub:https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-date-operations-1


原始标题:Add Hours to a Date in Java