在给定时区的 Android 上检索当前时间时出现错误

Bug retrieving current time on Android with a given timezone

在我的应用程序中,我从网络服务中检索了一个 unix 时间戳(未来 0 到 15 分钟之间),并以 XXm-XXs 的形式显示了那个时间的倒计时。 所以我只是做 System.currentTimeMillis() - timestamp 并将结果转换为人类可读的日期。 一切正常,但似乎在某些时区,我的计时器关闭了 30 分钟, 因为 System.currentTimeMillis() return 值比预期低 1800000 毫秒 因为日历 returns 错误的分钟值,当我用它请求分钟时。 时区是吉隆坡(马来西亚)的 GMT+8。使用另一个 GMT+8 时区可以正常工作。 示例:

 long till = requestTimeFromWebService()
 long now=System.currentTimeMillis();
 long remaining_time = till - now;
 Calendar c=Calendar.getInstance();
 c.setTimeInMillis(remaining_time);
 int minutes=c.get(Calendar.MINUTE);
 System.out.println(""+minutes+"m");
 int seconds=c.get(Calendar.SECOND);
 System.out.println(""+seconds+"s");

使用此代码 System.out.println(""+minutes+"m"); 打印(例如)5m 如果设置了 GMT+2 罗马时区,如果设置了 GMT+8 吉隆坡时区则打印 35m

这是已知错误吗? 我发现:http://www.objectdb.com/database/forum/363 这似乎证实了一个问题。

我还发现了这个:https://en.wikipedia.org/wiki/Time_in_Malaysia

Blockquote At 2330 hrs local time of 31 December 1981, people in Peninsular Malaysia adjusted their clocks and watches ahead by 30 minutes to become 00:00 hours local time of 1 January 1982, to match the time in use in East Malaysia, which is UTC+08:00. This could explain where the bug comes off.

有什么建议吗?

虽然我仍然不知道为什么原始代码不起作用,但我可以简单地使用

解决我的具体问题
Calendar c=Calendar.getInstance(TimeZone.getTimeZone("GMT"));

而不是

Calendar c=Calendar.getInstance();

所以我总是可以将时间戳与我感兴趣的 UTC 时区进行比较。

顺便说一句,日历在我的情况下应该可以工作,即使设置语言环境时区(当没有参数传递给 getInstance() 时会发生这种情况),它适用于大多数时区,但显然不是每个人都适用.

日期时间 != 时间跨度

您正在滥用日期时间 class java.util.Calendar(或 java.util.Date)以不恰当地跟踪时间跨度。 class 将时间跟踪为自 1970 年开始以来的毫秒数,以 UTC (1970-01-01T00:00:00Z) 加上分配的时区。

因此,当您以 5 分钟的毫秒计数实例化时,您实际上是在 1970 年开始后创建 5 分钟的日期时间,1970-01-01T00:05:00Zjava.util.Date 并添加一个时间java.util.Calendar 的区域。

当您为马来西亚应用时区时 您最终得到的是 1970 年的旧式马来西亚时间规则,而不是今天的 post-1981 年马来西亚规则。

所以,没有错误,只是对功能的误用。

经验教训:不要使用日期时间值来表示时间跨度。

java.time

另一个问题:您正在使用臭名昭著的旧旧日期时间 classes,现在已被 java.time classes 取代。

如果“unix 时间戳”是指从 UTC such as 1_473_738_754_764L, then use the Instant class. The Instant class represents a moment on the timeline in UTC with a resolution of nanoseconds 中的 1970 年开始的毫秒数。

首先,我们将问题中描述的一些输入数据模拟为未来最多 15 分钟。

Instant instantNow = Instant.now ();
long seconds = TimeUnit.MINUTES.toSeconds ( 10 );
String input = Long.toString ( instantNow.plusSeconds ( seconds ).toEpochMilli () ); // 10 minutes in the future.

为了处理该字符串输入,我们将其转换为 long 原始值,并将其提供给 Instant 工厂方法。

Instant instantLater = Instant.ofEpochMilli ( Long.valueOf ( input ) );

时间跨度

要捕获经过的时间,请使用 Duration class。

Duration duration = Duration.between ( instantNow , instantLater );

System.out.println ( "instantNow: " + instantNow + " | instantLater: " + instantLater + " | duration: " + duration );

当运行。请注意持续时间 PnYnMnDTnHnMnS 的标准 ISO 8601 格式,其中 P 标记开始,T 将年-月-日部分与时-分-秒部分分开。所以 PT10M 是“十分钟”。始终将此格式用于经过时间的文本表示,而不是模棱两可的时钟样式 (HH:MM:SS)。 DurationPeriod class 中的 java.time 可以直接解析并生成此类字符串,无需指定格式模式。

instantNow: 2016-09-13T19:16:33.913Z | instantLater: 2016-09-13T19:26:33.913Z | duration: PT10M

请注意,上面代码的 none 关心时区。所有值都在 UTC 中。您的大部分业务逻辑、数据存储和数据交换都应该使用 UTC。仅在必要时或为了向用户展示而使用分区值。

分区

您的问题询问的是罗马和马来西亚的分区值。应用 ZoneId 得到 ZonedDateTime。指定 proper time zone name。切勿使用 3-4 个字母的缩写,例如 ESTIST,因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。

ZoneId zMontreal = ZoneId.of ( "America/Montreal" );
ZoneId zRome = ZoneId.of ( "Europe/Rome" );
ZoneId zKualaLumpur = ZoneId.of ( "Asia/Kuala_Lumpur" );

ZonedDateTime zdtMontreal = instantNow.atZone ( zMontreal );
ZonedDateTime zdtRome = instantNow.atZone ( zRome );
ZonedDateTime zdtKualaLumpur = instantNow.atZone ( zKualaLumpur );

System.out.println ( "instantNow: " + instantNow + " | zdtMontreal: " + zdtMontreal + " | zdtRome: " + zdtRome + " | zdtKualaLumpur: " + zdtKualaLumpur );

instantNow: 2016-09-13T20:23:34.280Z | zdtMontreal: 2016-09-13T16:23:34.280-04:00[America/Montreal] | zdtRome: 2016-09-13T22:23:34.280+02:00[Europe/Rome] | zdtKualaLumpur: 2016-09-14T04:23:34.280+08:00[Asia/Kuala_Lumpur]