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

`uuuu` versus `yyyy` in `DateTimeFormatter` formatting pattern codes in Java?

DateTimeFormatter class 文档说明了它的年份格式代码:

u year year 2004; 04

y year-of-era year 2004; 04

Year: The count of letters determines the minimum field width below which padding is used. If the count of letters is two, then a reduced two digit form is used. For printing, this outputs the rightmost two digits. For parsing, this will parse using the base value of 2000, resulting in a year within the range 2000 to 2099 inclusive. If the count of letters is less than four (but not two), then the sign is only output for negative years as per SignStyle.NORMAL. Otherwise, the sign is output if the pad width is exceeded, as per SignStyle.EXCEEDS_PAD.

没有其他提及“时代”。

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

在 Java 中处理日期时,我什么时候应该使用这种模式 uuuu-MM-dd 以及什么时候 yyyy-MM-dd

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

其他格式 class 如遗留 SimpleDateFormat 只有 yyyy,所以我很困惑为什么 java.time 为“年”带来这个 uuuu时代”。

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签署年,其中0年是1 BC-1年是2 BC,等等。

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

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

当然,只有当年份为零或负数时,区别才有意义,因为这种情况很少见,所以大多数人不关心,尽管他们应该关心。

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

这被称为defensive programming:

Defensive programming is a form of defensive design intended to ensure the continuing function of a piece of software under unforeseen circumstances.


注意DateTimeFormatterSimpleDateFormat一致:

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

负年份一直是个问题,他们现在通过添加 u.

解决了这个问题

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

  • 使用“u”而不是“y”更安全,因为DateTimeFormatter will otherwise insist on having an era in combination with "y" (= year-of-era). So using "u" would avoid some possible unexpected exceptions in strict formatting/parsing. See also this 。与“y”相比,“u”符号改进的另一件小事是 printing/parsing 负公历年(在过去)。

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

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

背景资料:

在开发和集成 JSR 310 (java.time-packages) the designers decided to use Common Locale Data Repository (CLDR)/LDML 规范作为 DateTimeFormatter 中模式符号的基础时。符号“u”已经在 CLDR 中定义为公历年,所以这个含义被采用到新的即将到来的 JSR-310(但由于向后兼容性原因而不是 SimpleDateFormat)。

然而,这个遵循 CLDR 的决定并不十分一致,因为 JSR-310 还引入了新的模式符号,这些符号在 CLDR 中过去不存在,现在仍然不存在,另请参见这个旧的 CLDR-ticket. The suggested symbol "I" was changed by CLDR to "VV" and finally overtaken by JSR-310, including new symbols "x" and "X"。但是“n”和“N”在 CLDR 中仍然不存在,并且由于这张旧票已经关闭,所以不清楚 CLDR 是否会在 JSR-310 的意义上支持它。此外,票证没有提及符号“p”(JSR-310 中的填充指令,但未在 CLDR 中定义)。 所以我们在不同库和语言的模式定义之间仍然没有完全一致。

关于“y”:我们也不应忽视这样一个事实,即 CLDR 将这一时代与至少某种混合的 Julian/Gregorian 年份相关联,而不是与 JSR 的 proleptic gregorian 年份相关联- 310 确实如此(撇开负年份的奇怪之处)。所以 CLDR 和 JSR-310 之间也没有完美的协议。

长话短说

  1. 对于 99% 的目的,您可以掷硬币,使用 yyyyuuuu(或者使用 yyuu 两位数年份)。
  2. 这取决于您想要发生什么情况,以防发生早于公元 1 年(公元 1 年)的一年。关键是在 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 CE 或 1950 BCE(1950 BC)。因此,在这种情况下,您需要 u(或提供默认纪元,这可以通过 DateTimeFormatterBuilder)实现。

长话短说

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