Как работает Java "недельный год"?
Это началось как простая ошибка: у меня была YYYY
вместо YYYY
в моей строке формата для объекта SimpleDateFormat
. Но меня полностью сбило с толку результаты моих тестов с неправильной строкой формата.
Этот код:
@Test
public void whatTheHell() {
try {
SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/YYYY");
Date d1 = sdf.parse("01/07/2016");
Date d2 = sdf.parse("02/08/2016");
Date d3 = sdf.parse("11/29/2027");
System.out.println(d1.toString());
System.out.println(d2.toString());
System.out.println(d3.toString());
} catch (ParseException pe) {
fail("ParseException: " + pe.getMessage());
}
}
производит этот вывод:
Sun Dec 27 00:00:00 PST 2015
Sun Dec 27 00:00:00 PST 2015
Sun Dec 27 00:00:00 PST 2026
Я прочитал документацию по параметру "Y" здесь: https://docs.oracle.com/javase/7/docs/api/java/util/GregorianCalendar.html, но я все еще не вижу логики, которая работает здесь. В частности, последний экземпляр: я могу как-то разобраться, как даты в январе (возможно, февраль) могут быть переведены в декабрь прошлого года, но переключение даты 29 ноября назад на 11 месяцев меня озадачивает. А что такого особенного 27 декабря?
Может ли кто-нибудь объяснить?
ДОПОЛНИТЕЛЬНАЯ ИНФОРМАЦИЯ
@Я предположил, что проблема с методом toString() может быть проблемой, поэтому я определил формат даты для печати YYYY MM dd '-' yyyy MM dd
в том же коде, что и выше. Вот дополнительный вывод:
2016 12 27 - 2015 12 27
2016 12 27 - 2015 12 27
2027 12 27 - 2026 12 27
Ответы
Ответ 1
Это просто: 27 декабря 2015 года - это день 1 недели 1-го года 2016 года (а 27 декабря 2026 года - день 1 недели 1-го года 2027 года). Это можно проверить, добавив следующие строки:
SimpleDateFormat odf = new SimpleDateFormat("YYYY-ww-u");
System.out.println(odf.format(d1));
System.out.println(odf.format(d2));
System.out.println(odf.format(d3));
Если SimpleDateFormat
выводит дату, он может использовать все поля: год, месяц, день, день недели, неделя месяца, неделя в году, неделя-год и т.д.
При разборе SimpleDateFormat
ожидает соответствующий набор значений: день, месяц, год или день недели, неделя в году, неделя-год. Поскольку вы указали день недели, но не указали день недели и неделю в году, эти значения были приняты равными 1.
Фактические значения зависят от вашей локали:
- какая неделя года является неделей 1
- какой день является первым днем недели
(см. https://docs.oracle.com/javase/7/docs/api/java/util/GregorianCalendar.html#week_and_year)
В моей системе (с использованием языка de-ch, с форматом "EEE MMM dd HH: mm: ss zzz yyyy - YYYY-ww-u")
Mo Jan 04 00:00:00 MEZ 2016 - 2016-01-1
Mo Jan 04 00:00:00 MEZ 2016 - 2016-01-1
Mo Jan 04 00:00:00 MEZ 2027 - 2027-01-1
Ответ 2
Определения могут быть разными
Неделю можно определить по-разному. Например, в Соединенных Штатах первым днем недели считается воскресенье, а в Европе и во многих других местах первым днем является понедельник.
Аналогично, неделя в недельном году также может быть определена по-разному.
- Неделя № 1 содержит 1 января.
- Неделя № 1 - это первая неделя, в которой указан определенный день недели, например воскресенье.
- Неделя № 1 - это первая неделя, содержащая только дни нового года без дат предыдущего года.
- ... и многое другое
Унаследованные классы, которые вы используете, неявно используют определения, указанные в Locale
. Применяемый языковой стандарт также является неявным, используя текущее значение по умолчанию JVM Locale
, если не указано иное.
Еще более интересные подробности о сложности определения недели см. в этом Вопросе, Различное поведение WeekFields в JVM 8 и JVM 10
ISO 8601
Существует практический международный стандарт для обработки даты и времени, ISO 8601.
Определение недели ISO 8601 :
- Недели начинаются в понедельник
- Неделя № 1 имеет первый четверг года
- Год состоит из 52 или 53 недель.
- В году может быть один или несколько дней из предыдущего календарного года, а также из следующего календарного года.
Я предлагаю по возможности использовать стандартное определение ISO 8601. Это стандартное определение является простым и логичным с растущим распространением в разных отраслях.
java.time
Классы java.time предлагают некоторую поддержку для недельного года в классе WeekFields
.
LocalDate ld = LocalDate.of( 2019 , Month.JANUARY , 1 ) ;
long week = ld.get( WeekFields.ISO.weekOfWeekBasedYear() ) ;
Смотрите этот код на сайте IdeOne.com.
ld.toString(): 2019-01-01
week: 1
org.threeten.extra.YearWeek
Но если вы выполняете большую часть этой работы, я предлагаю добавить библиотеку ThreeTen-Extra в ваш проект. Вы найдете класс YearWeek
полезным. Этот класс предлагает несколько удобных методов, таких как создание LocalDate
для любого дня в пределах этой недели.
LocalDate ld = LocalDate.of ( 2019 , Month.JANUARY , 1 );
YearWeek yw = YearWeek.from ( ld );
LocalDate startOfWeek = yw.atDay ( DayOfWeek.MONDAY );
ld.toString(): 2019-01-01
yw.toString(): 2019-W01
startOfWeek.toString(): 2018-12-31
Обратите внимание, что первый день года в недельном 2019 году является датой предыдущего календарного года, а не 2019 года.
![calendar for month of January 2019, with week number, showing first week starts in the previous calendar year on 2018-12-31]()
О java.time
Среда java.time встроена в Java 8 и более поздние версии. Эти классы вытесняют проблемные старые устаревшие классы даты и времени, такие как java.util.Date
, Calendar
и & SimpleDateFormat
.
Чтобы узнать больше, см. Учебное пособие по Oracle. И поищите в Кару множество примеров и объяснений. Спецификация: JSR 310.
Проект Joda-Time, который теперь находится в режиме обслуживания, рекомендует выполнить переход на классы java.time.
Вы можете обмениваться объектами java.time напрямую с вашей базой данных. Используйте драйвер JDBC, совместимый с JDBC 4.2 или более поздней версией. Нет необходимости в строках, нет необходимости в классах java.sql.*
.
Где взять классы java.time?