在软件开发领域,大部分时间精确到秒或毫秒即可满足日常需求,但在某些对时间要求严格的场景中需要使用微秒、纳秒等更精确的时间值,本文简要记录如何在Java中通过LocalDateTime实现对于微秒、纳秒的精确解析以及转化为long型时间戳。

java.util.Date中相关实现

格式化测试

首先采用采用如下代码验证java.util.Datejava.text.SimpleDateFormat对于时间戳的支持情况。

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));
    }

}

运行结果如下:

不同时间格式下的Date输出

从上图中可以看出时间精确到微秒之后,采用java.util.Datejava.text.SimpleDateFormat进行输出时前后的结果已经不一致。同时也可以大致猜测,当使用java.util.Date时不能用其进行微秒或纳秒等高精度的时间存储展示,但此问题是由java.util.Date还是java.text.SimpleDateFormat造成的暂不确定。

对比数据测试

为了测试结果的准确性,在www.timestamp-converter.com中获取一个可用于验证的时间戳信息如下所示

测试用的时间戳

其中基于毫秒的时间戳为1676628010725,格式化后的显示为2023-02-17T10:00:10.725Z,采用下述代码进行验证:

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));
}

运行结果如下

Date对比测试结果

从结果中可知微秒与纳秒的结果与网站中展示的不一致,但是仍然无法确定到底是java.util.Date还是java.text.SimpleDateFormat的原因。

java.util.Date官方文档中可找到如下说明,从图中可知当采用时间戳来构造java.util.Date时,其接收的参数为毫秒相对数据值,不支持微秒和纳秒。

java.util.Date构造方法说明

java.text.SimpleDateFormate官方文档中有如下说明,同样可知道java.text.SimpleDateFormat不支持微秒和纳秒级别的时间精度。

java.text.SimpleDateFormat日期时间格式说明

初步结论:

java.util.Datejava.text.SimpleDateFormat均不支持微秒和纳秒级别的时间精度。

LocalDateTime中实现

由于JDK8中引入了LocalDateTime,故将最开始的测试代码都修改为使用java.time.LocalDateTimejava.time.format.DateTimeFormatter进行测试

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.LocalDateTimejava.time.format.DateTimeFormatter时,能够支持到纳秒级别,可满足要求。

java.time.LocalDateTime官方文档中有如下说明,从图中可知java.time.LocalDateTime支持到纳秒级别的时间精度

java.time.LocalDateTime精确度说明

java.time.format.DateTimeFormatter官方文档中有如下说明,从图中可知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构建即可。

与微秒互转

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;
    }
    
}

测试结果如下,符合预期

微秒级别的时间戳转换

与纳秒互转

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;
    }

}

测试结果如下,符合预期

纳秒级别的时间戳转换

参考文章: