Получение даты понедельника от заданной недели и года с использованием пакета java.time
Я хочу получить дату понедельника для данной недели и года, используя пакет Java 8 java.time. Но в какой-то момент я сталкиваюсь с проблемой, поскольку она не возвращает правильную дату.
private LocalDate getDateFromWeekAndYear(final String week,final String year){
LocalDate date = LocalDate.now();
date = date.with(WeekFields.ISO.dayOfWeek(), 1);
date = date.with(WeekFields.ISO.weekOfWeekBasedYear(), Long.parseLong(week));
date = date.with(WeekFields.ISO.weekBasedYear(), Long.parseLong(year));
return date;
}
Например:
- Если я пройду неделю =1 и год =2013, тогда дата: 2012-12-31.
- Но если я пройду неделю =53 и год =2015, тогда дата: 2014-12-29. Я ожидал 2014-12-28.
Есть ли какая-то логическая ошибка, которую я делаю, или какая-то другая проблема?
5 ответов
Это удивительно сложнее, чем частично неверные ожидания ОП, и большинство ответов показывают.
Во-первых, сказать: очень важно определить правильный порядок недельных манипуляций. ОП сначала применяет манипулирование днем, а затем манипулирование на основе года. Правильный подход обратный! Я покажу правильную реализацию вспомогательного метода:
public static void main(String... args) {
System.out.println(
getDateFromWeekAndYear("53", "2015")); // 2015-12-28, NOT 2014-12-28
System.out.println(
getDateFromWeekAndYear("53", "2015").get(WeekFields.ISO.weekOfWeekBasedYear())); // 53
System.out.println(
getDateFromWeekAndYear("53", "2014")); // 2014-12-29
System.out.println(
getDateFromWeekAndYear("53", "2014").get(WeekFields.ISO.weekOfWeekBasedYear())); // 1
}
private static LocalDate getDateFromWeekAndYear(final String week,final String year) {
int y = Integer.parseInt(year);
LocalDate date = LocalDate.of(y, 7, 1); // safer than choosing current date
// date = date.with(WeekFields.ISO.weekBasedYear(), y); // no longer necessary
date = date.with(WeekFields.ISO.weekOfWeekBasedYear(), Long.parseLong(week));
date = date.with(WeekFields.ISO.dayOfWeek(), 1);
return date;
}
Если вы не соблюдаете этот конкретный порядок, то для ввода 2015-W53 вы действительно иногда получите дату 2014 года (в зависимости от текущей даты).
Вторая проблема: я также избегал начинать с текущей даты, чтобы не быть ближе к началу или концу календарного года (календарный год!= Недельный год), и вместо этого выбрал середину года в качестве отправной точки.
Третья проблема - мягкая обработка недели 53 в (недельном) году - 2014. Этого не существует, потому что в 2014 году было всего 52 недели!!! Строгий алгоритм должен распознавать и отклонять такой ввод. Поэтому я советую не использовать YearWeek.of(2014, 53)
(во внешней библиотеке Threeten-Extra) по итогам первой недели 2015 года, см. также его Javadoc. Лучше, чем такое мягкое обращение было бы
YearWeek yw = YearWeek.of(2014, 52);
if (yw.is53WeekYear()) {
yw = YearWeek.of(2014, 53);
}
или используя этот код из моей собственной библиотеки времени Time4J (чей класс CalendarWeek имеет дополнительные функции i18n и дополнительную арифметику недели по сравнению с YearWeek
):
CalendarWeek.of(2014, 53); // throws an exception
System.out.println(CalendarWeek.of(2014, 1).withLastWeekOfYear()); // 2014-W52
Только используя java.time
-package:
Использование таких внешних библиотек, по крайней мере, помогло бы решить первую проблему прозрачным способом. Если вы не хотите добавлять дополнительную зависимость, вы можете сделать это для обработки недели 53, если она недействительна:
Если выражение WeekFields.ISO.weekOfWeekBasedYear()
При применении к результату вашего вспомогательного метода выдается значение 1, тогда вы знаете, что неделя 53 была недействительной. И тогда вы можете решить, хотите ли вы принять снисходительное обращение или выдать исключение. Но тихая настройка такого неверного ввода - ИМХО плохой дизайн.
Сначала вам нужно рассчитать первый понедельник первой недели года, а затем просто добавить плюс 7 к дате.
public static LocalDate firstMonday(int week, int year) {
LocalDate firstMonOfFirstWeek = LocalDate.now()
.with(IsoFields.WEEK_BASED_YEAR, year) // year
.with(IsoFields.WEEK_OF_WEEK_BASED_YEAR, 1) // First week of the year
.with(ChronoField.DAY_OF_WEEK, 1); // Monday
// Plus multi of 7
return firstMonOfFirstWeek.plusDays( (week - 1) * 7);
}
public static void main(String[] args) {
System.out.println(firstMonday(1, 2013)); // 2012-12-31
System.out.println(firstMonday(53 ,2015 )); // 2015-12-28
}
ТЛ; др
YearWeek.of ( 2013 , 1 ).atDay ( DayOfWeek.MONDAY )
Неправильные ожидания
Ваши ожидания не верны. Понедельник 2015-W53
является 2015-12-28
не 2014-12-28
, 2015 год, а не 2014. Нет оснований ожидать 2014 года. Пожалуйста, измените свой Вопрос, чтобы объяснить свои мысли, если вам нужно больше объяснений.
Вы можете быть озадачены календарным годом по сравнению с недельным годом. В определении ISO 8601 года на основенедели первая неделя содержит первый четверг календарного года. Это означает, что у нас есть некоторые совпадения между годами. Последние несколько дней календарного года могут находиться в следующем недельном году. И наоборот, первые несколько дней календарного года могут находиться в предыдущем недельном году.
В качестве примера, вы можете увидеть на скриншоте ниже, последний день календаря 2012 (31 декабря) приходится на первую неделю следующего года 2013 года на неделю номер 1. А на другом снимке экрана мы имеем наоборот, когда первые три дня календаря 2016 года (1, 2 и 3 января) приземляются в 2015 году на неделе за неделю № 53.
- Понедельник
2013-W01
является2012-12-31
, - Понедельник
2015-W53
является2015-12-28
,
YearWeek
Я предлагаю добавить библиотеку ThreeTen-Extra в ваш проект, чтобы использовать YearWeek
учебный класс. Вместо того, чтобы передавать простые целые числа в течение года и недели, передавайте объекты этого класса. Это делает ваш код более самодокументируемым, обеспечивает безопасность типов и обеспечивает допустимые значения.
// Pass ( week-based-year-number, week-number ). *Not* calendar year! See the ISO 8601 standard.
YearWeek yw = YearWeek.of( 2013 , 1 );
Вы можете вытащить любой день с этой недели.
LocalDate ld = yw.atDay( DayOfWeek.MONDAY );
Давайте попробуем этот вид кода.
YearWeek yw1 = YearWeek.of ( 2013 , 1 );
LocalDate ld1 = yw1.atDay ( DayOfWeek.MONDAY );
YearWeek yw2 = YearWeek.of ( 2015 , 53 );
LocalDate ld2 = yw2.atDay ( DayOfWeek.MONDAY );
System.out.println ( "yw1: " + yw1 + " Monday: " + ld1 );
System.out.println ( "yw2: " + yw2 + " Monday: " + ld2 );
yw1: 2013-W01 понедельник: 2012-12-31
yw2: 2015-W53 понедельник: 2015-12-28
Подсказка. Чтобы увидеть эти стандартные номера недели ISO 8601 на Mac в приложении Calendar.app, установите System Preferences
> Language & Region
> Calendar
> ISO 8601
, Затем в Calendar.app установите Preferences
> Advanced
> Show week numbers
,
Я хотел понедельник недели около 6 месяцев назад, а точнее 26 недель назад. Ниже код дал мне требуемую дату:
LocalDate.now().minusWeeks(26).with(WeekFields.ISO.dayOfWeek(), DayOfWeek.MONDAY.getValue())