创建 时Calendar
,它采用 JVM 的默认时区。当您将 a 解析String
为 a时Date
,它只设置一个值:自 epoch ( 1970-01-01T00:00Z
) 以来的毫秒数。ADate
没有任何时区信息,只有这个毫秒值。所以你需要在日历中设置时区。
在您的格式化程序中,您将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
. 但是要转换为 a Calendar
,您可以使用 aorg.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
偏移量。
如果要从偏移量中获取时区,恐怕没那么简单。多个时区可以使用相同的 offset,因此您无法确定要使用哪个时区(您能做的最好的事情就是获取可能的候选者列表)。
java.util.日期
只是一个更详细的说明java.util.Date
。这个链接解释了很多,所以我真的建议你阅读它。
如上所述,aDate
没有时区信息。它只保留自纪元以来的毫秒数(即UTC1970-01-01T00:00Z
或1970 年1 月 1日午夜)。
这个值在世界各地都是一样的。示例:在我写这篇文章的那一刻,当前时间的毫秒值是1504632865935
. 这个数字对于世界上与我同时获得当前时间的任何人来说都是相同的,无论他们使用的是哪个时区。
不同的是与此毫秒值对应的本地日期和时间。在 UTC 中,它对应于2017-09-05T17:34:25.935Z
,在纽约,日期相同(2017 年 9 月 5日)但时间不同(13:34),在东京是 2017 年 9 月6日凌晨 02:34。
虽然Date
对象是相同的(因为它的毫秒值1504632865935
适用于所有人),但对应的日期和时间会根据使用的时区而变化。
人们倾向于认为 aDate
有一个时区,因为在打印它(使用System.out.println
或通过记录)或在调试器中检查时,它隐式使用该toString()
方法,这会将日期转换为 JVM 的默认时区(并且它还打印区域名称)。这给人的印象是 aDate
设置了格式和时区,但事实并非如此。