在软件开发领域,大部分时间精确到秒或毫秒即可满足日常需求,但在某些对时间要求严格的场景中需要使用微秒、纳秒等更精确的时间值,本文简要记录如何在Java中通过LocalDateTime实现对于微秒、纳秒的精确解析以及转化为long
型时间戳。
java.util.Date中相关实现
格式化测试
首先采用采用如下代码验证java.util.Date
和java.text.SimpleDateFormat
对于时间戳的支持情况。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
public class TestDateConvert1 {
public static void main(String[] args) {
String text1 = "2023/01/04 17:54:38";
String text2 = "2023/01/04 17:54:38.610";
String text3 = "2023/01/04 17:54:38.610502";
String text4 = "2023/01/04 17:54:38.610502567";
try {
testDate1(text1);
testDate2(text2);
testDate3(text3);
testDate4(text4);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
public static void testDate1(String text) throws ParseException {
DateFormat df = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); //精确到秒
Date newDate = df.parse(text);
System.out.println("----------------精确到秒------------------------------");
System.out.println("原始时间:\t" + text);
System.out.println("解析后的时间:\t" + df.format(newDate));
System.out.println();
}
public static void testDate2(String text) throws ParseException {
DateFormat df = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSS"); // 精确到毫秒
Date newDate = df.parse(text);
System.out.println("----------------精确到毫秒----------------------------");
System.out.println("原始时间:\t" + text);
System.out.println("解析后的时间:\t" + df.format(newDate));
System.out.println();
}
public static void testDate3(String text) throws ParseException {
DateFormat df = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSSSSS"); //精确到微秒
Date newDate = df.parse(text);
System.out.println("----------------精确到微秒----------------------------");
System.out.println("原始时间:\t" + text);
System.out.println("解析后的时间:\t" + df.format(newDate));
System.out.println();
}
public static void testDate4(String text) throws ParseException {
DateFormat df = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSSSSSSSS"); //精确到微秒
Date newDate = df.parse(text);
System.out.println("----------------精确到纳秒----------------------------");
System.out.println("原始时间:\t" + text);
System.out.println("解析后的时间:\t" + df.format(newDate));
}
}
|
运行结果如下:
从上图中可以看出时间精确到微秒之后,采用java.util.Date
和java.text.SimpleDateFormat
进行输出时前后的结果已经不一致。同时也可以大致猜测,当使用java.util.Date
时不能用其进行微秒或纳秒等高精度的时间存储展示,但此问题是由java.util.Date
还是java.text.SimpleDateFormat
造成的暂不确定。
对比数据测试
为了测试结果的准确性,在www.timestamp-converter.com中获取一个可用于验证的时间戳信息如下所示
其中基于毫秒的时间戳为1676628010725
,格式化后的显示为2023-02-17T10:00:10.725Z
,采用下述代码进行验证:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public static void testDateCreate() {
DateFormat df1 = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
DateFormat df2 = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSS");
DateFormat df3 = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSSSSS");
DateFormat df4 = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSSSSSSSS");
long timestamp1 = 1676628010725L;//前述获取到的时间戳
Date date = new Date(timestamp1);
long timestamp2 = date.getTime();
System.out.println("timestamp1:\t" + timestamp1);
System.out.println("timestamp2:\t" + timestamp2);
System.out.println("精确到秒:\t" + df1.format(date));
System.out.println("精确到毫秒:\t" + df2.format(date));
System.out.println("精确到微秒:\t" + df3.format(date));
System.out.println("精确到纳秒:\t" + df4.format(date));
}
|
运行结果如下
从结果中可知微秒与纳秒的结果与网站中展示的不一致,但是仍然无法确定到底是java.util.Date
还是java.text.SimpleDateFormat
的原因。
在java.util.Date
的官方文档中可找到如下说明,从图中可知当采用时间戳来构造java.util.Date
时,其接收的参数为毫秒相对数据值,不支持微秒和纳秒。
在java.text.SimpleDateFormate
的官方文档中有如下说明,同样可知道java.text.SimpleDateFormat
不支持微秒和纳秒级别的时间精度。
初步结论:
java.util.Date
和java.text.SimpleDateFormat
均不支持微秒和纳秒级别的时间精度。
LocalDateTime中实现
由于JDK8
中引入了LocalDateTime
,故将最开始的测试代码都修改为使用java.time.LocalDateTime
和java.time.format.DateTimeFormatter
进行测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
public class TestDateConvert3 {
public static void main(String[] args) {
String text1 = "2023/01/04 17:54:38";
String text2 = "2023/01/04 17:54:38.610";
String text3 = "2023/01/04 17:54:38.610502";
String text4 = "2023/01/04 17:54:38.610502567";
try {
testDate1(text1);
testDate2(text2);
testDate3(text3);
testDate4(text4);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
public static void testDate1(String text) throws ParseException {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"); //精确到秒
LocalDateTime newDate = LocalDateTime.parse(text,formatter);
System.out.println("----------------精确到秒------------------------------");
System.out.println("原始时间:\t" + text);
System.out.println("解析后的时间:\t" + formatter.format(newDate));
System.out.println();
}
public static void testDate2(String text) throws ParseException {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss.SSS"); //精确到毫秒
LocalDateTime newDate = LocalDateTime.parse(text,formatter);
System.out.println("----------------精确到毫秒----------------------------");
System.out.println("原始时间:\t" + text);
System.out.println("解析后的时间:\t" + formatter.format(newDate));
System.out.println();
}
public static void testDate3(String text) throws ParseException {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss.SSSSSS"); //精确到微秒
LocalDateTime newDate = LocalDateTime.parse(text,formatter);
System.out.println("----------------精确到微秒----------------------------");
System.out.println("原始时间:\t" + text);
System.out.println("解析后的时间:\t" + formatter.format(newDate));
System.out.println();
}
public static void testDate4(String text) throws ParseException {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss.SSSSSSSSS"); //精确到纳秒
LocalDateTime newDate = LocalDateTime.parse(text,formatter);
System.out.println("----------------精确到纳秒----------------------------");
System.out.println("原始时间:\t" + text);
System.out.println("解析后的时间:\t" + formatter.format(newDate));
System.out.println();
}
}
|
测试结果如下:
从中可知当联合采用java.time.LocalDateTime
和java.time.format.DateTimeFormatter
时,能够支持到纳秒级别,可满足要求。
在java.time.LocalDateTime
的官方文档中有如下说明,从图中可知java.time.LocalDateTime
支持到纳秒级别的时间精度
在java.time.format.DateTimeFormatter
的官方文档中有如下说明,从图中可知java.time.format.DateTimeFormatter
也支持纳秒级别的格式化。
最终结论:
java.time.LocalDateTime
支持纳秒级别的时间存储,支持java.time.format.DateTimeFormatter
支持纳秒级别的时间显示。
LocalDateTime与时间戳互转
在java.util.Date
中可以很容易的通过Date().getTime()
和new Date(long timestamp)
来分别获取时间戳和基于时间戳构造时间,而在java.time.LocalDateTime
中要实现类似功能则稍微复杂点。
当要获取long类型的时间戳时,需要先获取秒,再获取微秒或纳秒,然后根据他们之前的换算关系进行累加返回,相关公式如下:
- $微秒时间戳=秒 \times 10^6 +微秒$
- $纳秒时间戳=秒 \times 10^9 +纳秒$
有了时间戳之后根据上述公式进行反向操作,分别获取秒与微秒(纳秒),然后根据这2个数值通过Instant
构建即可。
与微秒互转
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
public class TestDateConvert4 {
public static void main(String[] args) {
long time = convertTimeToMills("2023/01/04 17:54:38.610502");
convertMillsToTime(time);
}
public static long convertTimeToMills(String originalText) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss.SSSSSS");
System.out.println(originalText);
LocalDateTime dateTime = LocalDateTime.parse(originalText, formatter);
ZoneId zoneId = ZoneId.systemDefault();
Instant instant = dateTime.atZone(zoneId).toInstant();
long seconds = instant.getEpochSecond();
int micros = instant.get(ChronoField.MICRO_OF_SECOND);
long total = seconds * 1_000_000 + micros;
return total;
}
public static LocalDateTime convertMillsToTime(long time) {
long sec = time / 1_000_000;
long mic = time % 1_000_000;
Instant instant1 = Instant.ofEpochSecond(sec).plus(mic, ChronoUnit.MICROS);
ZoneId zoneId = ZoneId.systemDefault();
LocalDateTime localDateTime = LocalDateTime.ofInstant(instant1, zoneId);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss.SSSSSS");
System.out.println(formatter.format(localDateTime));
return localDateTime;
}
}
|
测试结果如下,符合预期
与纳秒互转
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
public class TestDateConvert5 {
public static void main(String[] args) {
long time = convertTimeToMills("2023/01/04 17:54:38.610502987");
convertMillsToTime(time);
}
public static long convertTimeToMills(String originalText) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss.SSSSSSSSS");
System.out.println(originalText);
LocalDateTime dateTime = LocalDateTime.parse(originalText, formatter);
ZoneId zoneId = ZoneId.systemDefault();
Instant instant = dateTime.atZone(zoneId).toInstant();
long seconds = instant.getEpochSecond();
int micros = instant.get(ChronoField.NANO_OF_SECOND);
long total = seconds * 1_000_000_000 + micros;
return total;
}
public static LocalDateTime convertMillsToTime(long time) {
long sec = time / 1_000_000_000;
long mic = time % 1_000_000_000;
Instant instant1 = Instant.ofEpochSecond(sec).plus(mic, ChronoUnit.NANOS);
ZoneId zoneId = ZoneId.systemDefault();
LocalDateTime localDateTime = LocalDateTime.ofInstant(instant1, zoneId);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss.SSSSSSSSS");
System.out.println(formatter.format(localDateTime));
return localDateTime;
}
}
|
测试结果如下,符合预期
参考文章: