Java中的`DateTimeFormatter`格式化模式代码中的`uuuu`与`yyyy`?

Posted

技术标签:

【中文标题】Java中的`DateTimeFormatter`格式化模式代码中的`uuuu`与`yyyy`?【英文标题】:`uuuu` versus `yyyy` in `DateTimeFormatter` formatting pattern codes in Java? 【发布时间】:2017-05-01 20:09:22 【问题描述】:

DateTimeFormatter 类文档说明了其当年的格式代码:

u 年 2004 年; 04

y 年代 2004 年; 04

年份: 字母数决定了使用填充的最小字段宽度。如果字母数为两个,则使用简化的两位数形式。对于打印,这将输出最右边的两位数。对于解析,这将使用 2000 的基值进行解析,从而得到 2000 到 2099 范围内的年份。如果字母数少于四个(但不是两个),则仅根据 SignStyle.NORMAL 输出负年份的符号。否则,如果超出焊盘宽度,则按照 SignStyle.EXCEEDS_PAD 输出符号。

没有其他提及“时代”。

那么uyyearyear-of-era这两个代码有什么区别?

在 Java 中处理日期时,我应该什么时候使用 uuuu-MM-ddyyyy-MM-dd 这样的模式?

似乎知道的人编写的示例代码使用uuuu,但为什么呢?

其他格式化类,如旧版SimpleDateFormat 只有yyyy,所以我很困惑为什么 java.time 将这个 uuuu 用于“时代”。

【问题讨论】:

java.time.* 中的模式字母基于 CLDR/LDML:unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns 对于未来的访问者:除了此页面上的答案,您还可以查看***.com/a/65928023/10819573以获得更多解释和示例。 【参考方案1】:

java.time-package的范围内,我们可以说:

使用“u”而不是“y”更安全,因为DateTimeFormatter 否则会坚持将时代与“y”(= 时代)结合使用。所以使用“u”可以避免在严格的格式化/解析中出现一些可能的意外异常。另见SO-post。与“y”相比,“u”-symbol 改进的另一件小事是打印/解析负公历年(远在过去)。

否则我们可以清楚地声明使用“u”而不是“y”打破了 Java 编程中长期存在的习惯。直观上也不清楚“u”表示任何类型的年份,因为a)英文单词“year”的第一个字母与这个符号不一致,b)SimpleDateFormat 将“u”用于不同的目的从 Java-7 (ISO-day-number-of-week) 开始。混乱是肯定的 - 永远?

我们还应该看到,如果我们考虑历史日期,则在 ISO 上下文中使用纪元(符号“G”)通常是危险的。如果“G”与“u”一起使用,则两个字段彼此无关。如果 "G" 与 "y" 一起使用,则格式化程序得到满足,但当历史日期要求不同的日历和日期处理时,仍会使用公历。

背景资料:

在开发和集成JSR 310 (java.time-packages) 时,设计人员决定使用Common Locale Data Repository (CLDR)/LDML-spec 作为DateTimeFormatter 中模式符号的基础。符号“u”已在 CLDR 中定义为预测公历年,因此新的即将推出的 JSR-310 采用了这个含义(但由于向后兼容的原因,SimpleDateFormat 没有采用这种含义)。

然而,遵循 CLDR 的决定并不完全一致,因为 JSR-310 还引入了新的模式符号,这些符号在 CLDR 中不存在并且仍然不存在,另请参见旧的CLDR-ticket。建议的符号“I”被 CLDR 更改为“VV”,最终被 JSR-310 取代,包括new symbols "x" and "X"。但是“n”和“N”在 CLDR 中仍然不存在,并且由于这个旧票已关闭,所以完全不清楚 CLDR 是否会支持 JSR-310 意义上的它。此外,票据没有提到符号“p”(JSR-310 中的填充指令,但在 CLDR 中没有定义)。 因此,我们在跨不同库和语言的模式定义之间仍然没有完美的一致性。

关于“y”:我们也不应该忽视这样一个事实,即 CLDR 将这一时代与至少某种混合的儒略年/公历年联系起来,而不是像 JSR-310 那样与预测的公历年相关联(离开撇开负年份的奇怪不谈)。所以这里 CLDR 和 JSR-310 之间也没有完美的协议。

【讨论】:

他们应该解决问题!!! Java Time 自古以来就是这么痛苦!!!我只是碰到了我认为的那件事,这是出乎意料的。他们为什么不将 System.out.println() 重命名为 System.out.write_line_and_dont_use_again_println() ?哈! @hephestos 这是题外话,但System.out.write_line_and_dont_use_again_println() 的替换只是System.out.print()【参考方案2】:

DateTimeFormatter 的javadoc 部分Patterns for Formatting and Parsing 中列出了以下3 个相关符号:

Symbol  Meaning        Presentation  Examples
------  -------        ------------  -------
 G       era            text          AD; Anno Domini; A
 u       year           year          2004; 04
 y       year-of-era    year          2004; 04

只是为了比较,这些其他符号很容易理解:

 D       day-of-year    number        189
 d       day-of-month   number        10
 E       day-of-week    text          Tue; Tuesday; T

day-of-yearday-of-monthday-of-week 显然是给定范围(年、月、周)内的

所以,year-of-era 表示给定范围(时代)内的 年份,而在其正上方 era 显示为示例值 AD(另一个值当然是BC)。

year签名 年,其中年 01 BC,年 -12 BC,依此类推。

举例说明:Julius Caesar assassinated是什么时候?

公元前 44 年 3 月 15 日(使用模式 MMMM d, y GG) 3 月 15 日,-43    (使用模式 MMMM d, u

当然,只有当年份为零或负数时,这种区别才有意义,而且由于这种情况很少见,大多数人并不关心,即使他们应该关心。

结论:如果你使用y,你也应该使用G。由于G 很少使用,所以正确的年份符号是u,而不是y,否则非正数年份会显示不正确。

这被称为defensive programming:

防御性编程是一种防御性设计形式,旨在确保软件在在不可预见的情况下的持续功能


注意DateTimeFormatterSimpleDateFormat一致:

Letter  Date or Time Component  Presentation  Examples
------  ----------------------  ------------  --------
G       Era designator          Text          AD
y       Year                    Year          1996; 96

负年一直是个问题,现在他们通过添加u 解决了这个问题。

【讨论】:

还应该提到 a) DateTimeFormatter 不够聪明,无法像 u 一样处理 y,如果没有时代,b) y 和 G 用于历史日期是错误的,因为没有支持儒略历和java.time.* 范围内的其他历史异常。 c) 这也是 CLDR 格式符号的一个普遍问题,这些符号通常以意想不到的字母开头(参见 u 与 y,其中后者表示单词“year”的第一个字母)。 关于DateTimeFormatterSimpleDateFormat之间的一致性:不,没有一致性,看看两个库中如何处理符号“u”。不一样!!!【参考方案3】:

长话短说

    对于 99 % 的目的,您可以掷硬币,无论您使用 yyyy 还是 uuuu(或者您是否使用 yyuu 来表示两位数的年份)都没有区别。李> 这取决于您希望在 1 CE (1 AD) 前一年发生的情况下发生什么。关键是,在 99% 的项目中,这样的一年永远不会发生。

另外两个答案已经展示了 uy 如何很好地工作的事实,但我仍然觉得缺少一些东西,所以我提供了稍微基于意见的答案。

格式化

假设您不希望在 1 CE 之前的一年被格式化,那么您能做的最好的事情就是检查这个假设并在它被破坏时做出适当的反应。例如,根据情况和要求,您可能会打印错误消息或抛出异常。一个非常软的故障路径可能是在这种情况下使用带有y(时代)和G(时代)的模式,在正常的当前时代情况下使用带有uy 的模式。请注意,如果您正在打印当前日期或程序编译日期,您可以确定它处于普通时代并且可以选择跳过检查。

用于解析

在许多(大多数?)情况下,解析也意味着验证意味着您无法保证输入字符串的外观。通常它来自用户或另一个系统。示例:日期字符串为 2018-09-29。这里uuuuyyyy 之间的选择应该取决于您想要发生的情况,以防字符串包含年份为0 或负数(例如,0000-08-17-012-11-13)。假设这将是一个错误,直接的答案是:使用yyyy 以便在这种情况下引发异常。更精细:使用uuuu 并在解析后执行解析日期的范围检查。后一种方法既可以进行更精细的验证,也可以在验证错误的情况下提供更好的错误消息。

特殊情况(Meno Hochschild 已经提到):如果您的格式化程序使用严格的解析器样式并且包含 y 而没有 G,则解析将总是失败,因为严格来说,年份是模棱两可的时代:1950 可能意味着公元 1950 年或公元前 1950 年(公元前 1950 年)。所以在这种情况下,您需要u(或提供默认时代,这可以通过DateTimeFormatterBuilder 实现)。

长话短说

明确日期范围检查,特别是您的年份,比依靠uuuuyyyy 之间的选择来捕捉意外的早年要好。

【讨论】:

以上是关于Java中的`DateTimeFormatter`格式化模式代码中的`uuuu`与`yyyy`?的主要内容,如果未能解决你的问题,请参考以下文章

无法使用Java 8中的DateTimeFormatter和ZonedDateTime从TemporalAccessor获取ZonedDateTime

Java日期时间API系列10-----Jdk8中java.time包中的新的日期时间API类的DateTimeFormatter

Spark 和不可序列化的 DateTimeFormatter

Java8- ZonedDateTime 与 DateTimeFormatter 无法识别格式

Java8 日期DateTimeFormatter LocalDateTime

Java 8 - ZonedDateTime 的 DateTimeFormatter 和 ISO_INSTANT 问题