Alex Rmcf
Alex Rmcf

Reputation: 924

Android device system time changes TimeZone of server date string when converting it to Date

I have to check if difference between time on server and Android device system time more then 1 hour. For now I have this string from server:

2020-08-24T11:50:18.613+0300

these is constant I use for SimpleDateFormat:

private static final String SYSTEM_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"

problem is when I try to convert string date to Date class I am getting this:

Mon Aug 24 13:50:18 GMT+05:00 2020

As I understand +05:00 there is because it is time zone which set at Android device as default. That is how I get this Date:

Locale locale =  new Locale("ru","RU");
DateFormat df = new SimpleDateFormat(SYSTEM_FORMAT, locale);
df.setTimeZone(TimeZone.getTimeZone("Europe/Moscow"));
Date date = df.parse(serverDate);

As you can see even setting time zone to +3 (Moscow time) does not make Date which I expect. I know I can compare it using just string, but demands are to compare Dates

Upvotes: 1

Views: 1879

Answers (3)

Anonymous
Anonymous

Reputation: 86276

There’s no need to worry about offset from UTC or time zone when comparing times. Comparison across offsets goes smoothly.

java.time

I am using java.time, the modern Java date and time API. Let’s first define a couple of useful constants:

private static final Duration TOLERANCE = Duration.ofHours(1);

private static final DateTimeFormatter serverFormatter = new DateTimeFormatterBuilder()
        .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
        .appendPattern("XX")
        .toFormatter();

Now we can do:

    String serverDateTimeString = "2020-08-24T11:50:18.613+0300";
    OffsetDateTime serverTime = OffsetDateTime.parse(serverDateTimeString, serverFormatter);
    System.out.println("Server time: " + serverTime);
    
    ZoneId deviceTimeZone = ZoneId.of("Asia/Yekaterinburg");
    OffsetDateTime deviceTime = OffsetDateTime.now(deviceTimeZone);
    System.out.println("Device time: " + deviceTime);
    
    if (deviceTime.isBefore(serverTime.minus(TOLERANCE)) || deviceTime.isAfter(serverTime.plus(TOLERANCE))) {
        System.out.println("Difference between time on server and Android device system time more than 1 hour");
    } else {
        System.out.println("Difference between time on server and Android device system time 1 hour or less");
    }

I have used your string from the question, so we shouldn’t be surprised that the snippet tells us there’s a great difference between the two times when I run it now:

Server time: 2020-08-24T11:50:18.613+03:00
Device time: 2020-08-24T23:08:57.939565+05:00
Difference between time on server and Android device system time more than 1 hour

For the sake of the experiment let’s also try setting the device time to the time from your question:

    OffsetDateTime deviceTime = OffsetDateTime.of(2020, 8, 24, 13, 50, 18, 0, ZoneOffset.ofHours(5));
Server time: 2020-08-24T11:50:18.613+03:00
Device time: 2020-08-24T13:50:18+05:00
Difference between time on server and Android device system time 1 hour or less

Question: Doesn’t java.time require Android API level 26?

java.time works nicely on both older and newer Android devices. It just requires at least Java 6.

  • In Java 8 and later and on newer Android devices (from API level 26) the modern API comes built-in.
  • In non-Android Java 6 and 7 get the ThreeTen Backport, the backport of the modern classes (ThreeTen for JSR 310; see the links at the bottom).
  • On older Android either use desugaring or the Android edition of ThreeTen Backport. It’s called ThreeTenABP. In the latter case make sure you import the date and time classes from org.threeten.bp with subpackages.

Links

Upvotes: 2

Arvind Kumar Avinash
Arvind Kumar Avinash

Reputation: 79085

It looks like there is some gap in your understanding of Zone-Offset in the date-time string. The date-time string 2020-08-24T11:50:18.613+0300 means it is the date-time at the Zone-Offset of +0300 hours i.e. the corresponding date-time string on UTC will be 2020-08-24T8:50:18.613+0000.

Note that java.util.Date lacks time-zone and zone-offset information. It just has the number of milliseconds since the epoch of 1 January 1970, 00:00:00 UTC. When you print a java.util.Date using an instance of SimpleDateFormat, you print the string representing the date-time in the time-zone set into the instance of SimpleDateFormat. You can understand it from the following example:

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

