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

huangapple go评论140阅读模式
英文:

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"

问题是,当我尝试将字符串日期转换为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(莫斯科时间),也无法得到我期望的日期。我知道我可以只使用字符串进行比较,但要求是要比较日期。

英文:

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

答案1

得分: 2

你并没有真正的时区,而是一个时间偏移量。

为了正确转换从服务器获取的 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,但是你可能需要添加一个 ZoneId,比如 ZoneId.of("Europe/Moscow")...

可以像这样使用已创建的 OffsetDateTime timeFromServer 进行操作:

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

并且将它们直接输出而不使用特殊格式,会得到如下结果:

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

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

英文:

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.

答案2

得分: 2

以下是翻译好的内容:

看起来你对日期时间字符串中的Zone-Offset理解还有些空白。日期时间字符串 2020-08-24T11:50:18.613+0300 表示的是在+0300 小时的Zone-Offset上的日期时间,即对应的在 UTC 上的日期时间字符串将会是 2020-08-24T8:50:18.613+0000

注意,java.util.Date 缺乏时区和Zone-Offset信息。它只有自1970年1月1日00:00:00 UTC时代以来的毫秒数。当你使用 SimpleDateFormat 的实例打印一个 java.util.Date 时,你实际上打印了在 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 {
        // 将日期时间字符串解析为 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);

        // 获取 Europe/Moscow 时区的日期时间字符串,基于 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。转而使用现代的 java.time 日期时间 API 以及相应的格式化 API (java.time.format)。从 Trail: Date Time 中学习更多关于现代日期时间 API 的内容。如果你的 Android 版本不支持 Java-8,你可以使用 ThreeTen-BackportCheck 进行后移。查看 在 Android 项目中使用 ThreeTenABP 的方法

使用现代日期时间 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 {
        // 给定的日期时间字符串
        String serverDate = "2020-08-24T11:50:18.613+0300";

        // 日期时间格式化器
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ");

        ZonedDateTime zdtServer = ZonedDateTime.parse(serverDate, formatter);
        System.out.println("给定的日期时间:" + zdtServer);
        System.out.println("给定日期时间的Zone Offset:" + zdtServer.getOffset());

        // 转换为其他时区,比如 Etc/UTC
        ZonedDateTime zdtatUTC = zdtServer.withZoneSameInstant(ZoneId.of("Etc/UTC"));
        System.out.println("等价于 UTC 的日期时间:" + zdtatUTC);

        // 转换为其他时区,比如 Europe/London
        ZonedDateTime zdtInLondon = zdtServer.withZoneSameInstant(ZoneId.of("Europe/London"));
        System.out.println("在伦敦的等价日期时间:" + zdtInLondon);
    }
}

输出:

给定的日期时间:2020-08-24T11:50:18.613+03:00
给定日期时间的Zone Offset:+03:00
等价于 UTC 的日期时间:2020-08-24T08:50:18.613Z[Etc/UTC]
在伦敦的等价日期时间:2020-08-24T09:50:18.613+01:00[Europe/London]

注意,现代日期时间 API 中有一个名为 ZonedDateTime 的类,它包含了时区信息以及日期和时间信息。

英文:

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.

答案3

得分: 2

不需要担心在比较时间时与UTC偏移或时区有关。跨偏移量进行比较会很顺利。

java.time

我正在使用现代的Java日期和时间API - java.time。让我们首先定义一些有用的常量:

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已内置。
  • 在非Android的Java 6和7上,可以使用ThreeTen Backport,这是现代类的后备(ThreeTen用于JSR 310)。
  • 在较旧的Android上,可以使用desugaring或ThreeTen Backport的Android版本,称为ThreeTenABP。在后一种情况下,确保从org.threeten.bp及其子包中导入日期和时间类。

链接

英文:

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.

huangapple
  • 本文由 发表于 2020年8月24日 17:01:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/63557826.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定