SimpleDateFormat.parse() 忽略时区?

BAr*_*ell 5 android simpledateformat datetime-parsing timezone-offset

我正在尝试使用此代码来解析带有时区的日期字符串进行测试:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mmZZZZZ", Locale.US);
Calendar calendar = Calendar.getInstance();
calendar.setTime(sdf.parse("2017-07-26T06:00-06:00"));
int offset = calendar.getTimeZone().getRawOffset();
Run Code Online (Sandbox Code Playgroud)

我正在尝试将时区从 更改-06+09,但offset始终包含10800000.

如何正确解析带有时区的日期(我需要时间和时区)?

小智 2

注意:-06:00偏移量,而不是时区——这两个概念是相关的,但它们是不同的东西(更多内容见下文)。


SimpleDateFormat和 的问题Calendar在于它们使用系统的默认时区,因此即使您解析具有不同偏移量的日期(例如-06:00),结果Calendar也将具有默认时区(您可以通过调用 来检查哪个区域TimeZone.getDefault())。

这只是这个旧 API 的众多问题设计问题之一。

幸运的是,如果您不介意向项目添加依赖项,那么还有更好的选择(在这种情况下,我认为这是完全值得的)。在 Android 中,您可以使用ThreeTen Backport,它是 Java 8 新日期/时间类的一个很好的向后移植。对于Android,您还需要ThreeTenABP才能使其工作(更多关于如何使用它的信息,请参见此处)。

要使用偏移量,您可以使用该类org.threeten.bp.OffsetDateTime

// parse the String
OffsetDateTime odt = OffsetDateTime.parse("2017-07-26T06:00-06:00");
Run Code Online (Sandbox Code Playgroud)

这将正确解析所有字段(日期/时间和偏移量)。要获取偏移值,类似于calendar.getTimeZone().getRawOffset(),您可以执行以下操作:

// get offset in milliseconds
int totalSeconds = odt.getOffset().getTotalSeconds() * 1000;
Run Code Online (Sandbox Code Playgroud)

我必须乘以 1000,因为calendar返回的值以毫秒为单位,但ZoneOffset以秒为单位返回。

要将其转换为另一个偏移量 ( +09:00),很简单:

// convert to +09:00 offset
OffsetDateTime other = odt.withOffsetSameInstant(ZoneOffset.ofHours(9));
Run Code Online (Sandbox Code Playgroud)

正如我所说,时区和偏移量是不同的东西:

  • offset 是与UTC的差异:-06:00表示“比 UTC 晚 6 小时”,+09:00表示“比 UTC 早 9 小时”
  • 时区是一个区域在其历史期间(以及这些变化发生时)曾经、现在和将来的所有不同偏移量的集合。最常见的情况是夏令时转变,即某个地区的时钟向前或向后调整 1 小时。所有这些关于何时更改(以及更改前后的偏移量是多少)的规则都由时区概念封装。

因此,如果您正在使用偏移量并希望转换为不同的偏移量,则上面的代码可以正常工作。但如果您想使用时区,则必须将 转换OffsetDateTime为 a ZonedDateTime

// convert to a timezone
ZonedDateTime zdt = odt.atZoneSameInstant(ZoneId.of("Asia/Tokyo"));
// get the offset
totalSeconds = zdt.getOffset().getTotalSeconds() * 1000;
Run Code Online (Sandbox Code Playgroud)

上面的方法getOffset()将检查指定时区的历史记录并获取在相应时刻有效的偏移量(例如,如果您在 DST 期间获取日期,则偏移量(以及日期和时间)将相应调整) 。

API 使用IANA 时区名称(始终采用或Region/City等格式)。避免使用 3 个字母的缩写(如或),因为它们不明确且不标准America/Sao_PauloEurope/BerlinCSTPST

您可以通过调用 获取可用时区列表(并选择最适合您的系统的时区)ZoneId.getAvailableZoneIds()

您还可以将系统的默认时区与 一起使用ZoneId.systemDefault(),但这可能会在不通知的情况下更改,甚至在运行时也是如此,因此最好明确使用特定的时区。