Android 设备系统时间在将服务器日期字符串转换为日期时会更改服务器日期字符串的时区

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

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

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

这些是我用于 SimpleDateFormat 的常数:

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

问题是当我尝试将字符串日期转换为日期 class 我得到这个:

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(莫斯科时间)也不会生成我期望的日期。我知道我可以只使用字符串来比较它,但要求是比较 Dates

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

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

这是一个使用适合您情况的 class 的示例 (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(+) 功能

看来您对date-time字符串中的Zone-Offset的理解有些差距。 date-time 字符串 2020-08-24T11:50:18.613+0300 表示它是 +0300 hours 的 Zone-Offset 处的 date-time 即 UTC 上相应的 date-time 字符串将是 2020-08-24T8:50:18.613+0000.

请注意 java.util.Date 缺少 time-zone 和 zone-offset 信息。它只有自 1970 年 1 月 1 日纪元以来的毫秒数,00:00:00 UTC。当您使用 SimpleDateFormat 的实例打印 java.util.Date 时,您将代表 time-zone 集中的 date-time 的字符串打印到 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));
    }
}

我建议你停止使用过时的和error-pronejava.utildate-timeAPI和SimpleDateFormat。切换到 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 .

使用现代 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);
    }
}

输出:

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]

请注意,现代 date-time API 有一个名为 class 的 ZonedDateTime,其中包含 time-zone 信息以及日期和时间信息。

比较时间时无需担心与 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, 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

问题:java.time 不需要 Android API 26 级吗?

java.time 在新旧 Android 设备上都能很好地工作。它只需要至少 Java 6.

  • 在 Java 8 和更新的 Android 设备上(从 API 级别 26)现代 API 出现 built-in.
  • 在 non-Android Java 6 和 7 中获取 ThreeTen Backport,现代 类 的 backport(ThreeTen 用于 JSR 310;请参阅底部的链接)。
  • 在较旧的 Android 上使用脱糖或 ThreeTen Backport 的 Android 版本。它叫做 ThreeTenABP。在后一种情况下,请确保使用子包从 org.threeten.bp 导入日期和时间 类。

链接