У меня есть следующий код, чтобы вычислить, какой день будет через 6 месяцев с сегодняшнего дня.
// Java code
Date currentDate = (new SimpleDateFormat("yyyy-MM-dd")).parse("2024-08-30");
Calendar calendar = Calendar.getInstance();
calendar.setTime(currentDate);
calendar.add(Calendar.MONTH, 6);
Date sixMonthsLaterDate = calendar.getTime();
String sixMonthsLaterDateString = new SimpleDateFormat("yyyy-MM-dd").format(sixMonthsLaterDate);
System.out.println("sixMonthsLaterDateString: " + sixMonthsLaterDateString); // returns 2025-02-28
в Java он возвращает «28 февраля 2025 г.»
// PHP code
$currentDate = date_create_from_format('Y-m-d', '2024-08-30');
$sixMonthsLaterDate = $currentDate->modify('+6 month');
$sixMonthsLaterDateString = date_format($sixMonthsLaterDate, 'Y-m-d');
echo "sixMonthsLaterDateString: $sixMonthsLaterDateString"; // returns 2025-03-02
в PHP он возвращает «2025-03-02»
Почему они разные? Может ли кто-нибудь объяснить это? Спасибо!
«Месяц» — довольно неопределенный термин, открытый для интерпретации, особенно при попытке его сложить или вычесть. Ваша реализация Java, по-видимому, попыталась остаться «внутри» месяца и исправила дату на максимальную дату, которую февраль может иметь в 2025 году, тогда как PHP приземлился на 30 февраля 2025 года, а затем попытался исправить «переполнение», переместив соответствующее количество дней в следующем месяце.
В качестве дополнительного замечания: поведение, которое вы наблюдаете в Java, указано и обосновано в классе Calendar
. То же самое работает и для LocalDate.plusMonths()
(современный API).
Возможно, 2 марта — это глупо, но это все же верная интерпретация. Вы можете ожидать 31 января + 1 месяц = 28 января и 31 января + 2 месяца = 30 марта, но что произойдет, если вы будете добавлять по одному месяцу? 28 января + 1 месяц = 28 марта!
Повторно открыл этот вопрос. Он был закрыт как «основанный на мнении», что было неверным. Этот вопрос поднимает сложные проблемы, связанные с различными библиотеками, использующими разные подходы к определению месяцев и обработке месяцев переменной длины, а также с различными вытекающими из этого результатами. Это не основано на мнениях.
LocalDate.parse( "2024-08-30" ).plusMonths( 6 ) // Accounts for calendar months, adjusting to end-of-month if needed.
Посмотрите этот код, запущенный на Ideone.com.
2025-02-28
Вы используете ужасно ошибочные классы даты и времени, которые теперь устарели. Много лет назад они были вытеснены современными классами java.time, определенными в JSR 310.
LocalDate
Для значения только даты используйте java.time.LocalDate
.
LocalDate ld = LocalDate.parse( "2024-08-30" ) ;
Укажите полгода с классом Period
.
Period period = Period.ofMonths( 6 ) ;
Добавлять.
LocalDate sixMonthsLater = ld.plus( period ) ;
Как обсуждалось в комментариях, подсчет месяцев — сложная тема. В современную эпоху в календарной системе ISO 8601 месяцы имеют разную длину: 28, 29, 30 и 31 день.
Платформа java.time пытается учитывать календарный месяц, а не добавляет произвольное количество дней, например ( 6 * 30 )
.
Алгоритм описан в Javadoc LocalDate#plusMonths. Цитирую:
… три шага:
Добавьте введенные месяцы в поле месяца года.
Проверьте, будет ли полученная дата недействительной.
При необходимости откорректируйте день месяца на последний действительный день.
В случае ввода вашего примера 2024-08-30
шестым месяцем после августа является февраль. Затем мы рассматриваем день месяца. Но ни в одном феврале не бывает 30-го дня. Нет и дня 29 в феврале того года. Последний действительный день февраля — 28. Вуаля, 28 февраля 2025 г. — вот решение.
Я понятия не имею, что делает ваша библиотека PHP. Не учитывается календарный месяц. Это не сложение 6*30 дней или 6*31 дней.
Этот комментарий C3roe дает объяснение поведения PHP, но я его не исследовал. Цитирую:
… PHP приземлился 30 февраля 2025 г., а затем попытался исправить «переполнение», переместив соответствующее количество дней в следующий месяц.
При таком подходе два дня «переполнения» — это несуществующие 29 и 30. Прибавив эти два дня к 28 февраля 2025 года, вы получите 2 марта.
Если это действительно алгоритм подхода PHP, мне это кажется странным. Это был бы уродливый гибридный подход, не учитывающий ни полностью календарные месяцы, ни точный подсчет дней. Этот подход учитывает «лишние» дни в последнем месяце (в данном случае февраль), но игнорирует разницу в продолжительности промежуточных месяцев (в данном случае сентябрь-январь).
Мы видели следующие подходы к перемещению вперед/назад во времени по месяцам:
Могут существовать и другие подходы. Так какой же правильный подход использовать?
Правильный подход — это… все, что говорят руководители вашего проекта приложения. Вы всегда должны поднимать такие вопросы, связанные с датой и временем, с экспертами в предметной области вашего проекта. Это могут быть экспедиторы, координаторы логистики, бухгалтеры и т. д. Попросите их указать, как должен работать подсчет месяцев. Я бы рекомендовал, чтобы они сделали это в письменной форме. Затем скопируйте эти правила в комментарии вашей кодовой базы.
Кроме того, никогда не следует использовать
Date
,Calendar
иSimpleDateFormat
в Java, поскольку при некоторых обстоятельствах они тоже могут давать разные результаты. Классы имеют серьезные проблемы с дизайном и устарели за последние 10 лет. Используйте java.time, современный API даты и времени Java.