【问题标题】:Achieve DateTimeFormatter Y10K output equal to SimpleDateFormat?实现DateTimeFormatter Y10K输出等于SimpleDateFormat?
【发布时间】:2021-11-15 11:59:06
【问题描述】:

在为某些日期助手编写单元测试时,我偶然发现了 DateTimeFormatter 的特定行为,我想了解如何解决。

当输出年份 >9999 时,总是在年份数字前面加上一个加号。一些快速代码来说明这一点:

LocalDate localDate = LocalDate.of(9999, 1, 1);
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
cal.set(9999, 0, 1, 12, 0 , 0);

// following assertion passes as both strings are "01-01-9999"
Assertions.assertEquals(
    new SimpleDateFormat("dd-MM-yyyy").format(cal.getTime()),
    localDate.format(DateTimeFormatter.ofPattern("dd-MM-yyyy"))
);


localDate = localDate.plusDays(365);
cal.add(Calendar.DAY_OF_MONTH, 365);

// following assertion passes (lengthy workaround using SimpleDateFormat)
Assertions.assertEquals(
    new SimpleDateFormat("dd-MM-yyyy").format(cal.getTime()),
    new SimpleDateFormat("dd-MM-yyyy").format(Timestamp.valueOf(localDate.atTime(LocalTime.MIDNIGHT)))
);

// following assertion fails:
// Expected : "01-01-10000"
// Actual   : "01-01-+10000"
Assertions.assertEquals(
    new SimpleDateFormat("dd-MM-yyyy").format(cal.getTime()),
    localDate.format(DateTimeFormatter.ofPattern("dd-MM-yyyy"))
);

现在docs 表示年份模式:

如果字母数少于四个(但不是两个),则仅根据 SignStyle.NORMAL 输出负年份的符号。否则,如果超出焊盘宽度,则按照 SignStyle.EXCEEDS_PAD 输出符号。

所以这给出了一个提示,但我仍然一无所知:

如何使DateTimeFormatter 在我的示例中输出与SimpleDateFormat.format() 完全相同的Y10K+ 日期字符串(=无符号表示正数>9999)?

【问题讨论】:

  • 我建议您放弃使用旧的日期类,包括 DateCalendarDateTimeFormatter。这些可怕的类在几年前被 JSR 310 中定义的现代 java.time 类所取代。
  • 好建议——Java 生态系统确实充满了遗留(pre java8+)库,这些库遭受古怪的接口或产生有趣的结果。希望有一个硬(er)的弃用政策或更清楚地暗示最现代的图书馆在集成文档中是什么,目前对于没有经验的人来说,看到那些是什么感觉比必要的困难。展望未来,我正在努力只将最现代的库用于新代码——但由于我目前的任务是为现有代码编写单元测试,因此详细了解这些细微差异以及如何解决它们非常重要。
  • @BasilBourque DateTimeFormatter 实际上是一个 java.time 类——我假设你的意思是建议放弃 java.text.SimpleDateFormat,对吗?
  • @Philsen,哎呀!是的,我上面的第一条评论应该是:我建议你放弃使用旧的日期类,包括DateCalendarSimpleDateFormat。这些可怕的类在几年前被 JSR 310 中定义的现代 java.time 类所取代,例如 LocalDateDateTimeFormatter

标签: java datetime simpledateformat datetimeformatter y10k


【解决方案1】:

ISO 8601 确实允许这种年份格式。来自Wikipedia

为了表示 0000 之前或 9999 之后的年份,该标准还允许扩展年份表示,但只能通过发送方和接收方之间的事先协议。扩展的年份表示 [±YYYYY] 必须具有超出四位数最小值的商定的额外年份数字,并且必须以 + 或 - 符号作为前缀,而不是更常见的 AD/BC(或 CE/BCE ) 符号;

但是,由于它只允许“通过发送方和接收方之间的事先约定”进行此操作,因此添加符号是LocalDate.toString 的默认行为是很奇怪的。

根据docs

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

所以如果你不想要这个符号,你可以使用 3 个“y”,或者只使用 1 个“y”,因为 3 和 1 都是“少于四个(但不是两个)”。

另外,由于“y”表示“时代”,因此不会有任何负年份,因此您也不必担心它会输出负年份的符号。

例子:

System.out.println(
    LocalDate.of(10000, 1, 1)
        .format(DateTimeFormatter.ofPattern("dd-MM-yyy"))
); // 01-01-10000

更一般地,您可以使用DateTimeFormatterBuilder 中的appendValue(TemporalField, int, int, SignStyle) 方法指定符号样式。您可以指定SignStyle.NEVER 使其从不输出符号。

【讨论】:

  • 这是一个很好的答案——标准引用、文档分析、到目前为止我错过的直接解决方案......所有的花里胡哨:) 接受。
  • 实际上——我担心在完美世界中签署的 BC 年份——我意识到这可以通过大写年份模式来实现,即“dd-MM-YYY”。虽然这会产生不同的日期结果,因为它是基于周的?......如果有人知道一个简单、一致的模式解决方案,那会很酷,否则我意识到这超出了我最初问题的范围,因此,这是尽管如此,一个很好的答案。
  • @Philzen 你想要签署 BC 年吗? dd-MM-uuu 怎么样?
  • 不,大写 YYY 是错误的(它是基于周的年份,仅对周数有用)。 uuu(小写)给你一个签名的年份。或者简单地检查getYear() 返回一个正数,如果不是,则拒绝该日期。另见uuuu versus yyyy in DateTimeFormatter formatting pattern codes in Java?
猜你喜欢
  • 2022-01-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-14
  • 2019-09-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多