从 joda 时间到 java.time 的 `w` 符号格式不一致

Inconsistent `w` symbol formatting from joda time to java.time

我的团队正在寻求从 Joda 时间切换到 java.time,但我们发现使用相同模式的格式设置有不同的行为。当我们使用一年中的一周 w 符号时,问题就出现了:

final String dateString = "2016-01-04 00:00:00";
final String inputPattern = "yyyy-MM-dd HH:mm:ss";

// parse the input string using Joda
final org.joda.time.format.DateTimeFormatter jodaInputFormatter = org.joda.time.format.DateTimeFormat.forPattern(inputPattern);
final org.joda.time.DateTime jodaDateTime = jodaInputFormatter.parseDateTime(dateString);

// parse the input string using two different java.time classes
final java.time.format.DateTimeFormatter javaTimeInputFormatter = java.time.format.DateTimeFormatter.ofPattern(inputPattern).withZone(java.time.ZoneOffset.UTC);
final java.time.LocalDateTime localDateTime = java.time.LocalDateTime.parse(dateString, javaTimeInputFormatter);
final java.time.ZonedDateTime zonedDateTime = java.time.ZonedDateTime.parse(dateString, javaTimeInputFormatter);

final String outputPattern = "'week' w - dd/MM/yyyy HH:mm:ss";
final org.joda.time.format.DateTimeFormatter jodaOutputFormatter = org.joda.time.format.DateTimeFormat.forPattern(outputPattern);
final java.time.format.DateTimeFormatter javaTimeOutputFormatter = java.time.format.DateTimeFormatter.ofPattern(outputPattern);

// output: week 1 - 04/01/2016 00:00:00
System.out.println("With joda: " + jodaOutputFormatter.print(jodaDateTime));
// output: week 2 - 04/01/2016 00:00:00
System.out.println("With LocalDateTime: " + javaTimeOutputFormatter.format(localDateTime));
// output: week 2 - 04/01/2016 00:00:00
System.out.println("With ZonedDateTime: " + javaTimeOutputFormatter.format(zonedDateTime));

出于某种原因,w 符号的输出在两个实现中相差一个。

造成这种不一致的原因是什么? w 符号在 Joda 时间和 java.time 中的实现不一致吗?

编辑:正如 Richard 指出的,我错了——Java SE actually does say 以周为基础的一年的第一周是第一个以星期一为基础的一周至少包含四天,就像 Joda-Time 一样。

来自 Java SE IsoFields.WEEK_OF_WEEK_BASED_YEAR 的文档:

The week-of-week-based-year has values from 1 to 52, or 53 if the week-based-year has 53 weeks.

没有提到排除任何周,因此假设计算所有周是有意义的。

来自Joda-Time Fields overview

Weeks run from 1 to 52-53 in a week based year. The first day of the week is defined as Monday and given the value 1. The first week of a year is defined as the first week that has at least four days in the year.

1 月 1 日和 2 日构成了 2016 年的第一个部分周,由于不到四天,Joda-Time 根本不将其计为一周。 1 月 4 日是包含四天或更多天的第一周。

好吧,这有点推测,但既然你告诉我你的系统时区是 EST (-05:00),我假设你坐在美国(纽约?)。而美国不适用 ISO-8601 周规则。星期从星期日开始,一年的第一周不需要至少包含4天(即使一天也足以算作一年的第一周)。

让我们看看您的示例日期 1 月 4 日。今天是星期一。第一个美国周是从 2016-01-01 到 2016-01-02(2 天 - 对美国来说足够了)。第二个美国周从 1 月 3 日星期日开始,所以 1 月 4 日也在第二周。

现在关键点:java.time (JSR-310) 对模式符号 w 使用基于周的本地化周,另请参阅其backport which should have the same code。代码摘录:

} else if (cur == 'w') {
    if (count > 2) {
        throw new IllegalArgumentException("Too many pattern letters: " + cur);
    }
    appendInternal(new WeekFieldsPrinterParser('w', count));

...

static final class WeekFieldsPrinterParser implements DateTimePrinterParser {
    private final char letter;
    private final int count;

    public WeekFieldsPrinterParser(char letter, int count) {
        this.letter = letter;
        this.count = count;
    }

    @Override
    public boolean print(DateTimePrintContext context, StringBuilder buf) {
        WeekFields weekFields = WeekFields.of(context.getLocale());
        DateTimePrinterParser pp = evaluate(weekFields);
        return pp.print(context, buf);
    }

使用 WeekFields.of(context.getLocale()) 作为模式符号 "w" 是显而易见的。

相比之下,Joda-Time 仅使用 ISO-8601-week-definition,它让周从星期一开始,并将该周算作一年中的第一周,其中至少包含四个星期当前日历年中的天数。所以 1 月 4 日星期一是一年中第一周的开始,因为之前的三天不足以让 ISO-8601 算作一周。之前的那些日子被视为上一年的最后一周。

因此,Joda-Time 显示 1 月 4 日的第 1 周,而 java.time 使用美国第 2 周。

您的问题的解决方案是指定语言环境,这样格式化程序将使用 ISO 周,这样您就可以获得与 Joda-Time 中相同的结果。例如,您可以选择 Locale.UK,它也使用英语但其他星期规则。 不要依赖您的默认语言环境。这可能会欺骗您。