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?
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)