SimpleDateFormat使用&Quot;S"格式显示不正确的毫秒,但使用&Quot;SSS&Quot;格式不显示错误的毫秒

2022-08-24 00:00:00 date simpledateformat java

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

具体而言,52.050在使用.S格式时显示为52.50,但在使用.SSS格式时显示为52.050

以下面的代码为例:

// 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(), dateFormat,
        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更好,但我仍然希望了解SimpleDateFormat行为。

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


解决方案

旧的日期-时间接口(java.util日期-时间类型及其格式类型,SimpleDateFormat)过时且容易出错。建议完全停止使用,切换到java.time、modern date-time API*

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

1620946852050毫秒=1620946852000毫秒+50毫秒

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

50毫秒=(50/1000)秒=0.05秒。DateTimeFormatter也是这样呈现的。documentation清楚地将S描述为:。换句话说,它将其表示为小数点后9位(纳秒)的0.05的数学浮点数。

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

  • S:.0
  • SS:0.05
  • SSS:.050
  • SSSS:.0500以此类推
  • SSSSSSSSS:.050000000

这是我们小时候学数学分数时学到的表示法。

另一方面,SimpleDateFormat没有将其表示为秒的分数;相反,它将.之后的数字表示为毫秒数。documentation将S描述为:毫秒。因此,它将其表示为一个数学十进制整数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, dtf: %s%n", sdf, dtf.substring(0, 4));// Including two places for "0."
        System.out.format("sdf: %03d, dtf: %s%n", sdf, dtf.substring(0, 5));// Including two places for "0."
        System.out.format("sdf: %04d, dtf: %s%n", sdf, dtf.substring(0, 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 desugaring和How to use ThreeTenABP in Android Project。从Trail: Date Time了解有关现代日期-时间API的更多信息。

相关文章