试图通过Java的new Date().toString()支持Javascript的DateTimeFormatter输出格式,但似乎无法使其正常工作。

Js的输出具有以下性质:


2018年4月4日星期三09:56:16 GMT-0500(美国太平洋标准时间)
2018年4月4日星期三16:12:41 GMT + 0200(CEST)


我当前的格式化程序:

int defaultOffset = ZonedDateTime.now().getOffset().getTotalSeconds();
DateTimeFormatter dtfJs =  new DateTimeFormatterBuilder()
                                .appendPattern("EE MMM dd yyyy HH:mm:ss [OOOO (zzzz)]")
                                .parseDefaulting(ChronoField.OFFSET_SECONDS,defaultOffset
                                .toFormatter();


如果我.parse()来自js的那些日期字符串,则会出现以下错误:


  [日期]无法在索引25处解析


提到的两个日期的索引25是:


GMT-0500(南太平洋标准时间)
GMT + 0200(CEST)


我知道问题出在:(冒号),因为如果用dtfJs打印当前日期,则会得到:


  2018年4月4日星期三10:25:10 GMT-05:00(哥伦比亚时间)


因此,在接收到的字符串中,GMT-05:00的部分被期望为GMT-0500,但是我找不到与此匹配的reserved pattern letter

文档说:


  偏移量O:此格式可根据
  图案字母。一个字母输出本地化的缩写
  偏移量,是带有小时的本地化偏移量文本,例如“ GMT”
  不带前导零,可选的两位数分钟和秒(如果非零),
  和冒号,例如“ GMT + 8”。四个字母输出完整格式,
  这是本地化的偏移文本,例如'GMT,带有2位数字的小时和
  分钟字段,可选的第二字段(如果非零)和冒号,用于
  例如“ GMT + 08:00”。其他任何字母计数
  IllegalArgumentException。
  
  偏移Z:这会根据图案数量设置偏移格式
  字母。一,二或三个字母输出
  
  小时和分钟,不带冒号,例如“ +0130”。输出将
  当偏移量为零时为'+0000'。四个字母输出完整格式
  的局部偏移量,相当于四个偏移量O。的
  如果偏移量,输出将是对应的本地化偏移量文本
  是零。五个字母输出时,分,可选秒
  如果非零,则带有冒号。如果偏移为零,则输出“ Z”。六或
  更多字母将引发IllegalArgumentException。


这意味着四个字母将始终以冒号“:”输出,从而抛出DateTimeParseException

非常感谢帮助,谢谢

编辑

感谢@mszymborski,我设法继续验证括号部分“(CEST)”,这里有用的是什么?

我尝试使用EE MMM dd yyyy HH:mm:ss 'GMT'Z (zz),但这仅适用于列表中的第二个日期,而不适用于第一个日期


GMT-0500(南太平洋标准时间)错误
GMT + 0200(CEST)通过

最佳答案

JavaScript is a big mess中的日期。 toString()不仅browser/implementation dependent,而且对语言环境敏感。我在巴西,因此我的浏览器设置为葡萄牙语,并且new Date().toString()给出以下结果:


  2018年4月4日星期三14:14:04 GMT-0300(Hora oficial do Brasil)


月和星期几的名称是英语,但时区名称是葡萄牙语。真是一团糟!

无论如何,要解析这些字符串,您必须做出一些决定。


  您需要获取时区还是偏移量?


偏移量GMT + 0200为used by more than one country(因此,使用了多个时区)。尽管偏移量足以具有明确的时间点,但仅了解时区还是不够的。

即使短名称(例如CEST)也不够,因为这也是used by more than 1 country

如果只想解析偏移量,最好的方法是简单地删除(之后的所有内容并将其解析为OffsetDateTime

DateTimeFormatter parser = DateTimeFormatter.ofPattern("EEE MMM dd yyyy HH:mm:ss 'GMT'Z", Locale.US);

// 2018-04-04T16:12:41+02:00
OffsetDateTime.parse("Wed Apr 04 2018 16:12:41 GMT+0200", parser);


另请注意,我使用了java.util.Locale。这是因为星期和星期几是英文,并且如果您未设置语言环境,它将使用JVM默认值-并且您不能保证它始终是英文。如果您知道输入的语言,最好设置一个区域。

但是,如果您需要获取时区,则更为复杂。

Names like "CEST" are ambiguous,您需要为它们做出任意选择。使用java.time可以构建一组首选时区,以便在出现歧义时使用:

Set<ZoneId> zones = new HashSet<>();
zones.add(ZoneId.of("Europe/Berlin"));
zones.add(ZoneId.of("America/Bogota"));
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
    .appendPattern("EEE MMM dd yyyy HH:mm:ss 'GMT'Z (")
    // optional long timezone name (such as "Colombia Time" or "Pacific Standard Time")
    .optionalStart().appendZoneText(TextStyle.FULL, zones).optionalEnd()
    // optional short timezone name (such as CET or CEST)
    .optionalStart().appendZoneText(TextStyle.SHORT, zones).optionalEnd()
    // close parenthesis
    .appendLiteral(')')
    // use English locale, for month, timezones and day-of-week names
    .toFormatter(Locale.US);


这样,您可以将输入解析为ZonedDateTime

// 2018-04-04T16:12:41+02:00[Europe/Berlin]
ZonedDateTime.parse("Wed Apr 04 2018 16:12:41 GMT+0200 (CEST)", fmt);

// 2018-04-04T10:25:10-05:00[America/Bogota]
ZonedDateTime.parse("Wed Apr 04 2018 10:25:10 GMT-0500 (Colombia Time)", fmt);


不幸的是,这没有解析“ SA Pacific Standard Time”案例。这是因为时区名称是在JVM中内置的,并且“ SA Pacific Standard Time”不是预定义的字符串之一。

一个很好的选择是使用M.Prokhorov在注释中建议的映射:https://github.com/nfergu/Java-Time-Zone-List/blob/master/TimeZones/src/TimeZoneList.java

然后,您手动替换字符串中的名称,并使用VV模式(而不是z)进行解析,因为映射使用IANA的名称(例如Europe/Berlin,由VV解析)。



但是最好的选择是使用toISOString(),它会在ISO8601 format中生成字符串,例如2018-04-04T17:39:17.623Z。最大的优点是java.time类可以直接解析它(您无需创建自定义格式化程序):

OffsetDateTime.parse("2018-04-04T17:39:17.623Z");

07-24 19:01
查看更多