SimpleDateFormat.parse 导致基于 Android 版本的非常不同的值

SimpleDateFormat.parse leads to very different values based on Android version

我已经为此苦恼了一会儿。我制作了一个小型示例应用程序,以确保我没有看到任何东西,或者这些值没有在我没有捕捉到的后期被操纵。

问题是,当我尝试用日期格式解析字符串日期时,根据设备 API 级别,我得到两个截然不同的结果。

DateFormat utcFormat;
String utcDate = "2017-01-31 18:58:12.2334924Z";
utcFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSSSSS'Z'");
utcFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
try{
    conversion.setText(utcFormat.parse(utcDate).toString());
}
catch (ParseException e){
    conversion.setText("exception encountered: " + e.getMessage());
}

这是我的结果:

On Android 4.4.2 device: Tue Jan 31 14:37:06 EST 2017
On Android 6.0.1 device: Tue Jan 31 13:58:12 EST 2017

如您所见,基于完全相同的输入,大约有 40 分钟的差异。设备区域设置相同(尽管我也尝试过对区域设置进行硬编码以确保),并且设备上的设置时间相同。

我是不是做错了什么?

符号 S 的一般问题是:java.util.Datejava.text.SimpleDateFormat 的精度最多限制为毫秒。 不支持更高的精度.因此,指定超过 3 个模式符号 SSS 没有意义。

此外,我试着做了一些研究,发现了这个:

pattern symbol table of official javadoc 将 "S" 指定为 "Millisecond"。这意味着,输入中点后面的值“2334924”将按字面意思解析为绝对毫秒数,结果约为 39 分钟。这解释了您在 Android v4.4.

上测试的第一个示例的解析时间

但是,已发布的 javadoc 在这个细节上似乎有点过时了。如果你 google 为 source code 那么你会在第 71 行找到:

 <tr> <td>{@code S}</td> 
 <td>fractional seconds</td>      
 <td>(Number)</td>      
 <td>978</td> </tr>

但是小数秒导致对文本的完全不同的解释 “2334924”,即 233 毫秒(加上忽略的更高精度部分)。这解释了 Android v6 上的第二个代码示例。我怀疑在 Android 两个版本之间的某个地方,Google 决定在没有任何文档的情况下更改行为。我能找到的最好的似乎是 issue 78859,它可能与这种行为变化有关。

ICU4J 似乎越来越多地集成到更新的 Android 版本中,也将符号 "S" 声明为小数秒。

解决方法和解决方案:

避免使用超过 3 个模式符号 S。而是使用(忽略更高精度的部分):

String input = "2017-01-31 18:58:12.2334924Z";
int dot = input.indexOf("."); 
String trimmed = input.substring(0, dot + 4) + "Z"; // 2017-01-31 18:58:12.233Z
DateFormat utcFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS'Z'");
utcFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
java.util.Date d = utcFormat.parse(trimmed);