Javascript Date.toString 输出的 DateTimeFormatter

DateTimeFormatter for Javascript Date.toString output

尝试使用 Java 的 DateTimeFormatter 支持 Java 脚本的 new Date().toString() 输出格式,但似乎无法正常工作。

Js 输出具有以下性质:

我当前的格式化程序:

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 的那些日期字符串,我得到以下错误:

[date] could not be parse at index 25

上述两个日期的索引 25 是:

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

Wed Apr 04 2018 10:25:10 GMT-05:00 (Colombia Time)

所以 GMT-05:00 的部分在收到的字符串中被执行为 GMT-0500 但我找不到匹配的 reserved pattern letter

文档说:

Offset O: This formats the localized offset based on the number of pattern letters. One letter outputs the short form of the localized offset, which is localized offset text, such as 'GMT', with hour without leading zero, optional 2-digit minute and second if non-zero, and colon, for example 'GMT+8'. Four letters outputs the full form, which is localized offset text, such as 'GMT, with 2-digit hour and minute field, optional second field if non-zero, and colon, for example 'GMT+08:00'. Any other count of letters throws IllegalArgumentException.

Offset Z: This formats the offset based on the number of pattern letters. One, two or three letters outputs

the hour and minute, without a colon, such as '+0130'. The output will be '+0000' when the offset is zero. Four letters outputs the full form of localized offset, equivalent to four letters of Offset-O. The output will be the corresponding localized offset text if the offset is zero. Five letters outputs the hour, minute, with optional second if non-zero, with colon. It outputs 'Z' if the offset is zero. Six or more letters throws IllegalArgumentException.

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

非常感谢帮助,谢谢

编辑

感谢@mszymborski,我设法通过验证括号部分“(CEST)”,这里有什么用处?

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

JavaScript is a big mess. toString() is not only browser/implementation dependent 中的日期,而且 区域设置敏感 。我在巴西,所以我的浏览器设置为葡萄牙语,new Date().toString() 给出了这个结果:

Wed Apr 04 2018 14:14:04 GMT-0300 (Hora oficial do Brasil)

月份和 day-of-week 名称为英文,但时区名称为葡萄牙文。真是一团糟!

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

Do you need to get the timezone or just the offset?

偏移量 GMT+0200 是 used by more than one country(因此,不止一个时区使用它)。虽然偏移量足以有一个 non-ambiguous 时间点,但仅仅知道时区是不够的。

连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 中的时区名称是 built-in 并且 "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");