问题描述
具体来说,当使用 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.Date
和 java.time
来说明意外行为,我知道 java.time
更好,但我仍然想了解 {{ 1}} 行为。
我运行的是 Java 14.0.2.12,但可以在 11.0.10.9 中重现。
解决方法
旧的日期时间 API(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
清楚地描述为: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
不会将其显示为几分之一秒;相反,它将 .
之后的数字表示为毫秒数。 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,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 desugaring 和 How to use ThreeTenABP in Android Project。从 Trail: Date Time 了解有关现代日期时间 API 的更多信息。