Java:解析ISO_DATE / ISO_OFFSET_DATE

问题描述 投票:1回答:3

对于REST Web服务,我需要使用时区返回日期(没有时间)。

显然,Java中没有ZonedDate这样的东西(只有LocalDateZonedDateTime),所以我使用ZonedDateTime作为后备。

将这些日期转换为JSON时,我使用DateTimeFormatter.ISO_OFFSET_DATE格式化日期,这非常有效:

DateTimeFormatter formatter = DateTimeFormatter.ISO_OFFSET_DATE;
ZonedDateTime dateTime = ZonedDateTime.now();
String formatted = dateTime.format(formatter);

2018-04-19+02:00

但是,尝试用...解析这样的日期

ZonedDateTime parsed = ZonedDateTime.parse(formatted, formatter);

...导致异常:

java.time.format.DateTimeParseException:无法解析文本'2018-04-19 + 02:00':无法从TemporalAccessor获取ZonedDateTime:{OffsetSeconds = 7200},ISO解析为java类型的2018-04-19。 time.format.Parsed

我也试过ISO_DATE并遇到了同样的问题。

我该如何解析这样的分区日期? 或者是否有任何其他类型(在Java Time API中)我应该用于分区日期?

java date iso java-time timezone-offset
3个回答
3
投票

问题是ZonedDateTime需要构建所有日期和时间字段(年,月,日,小时,分钟,秒,纳秒),但格式化程序ISO_OFFSET_DATE生成一个没有时间部分的字符串。

解析它时,没有与时间相关的字段(小时,分钟,秒),你得到一个DateTimeParseException

解析它的另一种方法是使用DateTimeFormatterBuilder并定义时间字段的默认值。当你在答案中使用atStartOfDay时,我假设你想要午夜,所以你可以做以下事情:

DateTimeFormatter fmt = new DateTimeFormatterBuilder()
    // date and offset
    .append(DateTimeFormatter.ISO_OFFSET_DATE)
    // default values for hour and minute
    .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
    .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
    .toFormatter();
ZonedDateTime parsed = ZonedDateTime.parse("2018-04-19+02:00", fmt); // 2018-04-19T00:00+02:00

Your solution也可以正常工作,但唯一的问题是你要解析输入两次(每次调用formatter.parse都会再次解析输入)。更好的选择是在没有时间查询的情况下使用parse方法(仅解析一次),然后使用解析的对象来获取所需的信息。

DateTimeFormatter formatter = DateTimeFormatter.ISO_OFFSET_DATE;
// parse input
TemporalAccessor parsed = formatter.parse("2018-04-19+02:00");

// get data from the parsed object
LocalDate date = LocalDate.from(parsed);
ZoneId zone = ZoneId.from(parsed);
ZonedDateTime restored = date.atStartOfDay(zone); // 2018-04-19T00:00+02:00

使用此解决方案,输入仅解析一次。


3
投票

tl;dr

使用时区(continent/region)而不是仅仅偏离UTC(小时 - 分钟 - 秒)。对于任何特定区域,偏移量可能会随时间而变化。

将两者结合起来确定一个时刻。

LocalDate.parse(
  "2018-04-19"
)
.atStartOfDay( 
    ZoneId.of( "Europe/Zurich" ) 
) // Returns a `ZonedDateTime` object.

2018-04-19T00:00 + 02:00 [欧洲/苏黎世]

从您的REST服务中,可以:

  • 分别返回日期和区域(使用分隔符或XML / JSON),或者,
  • 返回一天的开始,因为这可能是带有时区的日期的预期结果。

Separate your text inputs

Answer by Walser中的解决方案是将字符串输入有效地视为一对字符串输入。首先,提取和解析仅日期部分。其次,提取并解析从UTC部分偏移的部分。因此,输入被解析两次,每次忽略字符串的相反一半。

