1. 概述
在这个快速教程中,我们将学习如何解析来自Unix时间戳的日期表示。Unix时间是自1970年1月1日以来经过的秒数。然而,时间戳可以精确到纳秒。因此,我们将探讨可用的工具,并创建一个方法,将任何范围的时间戳转换为Java对象。
2. 旧方法(Java 8之前)
在Java 8之前,我们的最简单选择是Date
和Calendar
类。Date
类有一个直接接受毫秒级时间戳的构造函数:
public static Date dateFrom(long input) {
return new Date(input);
}
使用Calendar
,我们需要在getInstance()
后调用setTimeInMillis()
:
public static Calendar calendarFrom(long input) {
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(input);
return calendar;
}
换句话说,我们必须知道输入的时间戳是以秒、纳秒还是介于两者之间的其他精度。然后,我们不得不手动将时间戳转换为毫秒。
3. 新方法(Java 8+)
Java 8引入了Instant
类。这个类有一些实用方法,可以从秒和毫秒创建实例。其中的一个方法接受一个纳秒调整参数:
Instant.ofEpochSecond(seconds, nanos);
但我们仍然必须提前知道时间戳的精度。例如,如果我们知道时间戳是纳秒级的,就需要进行一些计算:
public static Instant fromNanos(long input) {
long seconds = input / 1_000_000_000;
long nanos = input % 1_000_000_000;
return Instant.ofEpochSecond(seconds, nanos);
}
首先,我们将时间戳除以十亿得到秒数,然后使用余数获取秒后的部分。
4. 使用Instant的通用解决方案
为了避免额外的工作,让我们创建一个方法,可以将任何输入转换为毫秒,大多数类都可以解析。首先,我们要检查时间戳所在的范围,然后执行计算来提取毫秒。此外,我们将使用科学记数法使条件更易于阅读。
还要记住,时间戳是有符号的,所以我们必须检查正负范围(负时间戳表示它们是从1970年开始倒计时的)。
首先,让我们检查输入是否为纳秒:
private static long millis(long timestamp) {
if (millis >= 1E16 || millis <= -1E16) {
return timestamp / 1_000_000;
}
// next range checks
}
首先,我们检查它是否在1E16
范围内,即一个后面跟着16个零。负值代表1970年之前的日期,所以我们也需要检查它们。然后,我们将值除以一百万以得到毫秒。
类似地,微秒在1E14
范围内。这次,我们将除以一千:
if (timestamp >= 1E14 || timestamp <= -1E14) {
return timestamp / 1_000;
}
当我们的值在1E11
到-3E10
范围内时,我们不需要做任何改变。这意味着我们的输入已经是毫秒级精度:
if (timestamp >= 1E11 || timestamp <= -3E10) {
return timestamp;
}
最后,如果输入不属于这些范围,那么它必须是秒级的,因此我们需要将其转换为毫秒:
return timestamp * 1_000;
4.1. 将输入标准化为Instant
现在,让我们创建一个方法,使用Instant.ofEpochMilli()
从任何精度的输入返回Instant
:
public static Instant fromTimestamp(long input) {
return Instant.ofEpochMilli(millis(input));
}
请注意,每次我们除以或乘以值时,都会丢失精度。
4.2. 使用LocalDateTime
处理本地时间
Instant
表示一个时间点,但如果没有时区信息,它不易读,因为它依赖于我们所在的世界位置。因此,让我们创建一个方法生成本地时间表示。我们将使用UTC来避免测试中的不同结果:
public static LocalDateTime localTimeUtc(Instant instant) {
return LocalDateTime.ofInstant(instant, ZoneOffset.UTC);
}
现在,我们可以看到,当方法期望特定格式时,使用错误精度可能导致完全不同的日期。首先,让我们传递一个已知正确日期的纳秒级时间戳,但将其转换为微秒并使用我们之前创建的fromNanos()
方法:
@Test
void givenWrongPrecision_whenInstantFromNanos_thenUnexpectedTime() {
long microseconds = 1660663532747420283l / 1000;
Instant instant = fromNanos(microseconds);
String expectedTime = "2022-08-16T15:25:32";
LocalDateTime time = localTimeUtc(instant);
assertThat(!time.toString().startsWith(expectedTime));
assertEquals("1970-01-20T05:17:43.532747420", time.toString());
}
当我们使用之前子节创建的fromTimestamp()
方法时,这个问题就不会发生:
@Test
void givenMicroseconds_whenInstantFromTimestamp_thenLocalTimeMatches() {
long microseconds = 1660663532747420283l / 1000;
Instant instant = fromTimestamp(microseconds);
String expectedTime = "2022-08-16T15:25:32";
LocalDateTime time = localTimeUtc(instant);
assertThat(time.toString().startsWith(expectedTime));
}
5. 总结
在这篇文章中,我们学习了如何使用核心Java类转换时间戳。然后,我们看到了它们可能具有不同的精度级别,以及这如何影响我们的结果。最后,我们创建了一个简单的方法来标准化输入并获得一致的结果。
如往常一样,源代码可在GitHub上找到。