SimpleDateFormat 以“S”格式显示不正确的毫秒,但不以“SSS”格式显示

问题描述

我遇到了一个问题,显示日期的毫秒分量乘以 10。

具体来说,当使用 52.050 格式时,时间 52.50 显示.S,但使用 52.050 格式时显示.SSS

以下面的代码为例:

// Some arbitrary point with 50 milliseconds
final Date date = new Date(1620946852050 l);
final LocalDateTime localDateTime = LocalDateTime.ofInstant(date.toInstant(),ZoneId.systemDefault());

final String format = "%-40s%-20s%-20s%n";
System.out.format(format,"Date Formatter","Date Format","Formatted Output");

Stream.of("HH:mm:ss","HH:mm:ss.S","HH:mm:ss.SS","HH:mm:ss.SSS").forEach(dateFormat - > {
    System.out.println();
    System.out.format(format,SimpleDateFormat.class.getName(),dateFormat,new SimpleDateFormat(dateFormat).format(date));
    System.out.format(format,DateTimeFormatter.class.getName(),DateTimeFormatter.ofPattern(dateFormat).format(localDateTime));
});

这会产生以下输出

Date Formatter                          Date Format         Formatted Output    

java.text.SimpleDateFormat              HH:mm:ss            00:00:52            
java.time.format.DateTimeFormatter      HH:mm:ss            00:00:52            

java.text.SimpleDateFormat              HH:mm:ss.S          00:00:52.50         
java.time.format.DateTimeFormatter      HH:mm:ss.S          00:00:52.0          

java.text.SimpleDateFormat              HH:mm:ss.SS         00:00:52.50         
java.time.format.DateTimeFormatter      HH:mm:ss.SS         00:00:52.05         

java.text.SimpleDateFormat              HH:mm:ss.SSS        00:00:52.050        
java.time.format.DateTimeFormatter      HH:mm:ss.SSS        00:00:52.050        

我已经使用 java.util.Datejava.time 来说明意外行为,我知道 java.time 更好,但我仍然想了解 {{ 1}} 行为。

我运行的是 Java 14.0.2.12,但可以在 11.0.10.9 中重现。

解决方法

旧的日期时间 API(java.util 日期时间类型及其格式类型 SimpleDateFormat)已过时且容易出错。建议完全停止使用,改用java.timemodern date-time API*

让我们了解 SimpleDateFormat 产生的结果如何令人困惑(因此容易出错)。

1620946852050 毫秒 = 1620946852000 毫秒 + 50 毫秒

对于 1620946852000,System.out.println(localDateTime) 将产生结果 2021-05-14T00:00:52

50 毫秒 = (50 / 1000) 秒 = 0.05 秒。这也是 DateTimeFormatter 的呈现方式。 documentationS 清楚地描述为:fraction-of-second。换句话说,它将其表示为 0.05 至小数点 9 位(纳秒)的数学浮点数。

您可以这样理解:在 String.valueOf(0.05) 的右侧填充零以将精度提高到小数点后 9 位。因此,它变成"0.050000000"。现在,根据S的个数,得到"0.050000000"的子串。请注意,您最多只能在 9 个位置执行此操作,即 SSSSSSSSSS 将引发异常。

  • S:.0
  • SS:.05
  • SSS:.050
  • SSSS:.0500 等等
  • SSSSSSSSS:.050000000

这是我们小时候在数学中学习分数时学到的表示。

另一方面,SimpleDateFormat 不会将其显示为几分之一秒;相反,它将 . 之后的数字表示为毫秒数。 documentationS 描述为:毫秒。因此,它将其表示为 50 的数学十进制整数。这会让人感到困惑,因为我们一看到 .,就会想到分数,而 SimpleDateFormat 认为它只是秒和毫秒的分隔符。

以下示例说明了这些不同的呈现方式:

public class Main {
    public static void main(String[] args) {
        int sdf = 50;
        String dtf = String.format("%.9f",0.05);

        System.out.format("sdf: %01d,dtf: %s%n",sdf,dtf.substring(0,3));// Including two places for "0."
        System.out.format("sdf: %02d,4));// Including two places for "0."
        System.out.format("sdf: %03d,5));// Including two places for "0."
        System.out.format("sdf: %04d,6));// Including two places for "0."
    }
}

输出:

sdf: 50,dtf: 0.0
sdf: 50,dtf: 0.05
sdf: 050,dtf: 0.050
sdf: 0050,dtf: 0.0500

* 出于任何原因,如果您必须坚持使用 Java 6 或 Java 7,您可以使用 ThreeTen-Backport,它将大部分 java.time 功能向后移植到 Java 6 & 7. 如果您正在为 Android 项目工作并且您的 Android API 级别仍然不符合 Java-8,请检查 Java 8+ APIs available through desugaringHow to use ThreeTenABP in Android Project。从 Trail: Date Time 了解有关现代日期时间 API 的更多信息。