1. 概述

在 Java 中处理时间戳是常见任务,尤其是在处理数据库或全球化应用时,它能让我们更有效地操作和显示日期时间信息。java.sql.TimestampZonedDateTime 是处理时间戳和时区的两个核心类。

本文将探讨在 java.sql.TimestampZonedDateTime 之间转换的多种方法。

2. 将 java.sql.Timestamp 转换为 ZonedDateTime

首先我们来看几种将 java.sql.Timestamp 转换为 ZonedDateTime 的方法。

2.1. 使用 Instant

简单理解,*Instant* 类就是 UTC 时区下的一个时间点。如果将时间看作一条线,Instant 就是线上的一个点。

其底层实现是计算相对于标准 Unix 纪元时间(1970年1月1日 00:00:00 UTC)的秒数和纳秒数。这个时间点记为 0 秒 0 纳秒,其他时间都是相对于它的偏移量。

通过存储相对于这个特定时间点的秒数和纳秒数,该类可以表示负偏移(纪元前)和正偏移(纪元后)。换句话说,*Instant* 类可以表示纪元前后的时间

以下是使用 Instant 类转换的代码:

ZonedDateTime convertToZonedDateTimeUsingInstant(Timestamp timestamp) {
    Instant instant = timestamp.toInstant();
    return instant.atZone(ZoneId.systemDefault());
}

在上述方法中:

  1. 通过 Timestamp 类的 toInstant() 方法将时间戳转换为 Instant(表示 UTC 时间线上的一个点)
  2. 使用 atZone() 方法将 Instant 关联到特定时区(这里使用系统默认时区)

测试代码:

@Test
void givenTimestamp_whenUsingInstant_thenConvertToZonedDateTime() {
    Timestamp timestamp = Timestamp.valueOf("2024-04-17 12:30:00");
    ZonedDateTime actualResult = TimestampAndZonedDateTimeConversion.convertToZonedDateTimeUsingInstant(timestamp);
    ZonedDateTime expectedResult = ZonedDateTime.of(2024, 4, 17, 12, 30, 0, 0, ZoneId.systemDefault());
    Assertions.assertEquals(expectedResult.toLocalDate(), actualResult.toLocalDate());
    Assertions.assertEquals(expectedResult.toLocalTime(), actualResult.toLocalTime());
}

2.2. 使用 Calendar

另一种方法是使用旧版 Date API 中的 Calendar 类。该类提供了 setTimeInMillis(long value) 方法,可用于设置时间

ZonedDateTime convertToZonedDateTimeUsingCalendar(Timestamp timestamp) {
    Calendar calendar = Calendar.getInstance();
    calendar.setTimeInMillis(timestamp.getTime());
    return calendar.toInstant().atZone(ZoneId.systemDefault());
}

转换步骤:

  1. 通过 Calendar.getInstance() 初始化 Calendar 实例
  2. Calendar 实例的时间设置为与 Timestamp 对象相同
  3. 使用 Calendar 对象的 toInstant() 方法获取 Instant
  4. 通过 atZone() 关联到系统默认时区

测试代码:

@Test
void givenTimestamp_whenUsingCalendar_thenConvertToZonedDateTime() {
    Timestamp timestamp = Timestamp.valueOf("2024-04-17 12:30:00");
    ZonedDateTime actualResult = TimestampAndZonedDateTimeConversion.convertToZonedDateTimeUsingCalendar(timestamp);
    ZonedDateTime expectedResult = ZonedDateTime.of(2024, 4, 17, 12, 30, 0, 0, ZoneId.systemDefault());
    Assertions.assertEquals(expectedResult.toLocalDate(), actualResult.toLocalDate());
    Assertions.assertEquals(expectedResult.toLocalTime(), actualResult.toLocalTime());
}

2.3. 使用 LocalDateTime

Java 8 引入的 java.timе 包提供了现代化的日期时间 API。LоcalDatеTimе 是其中核心类之一,可存储和操作不同时区的日期时间。转换方法如下:

ZonedDateTime convertToZonedDateTimeUsingLocalDateTime(Timestamp timestamp) {
    LocalDateTime localDateTime = timestamp.toLocalDateTime();
    return localDateTime.atZone(ZoneId.systemDefault());
}

关键点:

  • Timestamp 类的 toLocalDateTime() 方法将其转换为不带时区信息的 LocalDateTime
  • 通过 atZone() 方法关联到系统默认时区

测试代码:

@Test
void givenTimestamp_whenUsingLocalDateTime_thenConvertToZonedDateTime() {
    Timestamp timestamp = Timestamp.valueOf("2024-04-17 12:30:00");
    ZonedDateTime actualResult = TimestampAndZonedDateTimeConversion.convertToZonedDateTimeUsingLocalDateTime(timestamp);
    ZonedDateTime expectedResult = ZonedDateTime.of(2024, 4, 17, 12, 30, 0, 0, ZoneId.systemDefault());
    Assertions.assertEquals(expectedResult.toLocalDate(), actualResult.toLocalDate());
    Assertions.assertEquals(expectedResult.toLocalTime(), actualResult.toLocalTime());
}

2.4. 使用 Joda-Time

Joda-Time 是非常流行的 Java 日期时间处理库,其 API 比标准 DateTime 类更直观灵活。

首先添加 Maven 依赖(来自 Maven Central):

<dependency> 
    <groupId>joda-time</groupId> 
    <artifactId>joda-time</artifactId> 
    <version>2.12.7</version> 
