Android设备系统时间将其转换为Date时更改服务器日期字符串的TimeZone

问题描述

我必须检查服务器时间与Android设备系统时间之间的时差是否超过1小时。 现在我有来自服务器的这个字符串:

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

这些是我用于SimpleDateFormat的常量:

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

问题是当我尝试将字符串日期转换为Date类时,我得到了这个提示

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

据我了解,+ 05:00是因为这是在Android设备上认设置的时区。这就是我得到这个日期的方式:

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

您可以看到,即使将时区设置为+3(莫斯科时间)也无法达到我期望的日期。我知道我可以只使用字符串进行比较,但要求是比较日期

解决方法

您实际上没有时间 zone ,而是一个偏移量。

要正确转换从服务器获得的String,应使用java.time而不是过时的java.util.Datejava.util.Calendar

下面是一个使用适合您的情况的类的示例(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))
    );
}

此输出(使用模仿您发布的格式的OffsetDateTime格式的输出)

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

您也可以使用ZonedDateTime,但是很可能必须添加ZoneIdZoneId.of("Europe/Moscow") ...
可以使用已经创建的OffsetDateTime timeFromServer这样完成:

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

,然后System.out对它们进行特殊格式化将为您提供以下几行:

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

请注意: 由于存在API Desugaring for Android,因此您可以在Android 26以下的API级别中使用Java 8(+)功能。

,

在日期时间字符串中,您对Zone-Offset的理解似乎有些差距。日期时间字符串2020-08-24T11:50:18.613+0300表示它是+0300 hours的区域偏移处的日期时间,即UTC上的相应日期时间字符串将为2020-08-24T8:50:18.613+0000。 / p>

请注意,java.util.Date缺少时区和时区偏移信息。它仅具有自1970年1月1日UTC时间00:00:00以来的毫秒数。当使用java.util.Date的实例打印SimpleDateFormat时,会将表示时区中日期时间的字符串打印到SimpleDateFormat的实例中。您可以从以下示例中了解它:

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));
    }
}

我建议您停止使用过时且容易出错的java.util日期时间API和SimpleDateFormat。切换到modern java.time日期时间API和相应的格式API(java.time.format)。从 Trail: Date Time 了解有关现代日期时间API的更多信息。如果您的Android版本与Java-8不兼容,则可以使用ThreeTen-BackportCheck向后移植。选中How to use ThreeTenABP in Android Project

使用现代日期时间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);
    }
}

输出:

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]

请注意,现代的日期时间API具有一个名为ZonedDateTime的类,该类具有时区信息以及日期和时间信息。

,

比较时间时,无需担心与UTC或时区的偏差。偏移量之间的比较比较顺利。

java.time

我正在使用java.time(现代Java日期和时间API)。首先定义几个有用的常量:

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

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

现在我们可以做到:

    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");
    }

我已经从问题中使用了您的字符串,因此当我现在运行该代码段时,这段代码告诉我们这两者之间存在很大差异,我们不会感到惊讶:

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

为了进行实验,我们还尝试将设备时间设置为您提出问题的时间:

    OffsetDateTime deviceTime = OffsetDateTime.of(2020,8,24,13,50,18,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

问题:java.time是否不需要Android API级别26?

java.time在较新和较旧的Android设备上均可正常运行。它只需要至少 Java 6

  • 在Java 8和更高版本以及更新的Android设备(API级别26以上)中,内置了现代API。
  • 在非Android Java 6和7中,获得ThreeTen Backport,这是现代类的backport(JSR 310的ThreeTen;请参见底部的链接)。
  • 在较旧的Android上,请使用废除旧书或Android版本的ThreeTen Backport。称为ThreeTenABP。在后一种情况下,请确保使用子包从org.threeten.bp导入日期和时间类。

链接