如何将十进制时间戳转换为 Java 中带有尾随小数的日期

How to convert decimal timestamp to date in Java with trailing decimals

我一直在试图弄清楚如何将时间戳转换为日期但末尾有尾随小数,例如: 时间戳 - C50204EC EC42EE92 相当于 2004 年 9 月 27 日 03:18:04.922896299 UTC.

时间戳格式包括作为跨越 136 年的字段的前 32 位无符号秒和解析 232 picoseconds 的 32 位小数字段。在时间戳格式中,原始纪元或纪元 0 的基准日期为 0 时 1900 年 1 月 1 日 UTC,此时所有位均为零。

到目前为止,这是我为代码编写的内容:

    BigDecimal bi = new BigDecimal("1096255084000");
    double decimal_timestamp = bi.doubleValue();

    DateFormat formatter = new SimpleDateFormat("dd/MM/yyyy hh:mm:ss.SSS");
    formatter.setTimeZone(TimeZone.getTimeZone("UTC"));

    Calendar calendar = Calendar.getInstance();
    calendar.setTimeInMillis(decimal_timestamp);
    String date = formatter.format(calendar.getTime());

    System.out.println(decimal_timestamp + " = " + date); 

我的想法是日历可能无法实现,所以我必须从头开始,但我不知道该怎么做。

java.time

使用解释中的示例:

Timestamp - C50204EC EC42EE92 is equivalent to Sep 27, 2004 03:18:04.922896299 UTC.

    Instant epoch = OffsetDateTime.of(1900, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC).toInstant();

    BigInteger timeStamp = new BigInteger("C50204ECEC42EE92", 16);

    // To get the whole part and the fraction right, divide by 2^32
    double secondsSince1900 = timeStamp.doubleValue() / 0x1_0000_0000L;

    // Convert seconds to nanos by multiplying by 1 000 000 000
    Instant converted = epoch.plusNanos(Math.round(secondsSince1900 * 1_000_000_000L));
    System.out.println(converted);

输出为:

2004-09-27T03:18:04.922896384Z

它关闭了 85 纳秒。可能更好的浮点运算可以做得更好。编辑:由于原始时间戳的分辨率为 2^-32 秒,精度是 Instant 的纳秒(10^-9 秒)分辨率的 4 倍以上,因此不可避免地会损失一点精度。

您尝试使用的 Calendar class 设计总是很糟糕,现在已经过时了。相反,我按照评论中建议的 Amongalen 做,我使用 java.time,现代 Java 日期和时间 API。编辑:为了比较,Calendar 具有毫秒分辨率,因此充其量只会给您带来精度的实质性损失。

编辑:更精确的数学

我不能让 85 纳秒成为现实。这是一个尽可能保持精度并给出预期结果的版本:

    BigDecimal timeStamp = new BigDecimal(new BigInteger("C50204ECEC42EE92", 16));

    // To get the whole part and the fraction right, divide by 2^32
    BigDecimal bit32 = new BigDecimal(0x1_0000_0000L);
    BigDecimal secondsSince1900 = timeStamp.divide(bit32);

    // Convert seconds to nanos by multiplying by 1 000 000 000; round to long
    long nanosSince1900 = secondsSince1900.multiply(new BigDecimal(TimeUnit.SECONDS.toNanos(1)))
            .setScale(0, RoundingMode.HALF_UP)
            .longValueExact();

    Instant converted = epoch.plusNanos(nanosSince1900);

2004-09-27T03:18:04.922896300Z

1纳米太多了?这是因为我在 setScale 的调用中使用了半向上舍入。相反,如果我截断(使用 RoundingMode.FLOOR),我会从解释中得到准确的结果。所以我的版本并没有比他们的更精确。

Link

Oracle tutorial: Date Time 解释如何使用 java.time。