DateTimeFormatter of YYYY-ww resolves differently on mac and ubuntu

Viewed 100
System.out.println(
    DateTimeFormatter.ofPattern("YYYY-ww").withZone(ZoneOffset.UTC).format(Instant.parse("2022-05-10T00:00:00.00Z"))
);
System.out.println(
    DateTimeFormatter.ofPattern("YYYY-ww").withZone(ZoneOffset.UTC).format(Instant.parse("2022-05-17T00:00:00.00Z"))
);

Why this pattern YYYY-ww resolves differently on Ubuntu and Mac:

Ubuntu: (default locale en_US, my computer)

2022-20
2022-21

Mac: (default locale en_GB)

2022-19
2022-20

EDIT

System.out.println(
    DateTimeFormatter.ofPattern("YYYY-ww").withLocale(Locale.UK).withZone(ZoneOffset.UTC).format(Instant.parse("2022-05-10T00:00:00.00Z"))
);
System.out.println(
    DateTimeFormatter.ofPattern("YYYY-ww").withLocale(Locale.UK).withZone(ZoneOffset.UTC).format(Instant.parse("2022-05-17T00:00:00.00Z"))
);

returns:

2022-19
2022-20 

Still, the question is why pattern ww is Locale specific? I don't see that in documentation for https://docs.oracle.com/javase/8/docs/api/java/time/temporal/WeekFields.html or https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html

2 Answers

In the US locale, the first week of the year can have one to seven days, whereas in the UK, the first week of the year needs to have at least 4 days.

WeekFields.of(Locale.UK).getMinimalDaysInFirstWeek() // 4
WeekFields.of(Locale.US).getMinimalDaysInFirstWeek() // 1

Also, a US week starts on a Sunday, whereas a UK week starts on a Monday.

This means that for the US, the first week of 2022 is the single Saturday of 2022-01-01, and 2022-01-02 is the start of the second week. On the other hand, for the UK, the first week of 2022 starts from 2022-01-03, since the first two days of the year don't form a week. And this is where that extra week for the US machine came about.

When creating a DateTimeFormatter using ofPattern, the default formatting locale of the machine is used:

The formatter will use the default FORMAT locale. This can be changed using withLocale(Locale) on the returned formatter

Hence the difference.

I suppose you want standard ISO 8601 weeks here, and not want anything to do with locales. One way to do this is to construct a DateTimeFormatter using IsoFields:

var dtf = new DateTimeFormatterBuilder()
  .  appendValue(IsoFields.WEEK_BASED_YEAR, 4)
    .appendLiteral('-')
    .appendValue(IsoFields.WEEK_OF_WEEK_BASED_YEAR, 2)
    .toFormatter().withZone(ZoneOffset.UTC);
System.out.println(
    dtf.format(Instant.parse("2022-05-10T00:00:00.00Z"))
);
System.out.println(
    dtf.format(Instant.parse("2022-05-17T00:00:00.00Z"))
);

Definition of “week” varies by locale

The Answer by Sweeper is correct. There are several definitions of a week, and they vary by locale.

You asked:

Still, the question is why pattern ww is Locale specific?

Because the definition of week is cultural.

Even limiting our scope to the seven-day week currently common in the West, which of those seven days is first day of the week? Monday is common in Europe , while the US uses Sunday. Should week # 1 start with the first occurrence of that first day of week? Or should the week start with January 1?

ISO 8601 standard

If you want consistency, consider using the standard ISO 8601 week definition:

  • Monday is first day of week, Sunday last.
  • Week # 1 contains the first Thursday of the calendar year.
  • A week-based year has either 52 or 53 complete 7-day weeks.
  • The first and last week may have zero or more days from the previous/next Calendar year, respectively.

The standard format is four-digit year, hyphen, a W, and two digits for week number. The standard formats are cleverly designed to avoid ambiguity, be easily parsed by machine, and be understandable by humans across cultures.

To work with ISO 8601 weeks, I suggest adding the ThreeTen-Extra library to your project for its YearWeek class.

YearWeek yw = YearWeek.parse( "2022-W19" ) ;
String output = yw.toString() ;

Get year-week by date.

LocalDate ld = LocalDate.parse( "2022-05-10" ) ;
YearWeek yw = YearWeek.from( ld ) ;

Get year-week from a moment in UTC represented by text in standard ISO 8601 format. The Z on the end seen below means an offset from UTC of zero hours-minutes-seconds, and is pronounced “Zulu”.

String input = "2022-05-10T00:00:00.00Z" ; 
Instant instant = Instant.parse( input ) ;
OffsetDateTime odt = instant.at( ZoneOffset.UTC ) ;
YearWeek yw = YearWeek.from( odt ) ; 
Related