首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Java:解析ISO_DATE / ISO_OFFSET_DATE

Java:解析ISO_DATE / ISO_OFFSET_DATE
EN

Stack Overflow用户
提问于 2018-04-19 13:23:27
回答 3查看 4.5K关注 0票数 3

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

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

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

代码语言:javascript
复制
DateTimeFormatter formatter = DateTimeFormatter.ISO_OFFSET_DATE;
ZonedDateTime dateTime = ZonedDateTime.now();
String formatted = dateTime.format(formatter);

2018-04-19+02:00

但是,试图用.来解析这样的日期。

代码语言:javascript
复制
ZonedDateTime parsed = ZonedDateTime.parse(formatted, formatter);

..。结果出现了例外情况:

java.time.format.DateTimeParseException:文本“2018-04-19 +02:00”无法解析:无法从TemporalAccessor获得ZonedDateTime:{OffsetSeconds=7200},

我还尝试了ISO_DATE并遇到了同样的问题。

我如何解析这样的分区日期回呢?

或者,是否还有其他类型(在Java中)应该用于分区日期?

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2018-04-19 19:10:43

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

当将其解析回来时,不存在与时间相关的字段(小时、分钟、秒),您将得到一个DateTimeParseException

解析它的另一种方法是使用DateTimeFormatterBuilder并为时间字段定义默认值。当您在回答中使用atStartOfDay时,我假设您想要午夜,这样您就可以执行以下操作:

代码语言:javascript
复制
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

你的解决方案也很好,但唯一的问题是您要解析输入两次(每次对formatter.parse的调用都会再次解析输入)。另一种更好的方法是使用parse方法,而不需要时态查询(只解析一次),然后使用解析的对象获取所需的信息。

代码语言:javascript
复制
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

使用此解决方案,只对输入进行一次分析。

票数 4
EN

Stack Overflow用户

发布于 2018-04-19 19:24:00

tl;dr

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

把两者结合起来决定一个时刻。

代码语言:javascript
复制
LocalDate.parse(
  "2018-04-19"
)
.atStartOfDay( 
    ZoneId.of( "Europe/Zurich" ) 
) // Returns a `ZonedDateTime` object.

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

从你的休息服务中,或者:

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

分开您的文本输入

沃尔瑟的回答中的解决方案是将字符串输入有效地视为一对字符串输入。首先,只提取日期部分并对其进行分析。第二,提取和分析偏移距-UTC部分.因此,输入被解析了两次,每次忽略字符串的另一半。

我建议你把这个练习说清楚。将日期跟踪为一段文本,将偏移量(或更好的是时区)跟踪为另一段文本。正如另一个答案中的代码所示,在确定一个实际时刻(如一天的开始)之前,使用区域进行约会并没有真正的意义。

代码语言:javascript
复制
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类在解析/生成字符串时默认使用这些标准格式。

偏移对区域

至于应用日期和偏移以获得片刻,您将与世界协调时相抵时区混为一谈。偏移量只是几个小时、几分钟和几秒。不多也不差。相比之下,时区是过去、现在和未来的历史,是特定地区人民使用的偏移量的历史。

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

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

代码语言:javascript
复制
ZoneId z = ZoneId.of( "Europe/Zurich" ) ;  
ZonedDateTime zdt = ld.atStartOfDay( z ) ;

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

因此,我建议您跟踪两串输入:

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

合并。

代码语言:javascript
复制
ZonedDateTime zdt = 
    LocalDate.parse( inputDate )
             .atStartOfDay( ZoneId.of( inputZone ) ) 
;

注意:ZonedDateTime::toString方法生成的字符串格式明智地扩展了标准的ISO8601格式,方法是在方括号中附加时区的名称。这纠正了由设计良好的标准所造成的巨大疏忽。但是,只有知道客户端可以使用REST服务,才能通过REST服务返回此类字符串。

关于java.time

http://docs.oracle.com/javase/10/docs/api/java/time/package-summary.html框架内置到Java8和更高版本中。这些类取代了麻烦的旧遗赠日期时间类,如java.util.DateCalendarSimpleDateFormat

http://www.joda.org/joda-time/项目现在在维护模式中,建议迁移到java.time类。

要了解更多信息,请参见http://docs.oracle.com/javase/tutorial/datetime/TOC.html。并搜索堆栈溢出以获得许多示例和解释。规范是JSR 310

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

在哪里获得java.time类?

  • Java 8Java 9Java 10,以及后来的
    • 内置的。
    • 带有捆绑实现的标准Java的一部分。
    • Java 9添加了一些次要的特性和修复。

  • Java 6Java 7
    • java.time的大部分功能都是在http://www.threeten.org/threetenbp/中移植到Java6&7中的。

三次-额外项目使用其他类扩展java.time。这个项目是将来可能加入java.time的试验场。您可以在这里找到一些有用的类,如IntervalYearWeekYearQuarter更多

票数 4
EN

Stack Overflow用户

发布于 2018-04-19 13:45:37

我找到了解决方案(使用TemporalQueries):

分别解析日期和区域,并使用该信息还原分区日期:

代码语言:javascript
复制
LocalDate date = formatter.parse(formatted, TemporalQueries.localDate());
ZoneId zone = formatter.parse(formatted, TemporalQueries.zone());
ZonedDateTime restored = date.atStartOfDay(zone);
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/49922244

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档