2

I expect these two formatters to be equivalent:

DateTimeFormatter fromBuilder = new DateTimeFormatterBuilder()
    .appendValue(IsoFields.WEEK_BASED_YEAR, 4)
    .appendLiteral('-')
    .appendValue(IsoFields.WEEK_OF_WEEK_BASED_YEAR, 2)
    .toFormatter();
DateTimeFormatter fromPattern = DateTimeFormatter.ofPattern("YYYY-ww");

But they do not give the same result:

LocalDate date = LocalDate.of(2017, 1, 1);
System.out.printf("from builder: %s%n", fromBuilder.format(date));  // prints 'from builder: 2016-52'
System.out.printf("from pattern: %s%n", fromPattern.format(date));  // prints 'from pattern: 2017-01'

What am I missing?

glerup
  • 1,532
  • 1
  • 12
  • 11
  • 1
    Your `fromPattern`-formatter is not based on ISO but your default locale settings for first-day-of-week and minimum-days-in-first-calendar-week. – Meno Hochschild Sep 04 '17 at 11:58

1 Answers1

3

The Y and w patterns correspond to a localized version of week-fields, using the JVM's default locale (java.util.Locale). The second formatter is equivalent to:

// localized week fields (using default Locale)
WeekFields weekFields = WeekFields.of(Locale.getDefault());
// equivalent to YYYY-ww
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
    .appendValue(weekFields.weekBasedYear(), 4)
    .appendLiteral('-')
    .appendValue(weekFields.weekOfWeekBasedYear(), 2)
    .toFormatter();

As this is locale dependent, it can or can't work like IsoFields. The WeekFields created above will have a different behaviour depending on the JVM's default locale.

IsoFields, on the other hand, follows ISO-8601 definition to define the week-based fields, as described in the javadoc:

The first week of a week-based-year is the first Monday-based week of the standard ISO year that has at least 4 days in the new year.

  • If January 1st is Monday then week 1 starts on January 1st
  • If January 1st is Tuesday then week 1 starts on December 31st of the previous standard year
  • If January 1st is Wednesday then week 1 starts on December 30th of the previous standard year
  • If January 1st is Thursday then week 1 starts on December 29th of the previous standard year
  • If January 1st is Friday then week 1 starts on January 4th
  • If January 1st is Saturday then week 1 starts on January 3rd
  • If January 1st is Sunday then week 1 starts on January 2nd

As 2017-01-01 is a Sunday, it corresponds to the last line above: week 1 starts on January 2nd 2017, so January 1st 2017 is still in the last week of 2016.


You can check how your WeekFields instance differs from IsoFields by calling the methods getFirstDayOfWeek() and getMinimalDaysInFirstWeek() - which are used to calculate the values of the respecitive week-based fields:

A week is defined by:

  • The first day-of-week. For example, the ISO-8601 standard considers Monday to be the first day-of-week.
  • The minimal number of days in the first week. For example, the ISO-8601 standard counts the first week as needing at least 4 days.

Together these two values allow a year or month to be divided into weeks.

In the JVM I'm using, the default locale is pt_BR, and the WeekFields created has the first day-of-week as Sunday, and minimal days in first week as 1. Check yours and you'll see that it also differs from IsoFields.


You can check ISO's definition by using the constant WeekFields.ISO: getFirstDayOfWeek() returns Monday and getMinimalDaysInFirstWeek() returns 4.


Also, remind that there's a small difference between IsoFields and WeekFields.ISO. Quoting JodaStephen's comment in this thread:

The only observable difference was that WeekFields operates on all calendar systems (by converting to ISO) whereas IsoFields only operates on ISO (and rejects other calendar systems)

  • 1
    Great answer! I can't believe none of this is mentioned in the description of the pattern symbols: https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html – glerup Sep 04 '17 at 14:12
  • Follow up question: Is is possible to write a pattern that use ISO week fields? – glerup Sep 04 '17 at 14:13
  • @glerup I don't think so, probably the only way is to use a `DateTimeFormatterBuilder` with the custom fields. –  Sep 04 '17 at 14:16
  • @glerup And it's (discretely) mentioned in [`DateTimeFormatterBuilder.appendPattern` javadoc](https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatterBuilder.html#appendPattern-java.lang.String-): *"`Y` ... append special **localized** WeekFields element for numeric week-based-year"* etc. But I agree it should be better documented in `DateTimeFormatter` class. –  Sep 04 '17 at 14:24
  • 1
    @glerup One alternative is to use a locale that behaves like ISO and set it in the formatter, such as: `DateTimeFormatter.ofPattern("YYYY-ww", Locale.UK)`. But I don't like this solution. I prefer to explicity use `IsoFields` in this case, as the locale doesn't tell (at least in a specific way) that I want to use ISO week-based fields. And I'm not sure if locale-specific behaviour can change among different Java versions (probably not, but anyway, I prefer to not take the risk). –  Sep 04 '17 at 17:02