1. Introduction
1.绪论
In this quick tutorial, we’ll learn how to parse representations of dates from a Unix timestamp. Unix time is the number of seconds elapsed since January 1, 1970. However, a timestamp can represent time down to nanosecond precision. So, we’ll see the tools available and create a method to convert timestamps of any range to a Java object.
在这个快速教程中,我们将学习如何从Unix时间戳中解析日期的表示。Unix时间是指自1970年1月1日以来所经过的秒数。然而,一个时间戳可以代表精确到纳秒的时间。因此,我们将看到可用的工具,并创建一个方法,将任何范围的时间戳转换为一个Java对象。
2. Old Way (Before Java 8)
2.旧方法(Java 8之前)
Before Java 8, our simplest options were Date and Calendar. The Date class has a constructor that directly accepts a timestamp in milliseconds:
在Java 8之前,我们最简单的选择是Date和Calendar。Date类有一个构造函数,直接接受以毫秒为单位的时间戳。
public static Date dateFrom(long input) {
return new Date(input);
}
With Calendar, we have to call setTimeInMillis() after getInstance():
对于Calendar,我们必须在getInstance()之后调用setTimeInMillis() 。
public static Calendar calendarFrom(long input) {
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(input);
return calendar;
}
In other words, we must know if our input is in seconds, nanoseconds, or any other precision in between. Then, we have to convert our timestamp to milliseconds manually.
换句话说,我们必须知道我们的输入单位是秒、纳秒或介于两者之间的任何其他精度。然后,我们必须手动将我们的时间戳转换为毫秒。
3. New Way (Java 8+)
3.新方法(Java 8以上)
Java 8 introduced Instant. This class has utility methods to create instances from seconds and milliseconds. Also, one of them accepts a nanoseconds adjustment parameter:
Java 8引入了Instant。这个类有一些实用的方法,可以从秒和毫秒中创建实例。而且,其中一个方法还接受纳秒的调整参数。
Instant.ofEpochSecond(seconds, nanos);
But we still must know in advance the precision of our timestamp. So, for example, some calculations are needed if we know our timestamp is in nanoseconds:
但我们仍然必须事先知道我们的时间戳的精度。因此,例如,如果我们知道我们的时间戳是纳秒级的,就需要进行一些计算。
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);
}
First, we divide our timestamp by one billion to get the seconds. Then, we use its remainder to get the part after seconds.
首先,我们用我们的时间戳除以10亿来得到秒。然后,我们使用它的余数来得到秒之后的部分。
4. Universal Solution With Instant
4.具有即时性的通用解决方案
To avoid extra work, let’s create a method that can convert any input to milliseconds, which most classes can parse. Firstly, we check in what range our timestamp is. Then, we perform calculations to extract the milliseconds. Moreover, we’ll use scientific notations to make our conditions more readable.
为了避免额外的工作,让我们创建一个可以将任何输入转换为毫秒的方法,大多数类都可以解析。首先,我们检查我们的时间戳是在哪个范围。然后,我们进行计算以提取毫秒数。此外,我们将使用科学符号来使我们的条件更易读。
Also, remember that timestamps are signed, so we have to check both the positive and negative ranges (negative timestamps mean they’re counted backward from 1970).
另外,请记住,时间戳是有符号的,所以我们必须同时检查正负范围(负的时间戳意味着它们是从1970年开始往后计算的)。
So, let’s start by checking if our input is in nanoseconds:
所以,让我们开始检查我们的输入是否是纳秒。
private static long millis(long timestamp) {
if (millis >= 1E16 || millis <= -1E16) {
return timestamp / 1_000_000;
}
// next range checks
}
First, we check if it’s in the 1E16 range, which is one followed by 16 zeroes. Negative values represent dates before 1970, so we also have to check them. Then, we divide our value by one million to get to milliseconds.
首先,我们检查它是否在1E16范围内,也就是1后面有16个零。负值代表1970年以前的日期,所以我们也要检查它们。然后,我们用我们的值除以100万,得到毫秒数。
Similarly, microseconds are in the 1E14 range. This time, we divide by one thousand:
同样地,微秒也在1E14范围内。这一次,我们除以一千。
if (timestamp >= 1E14 || timestamp <= -1E14) {
return timestamp / 1_000;
}
We don’t need to change anything when our value is in the 1E11 to -3E10 range. That means our input is already in milliseconds precision:
当我们的值在1E11到-3E10范围内时,我们不需要改变任何东西。这意味着我们的输入已经达到了毫秒级的精度。
if (timestamp >= 1E11 || timestamp <= -3E10) {
return timestamp;
}
Finally, if our input isn’t any of these ranges, then it must be in seconds, so we need to convert this to milliseconds:
最后,如果我们的输入不是这些范围中的任何一个,那么它必须是以秒为单位的,所以我们需要将其转换为毫秒。
return timestamp * 1_000;
4.1. Normalizing Input for Instant
4.1.对Instant进行归一化输入
Now, let’s create a method that returns an Instant from input in any precision with Instant.ofEpochMilli():
现在,让我们创建一个方法,用Instant.ofEpochMilli()从任何精度的输入中返回一个Instant。
public static Instant fromTimestamp(long input) {
return Instant.ofEpochMilli(millis(input));
}
Note that every time we divide or multiply values, precision is lost.
请注意,每当我们对数值进行除法或乘法时,就会失去精确度。
4.2. Local Time With LocalDateTime
4.2.使用LocalDateTime的当地时间
An Instant represents a moment in time. But, without a time zone, it’s not easily readable, as it depends on our location in the world. So, let’s create a method to generate a local time representation. We’ll use the UTC to avoid different results in our tests:
一个Instant代表一个时间点。但是,如果没有时区,它就不容易读懂,因为它取决于我们在世界上的位置。所以,让我们创建一个方法来生成一个本地时间表示。我们将使用UTC,以避免在我们的测试中出现不同的结果。
public static LocalDateTime localTimeUtc(Instant instant) {
return LocalDateTime.ofInstant(instant, ZoneOffset.UTC);
}
Now, we can test how using wrong precisions can result in entirely different dates when methods expect specific formats. First, let’s pass a timestamp in nanoseconds we already know the correct date for, but convert it to microseconds and use the fromNanos() method we created earlier:
现在,我们可以测试一下,当方法期待特定的格式时,使用错误的精度会导致完全不同的日期。首先,让我们传递一个我们已经知道正确日期的纳秒级时间戳,但将其转换为微秒级并使用我们之前创建的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());
}
This problem won’t happen when we use the fromTimestamp() method we created in the previous subsection:
当我们使用上一小节中创建的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. Conclusion
5.总结
In this article, we learned how to convert timestamps with core Java classes. Then, we saw how they can have different levels of precision and how that affects our results. Lastly, we created a simple way to normalize our input and get consistent results.
在这篇文章中,我们学习了如何使用核心Java类转换时间戳。然后,我们看到了它们如何具有不同的精度水平,以及这对我们的结果有何影响。最后,我们创建了一个简单的方法来规范我们的输入并获得一致的结果。
And, as always, the source code is available over on GitHub.
而且,像往常一样,源代码可以在GitHub上获得超过。