简体   繁体   中英

Converting a UTC ISO 8601 string to milliseconds gives a time of +12 hours

I am receiving a time from my server in UTC ISO8601 format like so:

2018-01-25T05:14:03.4973436

I am converting this string to milliseconds and I am generating a time elapsed value using my current time like so:

public static CharSequence getRelativeTimeSpan(String timeStamp) {

    DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
    format.setTimeZone(TimeZone.getTimeZone("UTC"));

    Date date = format.parse(timeStamp);

    long past = date.getTime();

    int offset = TimeZone.getDefault().getRawOffset() + TimeZone.getDefault().getDSTSavings();
    long now = System.currentTimeMillis() + offset;

    return DateUtils.getRelativeTimeSpanString(
            past,
            now,
            DateUtils.MINUTE_IN_MILLIS);
}

However, for some reason, the code produces an immediate difference of 12 hours between past and now . I'm not sure why there is such a difference but for instance this UTC string ( 2018-01-25T05:14:03.4973436 ) generates the following milliseconds value ( 1516857243000 ) which doesn't really correspond to the current time in milliseconds .

The interesting part of all of this is that there's this automatic 12 hour difference. I made sure that I use "HH" instead of "hh" to account for AM/PM differences so I don't know what's causing this discrepancy.

UPDATED USING JAVA8 time - still the same behavior

I have updated the code with the following snippet and I am getting the exact same behavior ( 12 hour immediate difference )

public static CharSequence getRelativeTimeSpan(String timeStamp) {
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSSS");
    LocalDateTime fromCustomPattern = LocalDateTime.parse(timeStamp, formatter);
    return DateUtils.getRelativeTimeSpanString(
            fromCustomPattern.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(),
            Instant.now().toEpochMilli(),
            DateUtils.MINUTE_IN_MILLIS);
}

Two issues:

  1. long millisecond times coming from Date and System.currentTimeInMillis() are in UTC. Adding timezone offsets is not necessary when comparing such values. You should not be adjusting for timezone like that.

  2. Date , SimpleDateFormat etc. only support milliseconds resolution but your source strings have 100 nanosecond resolution. There are implementations that would take the 4973436 in 2018-01-25T05:14:03.4973436 as milliseconds, resulting in 436 millis and overflowing the 4973000 to other fields. This happens on some older API levels, newer ones truncate/pad the millis part to exactly 3 digits.

    To fix that, you can truncate your inputs to 3 fractional digits yourself, or use newer java.time APIs (API 26+, backport available for example in ThreeTenABP library).

Of course it can be made to work with java.time, the modern Java date and time API also known as JSR-310:

public static CharSequence getRelativeTimeSpan(String timeStamp) {
    LocalDateTime fromIso8601 = LocalDateTime.parse(timeStamp);
    return DateUtils.getRelativeTimeSpanString(
            fromIso8601.atOffset(ZoneOffset.UTC).toInstant().toEpochMilli(),
            Instant.now().toEpochMilli(),
            DateUtils.MINUTE_IN_MILLIS);
}

You don't need an explicit formatter. The modern date and time classes parse ISO 8601 as their default. Since your date-time string is in UTC, convert it using atOffset(ZoneOffset.UTC) (not your default time zone, this must have been what gave you the wrong result).

To use the above code with ThreeTenABP, make sure you import from org.threeten.bp :

import org.threeten.bp.Instant;
import org.threeten.bp.LocalDateTime;
import org.threeten.bp.ZoneOffset;

Links

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM