Функции PHP и Java возвращают разные даты при расчете на 6 месяцев вперед

У меня есть следующий код, чтобы вычислить, какой день будет через 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»

Почему они разные? Может ли кто-нибудь объяснить это? Спасибо!

Кроме того, никогда не следует использовать Date, Calendar и SimpleDateFormat в Java, поскольку при некоторых обстоятельствах они тоже могут давать разные результаты. Классы имеют серьезные проблемы с дизайном и устарели за последние 10 лет. Используйте java.time, современный API даты и времени Java.

Anonymous 30.08.2024 10:04

«Месяц» — довольно неопределенный термин, открытый для интерпретации, особенно при попытке его сложить или вычесть. Ваша реализация Java, по-видимому, попыталась остаться «внутри» месяца и исправила дату на максимальную дату, которую февраль может иметь в 2025 году, тогда как PHP приземлился на 30 февраля 2025 года, а затем попытался исправить «переполнение», переместив соответствующее количество дней в следующем месяце.

C3roe 30.08.2024 10:09

В качестве дополнительного замечания: поведение, которое вы наблюдаете в Java, указано и обосновано в классе Calendar. То же самое работает и для LocalDate.plusMonths() (современный API).

Nikos Paraskevopoulos 30.08.2024 10:51

Возможно, 2 марта — это глупо, но это все же верная интерпретация. Вы можете ожидать 31 января + 1 месяц = ​​28 января и 31 января + 2 месяца = 30 марта, но что произойдет, если вы будете добавлять по одному месяцу? 28 января + 1 месяц = ​​28 марта!

Álvaro González 30.08.2024 14:02

Повторно открыл этот вопрос. Он был закрыт как «основанный на мнении», что было неверным. Этот вопрос поднимает сложные проблемы, связанные с различными библиотеками, использующими разные подходы к определению месяцев и обработке месяцев переменной длины, а также с различными вытекающими из этого результатами. Это не основано на мнениях.

Basil Bourque 30.08.2024 23:04
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
3
5
125
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

вр; доктор

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

Объясните отличие от PHP

Как обсуждалось в комментариях, подсчет месяцев — сложная тема. В современную эпоху в календарной системе ISO 8601 месяцы имеют разную длину: 28, 29, 30 и 31 день.

Java-подход

Платформа java.time пытается учитывать календарный месяц, а не добавляет произвольное количество дней, например ( 6 * 30 ).

Алгоритм описан в Javadoc LocalDate#plusMonths. Цитирую:

… три шага:

  1. Добавьте введенные месяцы в поле месяца года.

  2. Проверьте, будет ли полученная дата недействительной.

  3. При необходимости откорректируйте день месяца на последний действительный день.

В случае ввода вашего примера 2024-08-30 шестым месяцем после августа является февраль. Затем мы рассматриваем день месяца. Но ни в одном феврале не бывает 30-го дня. Нет и дня 29 в феврале того года. Последний действительный день февраля — 28. Вуаля, 28 февраля 2025 г. — вот решение.

PHP-подход

Я понятия не имею, что делает ваша библиотека PHP. Не учитывается календарный месяц. Это не сложение 6*30 дней или 6*31 дней.

Этот комментарий C3roe дает объяснение поведения PHP, но я его не исследовал. Цитирую:

… PHP приземлился 30 февраля 2025 г., а затем попытался исправить «переполнение», переместив соответствующее количество дней в следующий месяц.

При таком подходе два дня «переполнения» — это несуществующие 29 и 30. Прибавив эти два дня к 28 февраля 2025 года, вы получите 2 марта.

Если это действительно алгоритм подхода PHP, мне это кажется странным. Это был бы уродливый гибридный подход, не учитывающий ни полностью календарные месяцы, ни точный подсчет дней. Этот подход учитывает «лишние» дни в последнем месяце (в данном случае февраль), но игнорирует разницу в продолжительности промежуточных месяцев (в данном случае сентябрь-январь).

Правильное решение

Мы видели следующие подходы к перемещению вперед/назад во времени по месяцам:

  • Произвольное количество дней, например (6 * 30).
  • Подсчет календарных месяцев с корректировкой на конец календарного месяца, если это необходимо (подход java.time).
  • Гибридный подход, при котором мы перемещаемся по календарным месяцам, игнорируя их продолжительность, но затем учитываем возможные дни «переполнения» в последнем месяце (похоже, подход PHP).

Могут существовать и другие подходы. Так какой же правильный подход использовать?

Правильный подход — это… все, что говорят руководители вашего проекта приложения. Вы всегда должны поднимать такие вопросы, связанные с датой и временем, с экспертами в предметной области вашего проекта. Это могут быть экспедиторы, координаторы логистики, бухгалтеры и т. д. Попросите их указать, как должен работать подсчет месяцев. Я бы рекомендовал, чтобы они сделали это в письменной форме. Затем скопируйте эти правила в комментарии вашей кодовой базы.

Другие вопросы по теме

Похожие вопросы

Файл журнала Log4J остается пустым при программной настройке
Почему JPA все еще работает с классом записи как с сущностью?
Приложение Spring Framework возвращает только ошибки 404
Spring: получение ошибки грамматики неправильного sql для именованного параметра – удаление параметра устраняет ошибку
Есть ли в Visual Studio Code сочетания клавиш, которые переключают детали предложения и всплывающие окна режима объяснения в предложениях?
Вывод консоли IntelliJ IDEA неправильно отображает символы вертикальной табуляции (U + 000B)
Ошибка создания bean-компонента с именем «entityManagerFactory», определенным в ресурсе пути к классу: невозможно построить Hibernate SessionFactory;
Как использовать внешние файлы конфигурации для обеспечения специфичных для среды конфигураций в приложении Spring Boot?
Как использовать общие файлы миграции для mysql и h2
Отключение клиента администратора Kafka Streams