1. 概述
在 Java 中处理时间戳是常见任务,尤其是在处理数据库或全球化应用时,它能让我们更有效地操作和显示日期时间信息。java.sql.Timestamp 和 ZonedDateTime 是处理时间戳和时区的两个核心类。
本文将探讨在 java.sql.Timestamp 和 ZonedDateTime 之间转换的多种方法。
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());
}
在上述方法中:
- 通过 Timestamp 类的 toInstant() 方法将时间戳转换为 Instant(表示 UTC 时间线上的一个点)
- 使用 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());
}
转换步骤:
- 通过 Calendar.getInstance() 初始化 Calendar 实例
- 将 Calendar 实例的时间设置为与 Timestamp 对象相同
- 使用 Calendar 对象的 toInstant() 方法获取 Instant
- 通过 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();
}
转换流程:
- 获取自纪元(1970-01-01T00:00:00Z)以来的毫秒数
- 使用默认时区创建 DateTime 对象
- 将 DateTime 转换为 GregorianCalendar
- 最终转换为 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);
}
转换步骤:
- 通过 toInstant() 方法将 ZonedDateTime 转换为 Instant
- 使用 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());
}
转换流程:
- 将 ZonedDateTime 转换为 Instant 并获取纪元毫秒数
- 使用默认时区创建 DateTime 对象
- 通过 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 中转换 ZonedDateTime 和 java.sql.Timestamp 的多种方法。关键点总结如下:
✅ 推荐方法:
- 优先使用 Java 8+ 的 Instant 和 LocalDateTime API(2.1/3.1 和 2.3/3.2)
- 这些方法简洁高效,且是标准库的一部分
⚠️ 注意事项:
- 避免使用旧版 Calendar 类(2.2),除非必须维护遗留代码
- 使用 Joda-Time(2.4/3.3)需额外引入依赖,适合已有 Joda-Time 的项目
❌ 常见误区:
- 混淆 LocalDateTime(无时区)和 ZonedDateTime(有时区)
- 忽略时区转换可能导致的时间偏差
本文所有代码可在 GitHub 获取。