【问题标题】:DateTimeFormatter for Javascript Date.toString outputJavascript Date.toString 输出的 DateTimeFormatter
【发布时间】:2018-09-14 05:55:56
【问题描述】:

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

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

[date] 无法在索引 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

文档说:

Offset O:这会根据数量来格式化本地化的偏移量 图案字母。一个字母输出本地化的简写形式 offset,即本地化的偏移文本,例如'GMT',带小时 没有前导零,如果非零,可选的 2 位数分钟和秒, 和冒号,例如“GMT+8”。四个字母输出完整的形式, 这是本地化的偏移文本,例如 'GMT,带有 2 位数的小时和 分钟字段,可选的第二个字段(如果非零)和冒号,用于 例如“GMT+08:00”。任何其他的字母计数 IllegalArgumentException。

偏移 Z:这会根据图案的数量格式化偏移 字母。一个、两个或三个字母输出

小时和分钟,不带冒号,例如“+0130”。输出将 当偏移量为零时为“+0000”。四个字母输出完整形式 本地化偏移量,相当于四个字母的 Offset-O。这 如果偏移量,输出将是相应的本地化偏移量文本 为零。五个字母输出小时、分钟,可选秒 如果非零,则使用冒号。如果偏移量为零,则输出“Z”。六或 更多字母会引发 IllegalArgumentException。

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

非常感谢您的帮助,谢谢

编辑

感谢@mszymborski,我成功地验证了括号部分“(CEST)”,这里有什么用处?

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

  • GMT-0500(南非太平洋标准时间)错误
  • GMT+0200 (CEST) 通行证

【问题讨论】:

  • 您可以转义 GMT 部分并使用 Z,例如:EE MMM dd yyyy HH:mm:ss 'GMT'Z
  • 有趣的是,如果你遍历TimeZone.getAvailableIDs(),然后从这些 ID 中获取 TimeZone (TimeZone.getTimeZone(id)),然后打印 ID 和显示名称,似乎 SA 太平洋标准时间不是那里。 CEST 也不存在,但中欧时间 (CET) 存在。不同之处在于 CEST 不跟踪夏令时。我认为使用 DateTimeFormatter 是不可行的。
  • 进一步补充——有人发布了这个:github.com/nfergu/Java-Time-Zone-List/blob/master/TimeZones/src/…,看来你需要准备某种字典来在 JS 区域和 Java 区域之间进行翻译。
  • 您绝对不应该依赖 JS Date.toString() 返回的内容:每个应用程序/浏览器/用户区域设置都不同。如果您想通过 REST 调用进行通信,请确保双方完全确定数据传输的格式:这种方式对双方来说要容易得多。例如,使用Date.toISOString
  • @estebanrincon,您实际上根本不需要时区名称。您已经有了时区偏移,这足以构建一个明确的 Instant 值。不管名字是什么,你都可以忽略它。

标签: javascript java java-8 java-time


【解决方案1】:

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

2018 年 4 月 4 日星期三 14:14:04 GMT-0300(巴西官方网站)

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

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

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

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 太平洋标准时间”的情况。这是因为时区名称是内置在 JVM 中的,并且“SA Pacific Standard Time”不是预定义的字符串之一。

一个不错的选择是使用 M.Prokhorov 在 cmets 中建议的映射: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");

【讨论】:

  • 我想提一下,OffsetDateTime 中没有“魔法”可以解析这些看似不需要任何格式化程序。它内部使用的是DateTimeFormatter.ISO_OFFSET_DATE_TIME。对于各种 ISO 格式变体,有几个这样的常量。
  • 说到乱七八糟,想想这个:monthEnd = new Date(2021, 1, 31); 当你期待 1 月 31 日 时,你会感到各种沮丧,但得到 3 月 3 日我>!
猜你喜欢
  • 2012-08-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-15
  • 2018-04-27
  • 2017-04-11
  • 2019-05-16
  • 2012-01-04
相关资源
最近更新 更多