作为输入,我有一个字符串,它是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
实例中显示如何选择时区吗?
答案 0 :(得分:3)
OffsetDateTime.parse( "2017-04-04T09:00:00-08:00" )
String的最后一部分是“-08:00”表示TimeZone Offset。
不要将偏移与时区混淆。
-08:00
代表offset-from-UTC,而不是time zone。时区是特定地区的人们在过去,现在和将来使用的各种偏移的历史。时区以大陆,斜杠和区域命名,例如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 8及更高版本中。这些类取代了麻烦的旧legacy日期时间类,例如java.util.Date
,Calendar
和& SimpleDateFormat
现在位于Joda-Time的maintenance mode项目建议迁移到java.time类。
要了解详情,请参阅Oracle Tutorial。并搜索Stack Overflow以获取许多示例和解释。规范是JSR 310。
从哪里获取java.time类?
答案 1 :(得分:2)
创建Calendar
时,它会占用JVM的默认时区。当您将String
解析为Date
时,它只会设置一个值:自纪元(1970-01-01T00:00Z
)以来的毫秒数。一个Date
doesn't have any timezone information,只有这个毫秒值。所以你需要在日历中设置时区。
在格式化程序中,您将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's answer已经说过的那样,-08:00
is an offset, not a timezone(TimeZone
类将偏移视为时区,这是一种解决方法/糟糕的设计选择。)
旧类(Date
,Calendar
和SimpleDateFormat
)有lots of problems和design issues,并且它们被新API取代
在Android中,您可以使用ThreeTen Backport,这是Java 8新日期/时间类的绝佳后端。您还需要ThreeTenABP才能使其正常运行(更多关于如何使用它here)。
@BasilBourque's answer已经告诉您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
偏移。
如果你想从偏移中获取时区,我担心它并不那么简单。不止一个时区can use the same offset,因此您无法确定使用哪个时区(您可以做的最好的事情就是获取可能的候选人名单)。
关于java.util.Date
的更详细说明。 This link解释了很多,所以我真的建议你阅读它。
如上所述,Date
没有时区信息。它只保留了自纪元以来的毫秒数(即1970-01-01T00:00Z
或 1月1日 st 1970年午夜的UTC )。
这个价值在世界各地都是一样的。示例:在我写这篇文章时,当前时间的millis值为1504632865935
。对于世界上任何人来说,这个数字是相同的,无论他们使用的是什么时区,我都能在当时获得当前时间。
与此millis值对应的本地日期和时间有所不同。在UTC中,它对应于2017-09-05T17:34:25.935Z
,在纽约,日期是相同的(9月5日 th 2017)但时间不同(13:34),东京是9月 6 th 2017年凌晨02:34。
虽然Date
对象相同(因为每个人的millis值为1504632865935
),但对应的日期和时间会根据使用的时区发生变化。
人们倾向于认为Date
有时区,因为在打印它时(使用System.out.println
或通过loggging)或在调试器中进行检查时,它隐含使用toString()
方法,这会将日期转换为JVM的默认时区(并且还会打印区域名称)。这给人的印象是Date
设置了格式和时区,但它没有。
答案 2 :(得分:1)
我想从雨果的答案中分享一个关键的理解,我的进一步搜索正在跟随。如果我错了,请纠正我:
日期并不关心时区。它表示自纪元以来经过的毫秒数。
关于从提供的ISO 8061格式中找到时区,Date类不能告诉我们,我们必须使用@Hugo和@Basil Bourque指定的一些替代方法。