【问题标题】:Bug retrieving current time on Android with a given timezone使用给定时区在 Android 上检索当前时间的错误
【发布时间】:2017-01-19 09:32:48
【问题描述】:

在我的应用程序中,我从 web 服务中检索了一个 unix 时间戳(未来 0 到 15 分钟之间),并以 XXm-XXs 的形式显示该时间的倒计时。 所以我只是做System.currentTimeMillis() - timestamp 并将结果转换为人类可读的日期。 一切正常,但似乎在某些时区,我的计时器关闭了 30 分钟,因为 System.currentTimeMillis() 返回的值比预期低 1800000 毫秒,因为日历在我请求分钟时返回错误的分钟值. 时区是吉隆坡(马来西亚)的 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");

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

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

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

Blockquote 1981 年 12 月 31 日当地时间 2330 时,马来西亚半岛的人们将时钟和手表提前 30 分钟,以成为 1982 年 1 月 1 日当地时间 00:00 时,以匹配东马使用的时间,这是UTC + 08:00。 这可以解释错误出现在哪里。

有什么建议吗?

【问题讨论】:

  • 30000 millis 是 30 秒,而不是 30 分钟
  • 您设备的日期是什么时候?
  • 某些时区不是 UTC 整小时的时间:timeanddate.com/time/time-zones-interesting.html。您也可以获取用户的 UTC 偏移量并使用它:TimeZone.getOffset()
  • 嗯,这里有些东西没有意义。 System.currentTimeMillis() 严格根据 UTC 返回时间。如果您的输入时间戳也基于 UTC,则时区无关紧要。
  • c.setTimeInMillis(millis); 在这种情况下millis 是什么? (我怀疑它是remaining_time 值,在这种情况下,一切都有意义,因为它代表纪元之后的一小段时间,大约是 1/1/1970 00:15 左右,这是马拉西亚的时区不是的时间gmt+8. -> 不要使用日历来显示时差)

标签: java android date timezone unix-timestamp


【解决方案1】:

虽然我仍然不知道为什么原始代码不起作用,但我可以简单地使用来解决我的具体问题

Calendar c=Calendar.getInstance(TimeZone.getTimeZone("GMT"));

而不是

Calendar c=Calendar.getInstance();

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

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

【讨论】:

  • 不要使用日历来比较时间戳
  • @njzk2 我没有,主要答案是用代码更新的,所以你可以在那里看到我不使用日历来比较任何东西,而只是为了获得人类可读的文本。跨度>
【解决方案2】:

日期时间!= 时间跨度

您正在滥用日期时间类java.util.Calendar(或java.util.Date)来不恰当地跟踪时间跨度。该类以自 1970 年 UTC (1970-01-01T00:00:00Z) 开始的纪元以来的毫秒计数以及指定的时区跟踪时间。

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

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

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

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

java.time

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

如果“unix 时间戳”是指 UTC(例如 1_473_738_754_764L)中 1970 纪元的毫秒数,则使用 Instant 类。 Instant 类代表UTC 中时间轴上的一个时刻,分辨率为nanoseconds

首先,我们将问题中描述的一些输入数据模拟为未来最多 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 类。

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

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

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

现在:2016-09-13T19:16:33.913Z |即刻以后:2016-09-13T19:26:33.913Z |持续时间:PT10M

请注意,上述代码都不关心时区。所有值都在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 );

现在:2016-09-13T20:23:34.280Z | zdt蒙特利尔:2016-09-13T16:23:34.280-04:00[美国/蒙特利尔] | zdt罗马:2016-09-13T22:23:34.280+02:00[欧洲/罗马] | zdtKualaLumpur: 2016-09-14T04:23:34.280+08:00[Asia/Kuala_Lumpur]

【讨论】:

    猜你喜欢
    • 2013-04-18
    • 2012-02-29
    • 1970-01-01
    • 1970-01-01
    • 2014-09-10
    • 2010-10-08
    • 1970-01-01
    • 2023-04-05
    • 1970-01-01
    相关资源
    最近更新 更多