Java 如何将ISO 8601格式字符串中的时区选择到日历安装中

Java 如何将ISO 8601格式字符串中的时区选择到日历安装中,java,android,date,datetime-parsing,java.util.calendar,Java,Android,Date,Datetime Parsing,Java.util.calendar,作为输入,我有一个字符串,它是ISO 8601中表示日期的字符串。例如: “2017-04-04T09:00:00-08:00” 字符串的最后一部分,“-08:00”表示时区偏移。我将此字符串转换为日历实例,如下所示: Calendar calendar = GregorianCalendar.getInstance(); Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US).parse(iso8601Da

作为输入,我有一个字符串,它是ISO 8601中表示日期的字符串。例如:

“2017-04-04T09:00:00-08:00”

字符串的最后一部分
“-08:00”表示时区偏移。我将此字符串转换为
日历
实例,如下所示:

Calendar calendar = GregorianCalendar.getInstance();
Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US).parse(iso8601Date);
calendar.setTime(date);
ISO8601日期为“2017-04-04T09:00:00-08:00”

但这不会选择时区,如果我从
Calendar
实例获取时区,它会给出膝上型电脑当前设置的实例,并且不会从ISO8601字符串中选择时间戳。我通过日历实例检查时区,如下所示:

calendar.getTimeZone().getDisplayName()
有人能在
日历
实例中演示如何选择时区吗?

tl;博士 细节 字符串的最后一部分“-08:00”表示时区偏移

不要将偏移与时区混淆

-08:00
表示一个,而不是一个。时区是特定地区的人们在过去、现在和未来使用的各种偏移量的历史。时区以大陆、斜线和区域命名,如或
太平洋/奥克兰

您使用的是麻烦的旧日期时间类,现在已被java.time类取代。对于Android,请参阅和项目

您的输入仅指示偏移,而不指示分区。因此,我们将解析为一个

如果您完全确定计划的时区,请指定它

ZoneId z = ZoneId.of( "America/Los_Angeles" ) ;
ZonedDateTime zdt = odt.atZoneSameInstant() ;

关于java.time 该框架内置于Java8及更高版本中。这些类取代了麻烦的旧日期时间类,例如,&

该项目现已启动,建议迁移到类

要了解更多信息,请参阅。并搜索堆栈溢出以获得许多示例和解释。规格是

从哪里获得java.time类

  • ,及以后
    • 内置的
    • 标准JavaAPI的一部分,带有捆绑实现
    • Java9添加了一些次要功能和修复
    • 大部分java.time功能都在中向后移植到Java6和Java7
    • 该项目专门为Android采用了ThreeTen Backport(如上所述)

当您创建
日历时,它采用JVM的默认时区。当您将
字符串
解析为
日期
时,它只设置一个值:自历元起的毫秒数(
1970-01-01T00:00Z
)。一个
日期
,仅此毫秒值。因此,您需要在日历中设置时区

在格式化程序中,您将
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
。如前所述,
TimeZone
类将偏移视为时区,这是一个解决方案/糟糕的设计选择)


Java新的日期/时间API 旧类(
Date
Calendar
SimpleDateFormat
)具有和,它们正在被新的API所取代

在Android中,您可以使用,这是Java8新的日期/时间类的一个很好的后端口。您还需要使它工作(更多关于如何使用它)

已经告诉您有关
OffsetDateTime
。但要转换为
日历
,可以使用
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
的更详细说明。解释了很多,所以我真的建议你读一读

如上所述,
日期
没有时区信息。它只保留自epoch起的毫秒数(即
1970-01-01T00:00Z
,或1970年1月1日UTC午夜时分)

这个价值观在世界各地都是一样的。示例:在我写这篇文章的时候,当前时间的毫秒值是
1504632865935
。这个数字对于世界上任何人来说都是一样的,无论他们使用的时区是什么,只要他们在我使用的同一时刻获得当前时间

不同的是与该毫数值对应的本地日期和时间。在UTC中,它对应于2017-09-05T17:34:25.935Z,在纽约,日期相同(2017年9月5日),但时间不同(13:34),在东京,是2017年9月6日凌晨2:34

尽管
Date
对象是相同的(因为它的millis值对于每个人来说都是
1504632865935
),但相应的日期和时间会根据所使用的时区而变化


人们倾向于认为
Date
有一个时区,因为当打印它(使用
System.out.println
或通过日志)或在调试器中检查时,它隐式地使用
toString()
方法,这会将日期转换为JVM的默认时区(还打印区域名称)。这给人的印象是,日期设置了格式和时区,但它没有设置。

我想从Hugo的回答和我的进一步研究中分享一个关键的理解
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);
String iso8601Date = "2017-04-04T09:00:00-08:00";
ZonedDateTime zdt = ZonedDateTime.parse(iso8601Date);
Calendar cal = DateTimeUtils.toGregorianCalendar(zdt);