</dependency>

转换实现:

ZonedDateTime convertToZonedDateTimeUsingJodaTime(Timestamp timestamp) {
    DateTime dateTime = new DateTime(timestamp.getTime());
    return dateTime.toGregorianCalendar().toZonedDateTime();
}

转换流程:

  1. 获取自纪元(1970-01-01T00:00:00Z)以来的毫秒数
  2. 使用默认时区创建 DateTime 对象
  3. DateTime 转换为 GregorianCalendar
  4. 最终转换为 ZonedDateTime

测试代码:

@Test
void givenTimestamp_whenUsingJodaTime_thenConvertToZonedDateTime() {
    Timestamp timestamp = Timestamp.valueOf("2024-04-17 12:30:00");
    ZonedDateTime actualResult = TimestampAndZonedDateTimeConversion.convertToZonedDateTimeUsingJodaTime(timestamp);
    ZonedDateTime expectedResult = ZonedDateTime.of(2024, 4, 17, 12, 30, 0, 0, ZoneId.systemDefault());
    Assertions.assertEquals(expectedResult.toLocalDate(), actualResult.toLocalDate());
    Assertions.assertEquals(expectedResult.toLocalTime(), actualResult.toLocalTime());
}

3. 将 ZonedDateTime 转换为 java.sql.Timestamp

现在来看反向转换的几种方法。

3.1. 使用 Instant

使用 Instant 类的转换方法:

Timestamp convertToTimeStampUsingInstant(ZonedDateTime zonedDateTime) {
    Instant instant = zonedDateTime.toInstant();
    return Timestamp.from(instant);
}

转换步骤:

  1. 通过 toInstant() 方法将 ZonedDateTime 转换为 Instant
  2. 使用 Timestamp.from() 创建 Timestamp 对象

测试代码:

@Test
void givenZonedDateTime_whenUsingInstant_thenConvertToTimestamp() {
    ZonedDateTime zonedDateTime = ZonedDateTime.of(2024, 4, 17, 12, 30, 0, 0, ZoneId.systemDefault());
    Timestamp actualResult = TimestampAndZonedDateTimeConversion.convertToTimeStampUsingInstant(zonedDateTime);
    Timestamp expectedResult = Timestamp.valueOf("2024-04-17 12:30:00");
    Assertions.assertEquals(expectedResult, actualResult);
}

3.2. 使用 LocalDateTime

使用 LocalDateTime 的转换方法:

Timestamp convertToTimeStampUsingLocalDateTime(ZonedDateTime zonedDateTime) {
    LocalDateTime localDateTime = zonedDateTime.toLocalDateTime();
    return Timestamp.valueOf(localDateTime);
}

关键点:

  • *LocalDateTime* 表示不带时区的日期时间
  • 通过 toLocalDateTime() 转换后,使用 Timestamp.valueOf() 创建时间戳

测试代码:

@Test
void givenZonedDateTime_whenUsingLocalDateTime_thenConvertToTimestamp() {
    ZonedDateTime zonedDateTime = ZonedDateTime.of(2024, 4, 17, 12, 30, 0, 0, ZoneId.systemDefault());
    Timestamp actualResult = TimestampAndZonedDateTimeConversion.convertToTimeStampUsingLocalDateTime(zonedDateTime);
    Timestamp expectedResult = Timestamp.valueOf("2024-04-17 12:30:00");
    Assertions.assertEquals(expectedResult, actualResult);
}

3.3. 使用 Joda-Time

使用 Joda-Time 的转换方法:

Timestamp convertToTimestampUsingJodaTime(ZonedDateTime zonedDateTime) {
    DateTime dateTime = new DateTime(zonedDateTime.toInstant().toEpochMilli());
    return new Timestamp(dateTime.getMillis());
}

转换流程:

  1. ZonedDateTime 转换为 Instant 并获取纪元毫秒数
  2. 使用默认时区创建 DateTime 对象
  3. 通过 getMillis() 获取毫秒数创建 Timestamp

测试代码:

@Test
void givenZonedDateTime_whenUsingJodaDateTime_thenConvertToTimestamp() {
    ZonedDateTime zonedDateTime = ZonedDateTime.of(2024, 4, 17, 12, 30, 0, 0, ZoneId.systemDefault());
    Timestamp actualResult = TimestampAndZonedDateTimeConversion.convertToTimestampUsingJodaTime(zonedDateTime);
    Timestamp expectedResult = Timestamp.valueOf("2024-04-17 12:30:00");
    Assertions.assertEquals(expectedResult, actualResult);
}

4. 总结

本文介绍了在 Java 中转换 ZonedDateTimejava.sql.Timestamp 的多种方法。关键点总结如下:

推荐方法

  • 优先使用 Java 8+ 的 InstantLocalDateTime API(2.1/3.1 和 2.3/3.2)
  • 这些方法简洁高效,且是标准库的一部分

⚠️ 注意事项

  • 避免使用旧版 Calendar 类(2.2),除非必须维护遗留代码
  • 使用 Joda-Time(2.4/3.3)需额外引入依赖,适合已有 Joda-Time 的项目

常见误区

  • 混淆 LocalDateTime(无时区)和 ZonedDateTime(有时区)
  • 忽略时区转换可能导致的时间偏差

本文所有代码可在 GitHub 获取。


原始标题:How to Convert Between java.sql.Timestamp and ZonedDateTime in Java