Недельный шаблон не работает должным образом

Я ожидаю, что эти два средства форматирования будут эквивалентны:

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");

Но они не дают того же результата:

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'

Что мне не хватает?


person glerup    schedule 04.09.2017    source источник
comment
Ваш форматировщик fromPattern основан не на ISO, а на ваших региональных настройках по умолчанию для первого дня недели и минимального количества дней в первой календарной неделе.   -  person Meno Hochschild    schedule 04.09.2017


Ответы (1)


Шаблоны Y и w соответствует локализованной версии полей недели с использованием локали JVM по умолчанию (java.util.Locale). Второй форматер эквивалентен:

// 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();

Поскольку это зависит от локали, оно может работать как IsoFields, а может и не работать. Созданный выше WeekFields будет вести себя по-разному в зависимости от локали JVM по умолчанию.

IsoFields, с другой стороны, следует определению ISO-8601 для определения полей на основе недели, как описано в javadoc:

Первая неделя года, основанного на неделях, — это первая неделя стандартного года ISO, основанная на понедельнике, которая имеет не менее 4 дней в новом году.

  • Если 1st января приходится на понедельник, то неделя 1 начинается 1st января.
  • Если 1st января приходится на вторник, то неделя 1 начинается 31st декабря предыдущего стандартного года.
  • Если 1го января приходится на среду, то неделя 1 начинается 30го декабря предыдущего стандартного года.
  • Если 1го января приходится на четверг, то неделя 1 начинается 29го декабря предыдущего стандартного года.
  • Если 1го января приходится на пятницу, то неделя 1 начинается 4го января.
  • Если 1го января приходится на субботу, то неделя 1 начинается 3го января.
  • Если 1го января приходится на воскресенье, то неделя 1 начинается 2го января.

Так как 2017-01-01 — воскресенье, это соответствует последней строке выше: неделя 1 начинается 2nd января 2017, поэтому 1 январяst 2017 год — последняя неделя 2016 года.


Вы можете проверить, чем ваш экземпляр WeekFields отличается от IsoFields, вызвав методы getFirstDayOfWeek() и getMinimalDaysInFirstWeek(), которые используются для рассчитать значения соответствующих недельных полей:

Неделя определяется:

  • Первый день недели. Например, стандарт ISO-8601 считает понедельник первым днем ​​недели.
  • Минимальное количество дней в первую неделю. Например, стандарт ISO-8601 считает, что первая неделя требует не менее 4 дней.

Вместе эти два значения позволяют разделить год или месяц на недели.

В JVM, который я использую, локаль по умолчанию — pt_BR, а созданный WeekFields имеет первый день недели как воскресенье и минимальные дни первой недели как 1. Проверьте свой, и вы увидите, что он также отличается от IsoFields.


Вы можете проверить определение ISO, используя константа WeekFields.ISO: getFirstDayOfWeek() возвращает понедельник, а getMinimalDaysInFirstWeek() возвращает 4.


Также напомните, что между IsoFields и WeekFields.ISO есть небольшая разница. Цитирование JodaStephen's комментарий в этой теме:

Единственная заметная разница заключалась в том, что WeekFields работает со всеми календарными системами (путем преобразования в ISO), тогда как IsoFields работает только с ISO (и отвергает другие календарные системы).

person Community    schedule 04.09.2017
comment
Отличный ответ! Я не могу поверить, что ничего из этого не упоминается в описании символов шаблона: docs.oracle.com/javase/8/docs/api/java/time/format/ - person glerup; 04.09.2017
comment
Дополнительный вопрос: возможно ли написать шаблон, который использует поля недели ISO? - person glerup; 04.09.2017
comment
@glerup Я так не думаю, наверное, единственный способ - использовать DateTimeFormatterBuilder с настраиваемыми полями. - person ; 04.09.2017
comment
@glerup И это (дискретно) упоминается в DateTimeFormatterBuilder.appendPattern javadoc: Y ... добавить специальный локализованный элемент WeekFields для числового года на основе недели и т. д. Но я согласен это должно быть лучше задокументировано в классе DateTimeFormatter. - person ; 04.09.2017
comment
@glerup Одна из альтернатив — использовать локаль, которая ведет себя как ISO, и установить ее в форматере, например: DateTimeFormatter.ofPattern("YYYY-ww", Locale.UK). Но мне это решение не нравится. Я предпочитаю явно использовать IsoFields в этом случае, поскольку локаль не говорит (по крайней мере, определенным образом), что я хочу использовать поля на основе недели ISO. И я не уверен, может ли поведение, зависящее от локали, меняться в разных версиях Java (вероятно, нет, но в любом случае я предпочитаю не рисковать). - person ; 04.09.2017