SimpleDateFormat.parse() 忽略时区?

SimpleDateFormat.parse() ignoring timezone?

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

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();

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

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

注意:-06:00 是一个 offset, not a timezone - 这两个概念是相关的,但它们是不同的东西(更多内容见下文)。


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

这只是这个旧 API 的众多 problems and design issues 之一。

幸运的是,如果您不介意向您的项目添加依赖项,还有更好的选择(在这种情况下,我认为这是完全值得的)。在 Android 中你可以使用 ThreeTen Backport, a great backport for Java 8's new date/time classes. And for Android, you'll also need the ThreeTenABP to make it work (more on how to use it ).

要使用偏移量,您可以使用 org.threeten.bp.OffsetDateTime class:

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

这将正确解析所有字段(date/time 和偏移量)。获取偏移值,类似于calendar.getTimeZone().getRawOffset(),可以这样做:

// get offset in milliseconds
int totalSeconds = odt.getOffset().getTotalSeconds() * 1000;

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

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

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

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

  • offset 是与 UTC 的区别:-06:00 表示“UTC 后 6 小时”,+09:00 表示“UTC 前 9 小时”
  • 时区是一个区域在其历史期间(以及这些变化发生时)已经、已经和将要具有的所有不同偏移量的集合。最常见的情况是 Daylight Saving Time 轮班,即某个地区的时钟向后或向前改变 1 小时。所有这些关于何时更改(以及更改前后的偏移量是多少)的规则都由时区概念封装。

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

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

上面的 getOffset() 方法将检查指定时区的历史并获取在相应时刻处于活动状态的偏移量(因此,如果您在 DST 期间获取日期,例如,偏移量(和日期和时间)也会相应调整)。

API 使用 IANA timezones names(格式总是 Region/City,如 America/Sao_PauloEurope/Berlin)。 避免使用 3 个字母的缩写(如 CSTPST),因为它们是 ambiguous and not standard.

您可以通过调用 ZoneId.getAvailableZoneIds().

获取可用时区列表(并选择最适合您的系统的时区)

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