如何从ISO 8601格式字符串中选择时区到日历实例中
作为输入,我有一个字符串,它是ISO 8601中表示日期的字符串。 例如:
“2017-04-04T09:00:00-08:00”
String
的最后一部分,即“-08:00”表示TimeZone Offset。 我将此字符串转换为Calendar
实例,如下所示:
Calendar calendar = GregorianCalendar.getInstance(); Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US).parse(iso8601Date); calendar.setTime(date);
iso8601Date是“2017-04-04T09:00:00-08:00”
但这并没有选择时区,如果我从Calendar
实例获取时区,它会提供当前设置的笔记本电脑实例,并且不会从ISO 8601字符串中获取时间戳。 我通过日历实例检查时区为:
calendar.getTimeZone().getDisplayName()
有人可以在Calendar
实例中显示如何选择时区吗?
创建Calendar
,它采用JVM的默认时区。 当你将一个String
解析为一个Date
,它只设置一个值:自纪元以来的毫秒数( 1970-01-01T00:00Z
)。 Date
没有任何时区信息 ,只有这个毫秒值。 因此,您需要在日历中设置时区。
在格式化程序中,您将Z
视为文字,因为它在引号内( 'Z'
)。 这会忽略偏移量并在JVM默认时区中获取日期(如果相应的偏移量不是-08:00,则会有不同的值)。
在JDK> = 7中,您可以使用X
模式来解析偏移量:
Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX", Locale.US).parse(iso8601Date);
但这并没有在日历中设置时区(它仍将使用JVM的默认值)。 因此,“更好”的方法是从输入中去除偏移量并单独处理它:
Calendar calendar = GregorianCalendar.getInstance(); String iso8601Date = "2017-04-04T09:00:00-08:00"; // get the offset (-08:00) String offset = iso8601Date.substring(19); TimeZone tz = TimeZone.getTimeZone("GMT" + offset); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US); // set the offset in the formatter sdf.setTimeZone(tz); // parse just date and time (without the offset) Date date = sdf.parse(iso8601Date.substring(0, 19)); // set the offset in the calendar calendar.setTimeZone(tz); calendar.setTime(date);
这样,日历将设置偏移-08:00
。 正如@ BasilBourque的回答已经说过的那样, -08:00
是一个偏移,而不是一个时区 ( TimeZone
类就像处理时区一样处理偏移,这是一种解决方法/糟糕的设计选择)。
Java新的日期/时间API
旧类( Date
, Calendar
和SimpleDateFormat
)存在许多问题和设计问题 ,并且它们正被新的API取代。
在Android中,您可以使用ThreeTen Backport ,这是Java 8新日期/时间类的绝佳后端 。 你还需要ThreeTenABP来使它工作(更多关于如何在这里使用它)。
@ BasilBourque的答案已经告诉你关于OffsetDateTime
。 但要转换为Calendar
,您可以使用org.threeten.bp.ZonedDateTime
并使用org.threeten.bp.DateTimeUtils
类进行转换:
String iso8601Date = "2017-04-04T09:00:00-08:00"; ZonedDateTime zdt = ZonedDateTime.parse(iso8601Date); Calendar cal = DateTimeUtils.toGregorianCalendar(zdt);
日历将已使用-08:00
偏移设置。
如果你想从偏移量中获取时区,我恐怕不是那么简单。 多个时区可以使用相同的偏移量 ,因此您无法确定使用哪个时区(您可以做的最好的事情是获取可能的候选者列表)。
java.util.Date
关于java.util.Date
的更详细说明。 这个链接解释了很多,所以我真的建议你阅读它。
如上所述, Date
没有时区信息。 它只保留自纪元以来的毫秒数(即1970-01-01T00:00Z
,或1970年1月1 日在UTC的午夜 )。
这个价值在世界各地都是一样的。 示例:在我写这篇文章时,当前时间的millis值为1504632865935
。 对于世界上任何人来说,这个数字是相同的,无论他们使用的是什么时区,我都能在当时获得当前时间。
不同的是与此millis值对应的本地日期和时间 。 在UTC中,它对应于2017-09-05T17:34:25.935Z
,在纽约,日期是相同的(2017年9月5日),但时间不同(13:34),在东京是9月6 日 2017年凌晨02:34。
虽然Date
对象是相同的(因为每个人的millis值为1504632865935
),但相应的日期和时间会根据使用的时区而变化。
人们倾向于认为Date
有一个时区,因为在打印它时(使用System.out.println
或通过loggging)或在调试器中检查时,它隐含使用toString()
方法,这会将日期转换为JVM的默认值时区(还会打印区域名称)。 这给人的印象是Date
有一个格式和时区设置,但它没有。
TL;博士
OffsetDateTime.parse( "2017-04-04T09:00:00-08:00" )
细节
String的最后一部分是“-08:00”表示TimeZone Offset。
不要将偏移与时区混淆。
-08:00
表示与UTC的偏移 ,而不是时区 。 时区是特定地区的人们在过去,现在和将来使用的各种偏移的历史。 时区以大陆,斜线和区域命名,例如America/Los_Angeles
或Pacific/Auckland
或Asia/Kolkata
。
您正在使用现在由java.time类取代的麻烦的旧日期时间类。 对于Android,请参阅ThreeTen- Backport和ThreeTenABP项目。
您的输入仅指示偏移但不指示区域。 所以我们解析为OffsetDateTime
。
OffsetDateTime odt = OffsetDateTime.parse( "2017-04-04T09:00:00-08:00" ) ;
如果您完全确定预期的时区,请进行分配。
ZoneId z = ZoneId.of( "America/Los_Angeles" ) ; ZonedDateTime zdt = odt.atZoneSameInstant() ;
关于java.time
java.time框架内置于Java 8及更高版本中。 这些类取代了麻烦的旧遗留日期时间类,如java.util.Date
, Calendar
和SimpleDateFormat
。
现在处于维护模式的Joda-Time项目建议迁移到java.time类。
要了解更多信息,请参阅Oracle教程 。 并搜索Stack Overflow以获取许多示例和解释。 规范是JSR 310 。
从哪里获取java.time类?
- Java SE 8 , Java SE 9及更高版本
- 内置。
- 带有捆绑实现的标准Java API的一部分。
- Java 9增加了一些小function和修复。
- Java SE 6和Java SE 7
- 许多java.timefunction都被反向移植到ThreeTen-Backport中的 Java 6和7。
- Android的
- ThreeTenABP项目特别适用于Android的ThreeTen-Backport (如上所述)。
- 请参见如何使用ThreeTenABP ….
我希望从雨果的答案中分享一个关键的理解,我的进一步搜索是如下。 如果我错了,请纠正我:
日期并不关心时区。 它表示自纪元以来经过的毫秒数。
关于从提供的ISO 8061格式中找到时区,Date类不能说明这一点,我们必须使用@Hugo和@Basil Bourque指定的一些替代方法。