在给定时区的 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:00Z
为 java.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)。 Duration
和 Period
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 个字母的缩写,例如 EST
或 IST
,因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。
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]
在我的应用程序中,我从网络服务中检索了一个 unix 时间戳(未来 0 到 15 分钟之间),并以 XXm-XXs 的形式显示了那个时间的倒计时。
所以我只是做 System.currentTimeMillis() - timestamp
并将结果转换为人类可读的日期。
一切正常,但似乎在某些时区,我的计时器关闭了 30 分钟, 因为 因为日历 returns 错误的分钟值,当我用它请求分钟时。
时区是吉隆坡(马来西亚)的 GMT+8。使用另一个 GMT+8 时区可以正常工作。
示例:System.currentTimeMillis()
return 值比预期低 1800000 毫秒
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:00Z
为 java.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)。 Duration
和 Period
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 个字母的缩写,例如 EST
或 IST
,因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。
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]