public class Main {
    public static void main(String[] args) throws ParseException {
        // Parse the date-time string to java.util.Date
        String serverDate = "2020-08-24T11:50:18.613+0300";
        final String SYSTEM_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
        DateFormat dfSource = new SimpleDateFormat(SYSTEM_FORMAT);
        Date date = dfSource.parse(serverDate);
        System.out.println(date);

        // Get the date-time string for the time-zone of Europe/Moscow from
        // java.util.Date
        Locale locale = new Locale("ru", "RU");
        DateFormat dfTarget = new SimpleDateFormat(SYSTEM_FORMAT, locale);
        dfTarget.setTimeZone(TimeZone.getTimeZone("Europe/Moscow"));
        System.out.println(dfTarget.format(date));
    }
}

I suggest you stop using the outdated and error-prone java.util date-time API and SimpleDateFormat. Switch to the modern java.time date-time API and the corresponding formatting API (java.time.format). Learn more about the modern date-time API from Trail: Date Time. If your android version is not compliant with Java-8, you can backport using ThreeTen-BackportCheck. Check How to use ThreeTenABP in Android Project.

Using modern date-time API:

import java.text.ParseException;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;

public class Main {
    public static void main(String[] args) throws ParseException {
        // Given date-time string
        String serverDate = "2020-08-24T11:50:18.613+0300";

        // Date-time formatter
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ");

        ZonedDateTime zdtServer = ZonedDateTime.parse(serverDate, formatter);
        System.out.println("Given date-time: " + zdtServer);
        System.out.println("Zone Offset of the given date-time: " + zdtServer.getOffset());

        // Convert it to some other time-zone e.g Etc/UTC
        ZonedDateTime zdtatUTC = zdtServer.withZoneSameInstant(ZoneId.of("Etc/UTC"));
        System.out.println("Equivalent date-time at UTC: " + zdtatUTC);

        // Convert it to some other time-zone e.g Europe/London
        ZonedDateTime zdtInLondon = zdtServer.withZoneSameInstant(ZoneId.of("Europe/London"));
        System.out.println("Equivalent date-time in London: " + zdtInLondon);
    }
}

Output:

Given date-time: 2020-08-24T11:50:18.613+03:00
Zone Offset of the given date-time: +03:00
Equivalent date-time at UTC: 2020-08-24T08:50:18.613Z[Etc/UTC]
Equivalent date-time in London: 2020-08-24T09:50:18.613+01:00[Europe/London]

Note that the modern date-time API has a class called, ZonedDateTime which has time-zone information along with the date & time information.

Upvotes: 2

deHaar
deHaar

Reputation: 18568

You don't really have a time zone but rather an offset.

To correctly convert the String you get from a servery, you should use java.time instead of the outdated java.util.Date or java.util.Calendar.

Here's an example that uses a suitable class for your situation (java.time.OffsetDateTime):

public static void main(String[] args) {
    String stringFromServer = "2020-08-24T11:50:18.613+0300";
    OffsetDateTime timeFromServer = OffsetDateTime.parse(
                        stringFromServer,
                        DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSZ")
    );
    
    System.out.println(timeFromServer.format(
            DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss XXX uuuu", Locale.ENGLISH))
    );
}

The output of this (the OffsetDateTime formatted using a pattern that mimics the one you posted) is

Mon Aug 24 11:50:18 +03:00 2020

You can use a ZonedDateTime as well, but then you will have to add a ZoneId, ZoneId.of("Europe/Moscow"), most likely...
It can be done like this using the already created OffsetDateTime timeFromServer:

ZonedDateTime moscowTime = timeFromServer.atZoneSameInstant(ZoneId.of("Europe/Moscow"));
ZonedDateTime berlinTime = timeFromServer.atZoneSameInstant(ZoneId.of("Europe/Berlin"));

and System.outing them without a special format would give you these lines:

2020-08-24T11:50:18.613+03:00[Europe/Moscow]
2020-08-24T10:50:18.613+02:00[Europe/Berlin]

Please note: Since there's API Desugaring for Android you can use Java 8(+) functionality in API levels below Android 26.

Upvotes: 2

Related Questions