2

I have a function that converts time from my server, it works fine on android api 24, but now im testing on KitKat (Api 19) and the time conversion does not work as it should. Both devices receive the same data, but the conversion is not the same. Both devices have "Automatic date and time" set, and the time is the same in both devices.

Received String from server:

(Api 19) -> CurrentTimeStamp:2018-02-23T15:50:15.9643834Z
(Api 24) -> CurrentTimeStamp:2018-02-23T15:50:15.9373834Z

Conversion

SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
try {
    Date dateServer = dateFormat.parse(currentTimeStamp);
    String currentDate = dateServer.toString();
} catch (ParseException e) {
    e.printStackTrace();
}

This prints:

(Api 19) -> DateServer:Fri Feb 23 12:26:28 CST 2018
(Api 24) -> DateServer:Fri Feb 23 09:50:15 CST 2018 <- this time is correct

Any help or ideas here would be appreciated

RonEskinder
  • 527
  • 8
  • 24
  • 3
    `SimpleDateFormat` does not really support higher precision than milliseconds, and you have 100 nanosecond precision. Java 8 java.time / ThreeTen backport does support nanoseconds, or you could just truncate the timestamps to millisecond resolution. – laalto Feb 23 '18 at 16:05
  • 1
    Well spotted, @laalto. API 19 (in agreement with the docs!) parses the number after the decimal point as 9643834 milliseoncds, that is, 2 hours 40 minutes 43 seconds 834 milliseconds. I don’t know why it doesn’t match your result exactly, maybe my calculation in my head is wrong. And it’s close. – Ole V.V. Feb 23 '18 at 16:11
  • that did the trick, nice spot @laalto! `currentTimeStamp = result.getPropertyAsString(CURRENT_SERVER_TIME); currentTimeStamp = String.format("%sZ", currentTimeStamp.split("\\.")[0]);` – RonEskinder Feb 23 '18 at 16:27

3 Answers3

2

SimpleDateFormat has a precision of milliseconds, so it can't handle more than 3 digits in the fraction of seconds. Check this answer: String-Date conversion with nanoseconds

Also, the "Z" in the end means the date is in UTC, so if you just treat it as a literal (inside quotes), this information (that it's in UTC) will be ignored (of course you do a workaround on this by setting the timezone in the formatter, but the correct would be to use the "X" pattern to parse it).

Anyway, to parse more than 3 fractional digits, you can use the ThreeTen Backport. Just follow the links in this answer to install it: How to add Duration to LocalDateTime for API level lower than 26

Or, if your API level already has java.time classes, much better. Anyway, the code is the same for both java.time and ThreeTen Backport:

Instant instant = Instant.parse("2018-02-23T15:50:15.9643834Z");

If you still need to work with java.util.Date, it's easy to convert:

// java.time
Date date = Date.from(instant);

// ThreeTenBackport
Date date = DateTimeUtils.toDate(instant);

Just remember that when converting to Date, only the first 3 fractional digits are kept, and the others are lost, due to the API's limitation.

dda
  • 36
  • 1
1

S format character designates milliseconds and not really any fraction of seconds. Your timestamps have 100 nanosecond resolution.

And then there are implementation differences:

Some fix options:

  • Java 8 java.time DateTimeFormatter supports nanoseconds but needs API 26 on Android. For older Androids there's ThreeTenABP.

  • You can just truncate the timestamp strings to millisecond resolution fractional seconds yourself before attempting to parse with SimpleDateFormat.

laalto
  • 150,114
  • 66
  • 286
  • 303
1

java.time

    String currentTimestamp = "2018-02-23T15:50:15.9643834Z";
    String currentDate = Instant.parse(currentTimestamp)
            .atZone(ZoneId.systemDefault())
            .toString();
    System.out.println(currentDate);

On my computer this prints

2018-02-23T16:50:15.964383400+01:00[Europe/Berlin]

If you want output in the same format as you got from Date.toString():

    DateTimeFormatter dateFormatter
            = DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss zzz yyyy", Locale.ENGLISH);
    String currentDate = Instant.parse(currentTimestamp)
            .atZone(ZoneId.systemDefault())
            .format(dateFormatter);

This gives:

Fri Feb 23 16:50:15 CET 2018

Both of the other answers are already giving java.time as the first way to fix your problem. I just wanted to elaborate a little bit on why and how.

  • The classes Date and SimpleDateFormat are long outdated. Because it was realized that they were poorly designed, their replacement came out in java.time with Java 8 in 2014. This modern API is so much nicer to work with.
  • SimpleDateFormat in particular is notoriously troublesome.
  • The old-fashioned classes (with the exception of java.sql.Timestamp) only support millisecond procision, so there is no way they could parse or just hold the full precision of your timestamps with 7 decimals on the seconds.
  • Your string is in ISO 8601 format. The modern classes parse and print this format as their default, that is, without any explicit formatter. And importantly, they have no problems in parsing anything from 0 to 9 decimals on the seconds correctly.

What went wrong in your code

API 19 (in agreement with the docs!) parses the number after the decimal point as 9643834 milliseoncds, that is, 2 hours 40 minutes 43 seconds 834 milliseconds. I was actually surprised to learn from your questions and from laalto’s answer in particular that this inadequate behaviour has been fixed in later Android versions. In my Java 9 it still rules.

Question: Can I use java.time on Android?

Yes, java.time works nicely on Android devices. It just requires at least Java 6.

  • In Java 8 and later and on new Android devices (from API level 26, I’m told) the new API comes built-in.
  • In Java 6 and 7 get the ThreeTen Backport, the backport of the new classes (ThreeTen for JSR 310, where the modern API was first described).
  • On (older) Android, use the Android edition of ThreeTen Backport. It’s called ThreeTenABP. Make sure you import the date and time classes from package org.threeten.bp and subpackages.

Links

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161