I have been bashing my head over this for a little while. I made a small sample app to be sure I wasn't seeing things or the values weren't being manipulated at a later point that I was not catching.
The problem is that when I try to parse a String date with a date format, I get two very different results depending on device API level.
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());
}
Here are my results:
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
As you can see, there is a difference of about 40 minutes based on the exact same input. The device locales are the same (though I have also tried hardcoding a locale to be sure), and the set time on the devices are equal.
Am I doing something wrong here?
The general problem with the symbol S is: The precision of java.util.Date and java.text.SimpleDateFormat is constrained to milliseconds at best. Higher precisions are not supported. Therefore it does not make sense to specify more than 3 pattern symbols SSS.
Furthermore, I tried to do some research and found this:
The pattern symbol table of official javadoc specifies "S" as "Millisecond". This means, a value of "2334924" behind the dot in your input is literally parsed as absolute count of milliseconds resulting in about 39 minutes. This explains the parsed time of your first example tested on Android v4.4.
However, the published javadoc seems to be slightly outdated in this detail. If you google for the source code then you will find in line 71:
<tr> <td>{@code S}</td>
<td>fractional seconds</td>
<td>(Number)</td>
<td>978</td> </tr>
But fractional seconds cause a quite different interpretation of the text "2334924", namely as 233 milliseconds (plus an ignored fraction of higher precision). This explains your second code example on Android v6. I suspect that somewhere between both Android versions, Google had decided to change the behaviour without any documentation. The best what I could find seems to be the issue 78859 which might be related to this change in behaviour.
ICU4J which seems to be more and more integrated into newer Android versions also declares the symbol "S" as fractional second.
Workaround and solution:
Avoid using more than 3 pattern symbols S. Instead use (ignoring the higher precision part):
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);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With