我建议你明确这个做法。将日期跟踪为一段文本,跟踪偏移(或更好,时区)作为另一段文本。正如其他答案中的代码所示,对于带有区域的日期没有实际意义,直到您采取下一步确定实际时刻(例如一天的开始)为止。

String inputDate = "2018-04-19" ;
LocalDate ld = LocalDate.parse( inputDate ) ;

String inputOffset = "+02:00" ;
ZoneOffset offset = ZoneOffset.of( inputOffset) ;

OffsetTime ot = OffsetTime.of( LocalTime.MIN , offset ) ; 
OffsetDateTime odt = ld.atTime( ot ) ;  // Use `OffsetDateTime` & `ZoneOffset` when given a offset-from-UTC. Use `ZonedDateTime` and `ZoneId` when given a time zone rather than a mere offset.

odt.toString():2018-04-19T00:00 + 02:00

如您所见,代码很简单,您的意图很明显。

而且不需要打扰任何DateTimeFormatter对象和格式化模式。这些输入符合ISO 8601标准格式。在解析/生成字符串时,java.time类默认使用这些标准格式。

Offset versus Zone

至于应用日期和偏移来获得片刻,你将offset-from-UTCtime zone混为一谈。偏移量只是小时数,分钟数和秒数。不多也不少。相反,时区是特定地区人民使用的偏移的过去,现在和将来变化的历史。

换句话说,+02:00恰好在许多日期被许多时区使用。但在某个特定区域,例如Europe/Zurich,其他日期可能会使用其他偏移量。例如,采用夏令时(DST)的愚蠢意味着一个区域将花费一半的时间用一个偏移量而另一个用于不同的偏移量。

proper time zone name的格式指定continent/region,例如America/MontrealAfrica/CasablancaPacific/Auckland。切勿使用诸如ESTIST之类的3-4字母缩写,因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。

ZoneId z = ZoneId.of( "Europe/Zurich" ) ;  
ZonedDateTime zdt = ld.atStartOfDay( z ) ;

zdt.toString():2018-04-19T00:00 + 02:00 [欧洲/苏黎世]

所以我建议你跟踪两个输入字符串:

  • 仅限日期(LocalDate):YYYY-MM-DD,如2018-04-19
  • 适当的时区名称(ZoneId):大陆/地区,如Europe/Zurich

结合。

ZonedDateTime zdt = 
    LocalDate.parse( inputDate )
             .atStartOfDay( ZoneId.of( inputZone ) ) 
;

注意:ZonedDateTime::toString方法以一种格式生成一个String,该格式通过在方括号中附加时区名称来明智地扩展标准ISO 8601格式。这纠正了由其他精心设计的标准所造成的巨大疏忽。但是如果您知道客户可以使用它,那么您只能通过REST服务返回这样的字符串。


About java.time

java.time框架内置于Java 8及更高版本中。这些类取代了麻烦的旧legacy日期时间类,如java.util.DateCalendarSimpleDateFormat

现在在Joda-Timemaintenance mode项目建议迁移到java.time班。

要了解更多信息,请参阅Oracle Tutorial。并搜索Stack Overflow以获取许多示例和解释。规格是JSR 310

您可以直接与数据库交换java.time对象。使用符合JDBC driver或更高版本的JDBC 4.2。不需要字符串,不需要java.sql.*类。

从哪里获取java.time类?

ThreeTen-Extra项目使用其他类扩展了java.time。该项目是未来可能添加到java.time的试验场。你可能会在这里找到一些有用的类,如IntervalYearWeekYearQuartermore


1
投票

我找到了解决方案(使用TemporalQueries): 分别解析日期和区域,并使用该信息恢复分区日期:

LocalDate date = formatter.parse(formatted, TemporalQueries.localDate());
ZoneId zone = formatter.parse(formatted, TemporalQueries.zone());
ZonedDateTime restored = date.atStartOfDay(zone);
© www.soinside.com 2019 - 2024. All rights reserved.