Blog. Just Blog

Как на PHP корректно прибавить месяц к дате

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Web-мастеру и не только | Автор: ManHunter
Как на PHP корректно прибавить месяц к дате
Как на PHP корректно прибавить месяц к дате

Одна из интересных задач, с которой мне приходилось сталкиваться на практике при работе с датами на PHP, это так называемый "календарный месяц". То есть некий интервал дат, отличающийся на месяц. Если дата находится где-то в середине или в начале месяца, то никаких сложностей, просто увеличиваем номер месяца на единицу, при необходимости корректируем год. А проблема начинается в тех случаях, когда дата начала интервала приходится на какое-нибудь число в конце месяца. Просто увеличить значение месяца на следующий, оставив число без изменений, нельзя, полученная дата может оказаться несуществующей. Добавлять 30 или 31 день тоже некорректно, в коротком феврале итоговая дата после такого прибавления перемахнет на начало марта. Вот для наглядности несколько примеров, чтобы было понятно, о чем идет речь:
  1. //------------------------------------------------------
  2. // Дата в начале или в середине месяца
  3. //------------------------------------------------------
  4. echo date('d.m.Y',strtotime('2015-01-12 +1 month'));
  5. // 12.02.2015 - правильно
  6.  
  7. //------------------------------------------------------
  8. // Дата в конце месяца
  9. //------------------------------------------------------
  10. echo date('d.m.Y',strtotime('2015-01-29 +1 month'));
  11. // 01.03.2015 - неправильно! Ожидалось 28.02.2015
  12.  
  13. echo date('d.m.Y',strtotime('2015-01-31 +1 month'));
  14. // 03.03.2015 - неправильно! Ожидалось 28.02.2015
  15.  
  16. echo date('d.m.Y',strtotime('2015-11-30 +1 month'));
  17. // 30.12.2015 - неправильно! Ожидалось 31.12.2015
Дата следующего короткого месяца не должна превышать количество дней в нем, а переход с отметки "конец месяца" с 30 числа ноября должен соответствовать отметке "конец месяца" декабря, то есть 31 числу, а никак не 30. Аналогично, конец февраля (28 или 29 число) при увеличении даты на один календарный месяц должен превратиться в 31 марта. Функция работы с датами strtotime, несмотря на всю свою интеллектуальность, в таких случаях просто прибавляет к исходной дате 30 календарных дней или увеличивает месяц на 1.

Конечно, окончательное решение зависит от поставленной задачи и от воли начальства. Например, значение синуса в военное время может достигать четырех понятие "месяц" при предоставлении некой услуги может быть как приравнено к 30 дням независимо от дат, так и корректироваться согласно фактической продолжительности разных месяцев. С первым вариантом я не сталкивался, а для второго была написана следующая функция более-менее корректного прибавления 1 календарного месяца к произвольной дате.
  1. //----------------------------------------------------------------------
  2. // Добавление 1 календарного месяца к TIMESTAMP
  3. //----------------------------------------------------------------------
  4. function add_month($time) {
  5.     $d=date('j',$time);  // день
  6.     $m=date('n',$time);  // месяц
  7.     $y=date('Y',$time);  // год
  8.  
  9.     // Прибавить месяц
  10.     $m++;
  11.     if ($m>12) { $y++; $m=1; }
  12.  
  13.     // Это последний день месяца?
  14.     if ($d==date('t',$time)) {
  15.         $d=31;
  16.     }
  17.     // Открутить дату до последнего дня месяца
  18.     if (!checkdate($m,$d,$y)){
  19.         $d=date('t',mktime(0,0,0,$m,1,$y));
  20.     }
  21.     // Вернуть новую дату в TIMESTAMP
  22.     return mktime(0,0,0,$m,$d,$y);
  23. }
Результаты ее работы на представленных выше примерах некорректного преобразования дат:
  1. //------------------------------------------------------
  2. // Корректная дата в конце месяца
  3. //------------------------------------------------------
  4. echo date('d.m.Y',add_month(strtotime('2015-01-29')));
  5. // 28.02.2015 - теперь все правильно
  6.  
  7. echo date('d.m.Y',add_month(strtotime('2015-01-31')));
  8. // 28.02.2015 - тоже правильно
  9.  
  10. echo date('d.m.Y',add_month(strtotime('2015-11-30')));
  11. // 31.12.2015 - все правильно, прибавлен календарный месяц
Почему я использовал определение "более-менее корректного" а не просто "корректного"? Теоретически может возникнуть ситуация, когда услуга была добавлена 28 или 29 января, то есть за несколько дней до реального конца месяца. При продлении ее на февраль, дата окончания попадает на 28 число и тем самым зафиксируется на условной отметке "конец месяца". При очередном продлении на март, дата ее окончания, вместо канувшего в Лету 28 или 29 числа, станет следующим "концом месяца", то есть 31 марта. Все следующие продления будут также ориентироваться на конец месяца. Эту неувязку можно решить, если где-нибудь в базе хранить дату первоначального добавления услуги и при продлении проверять, чтобы день месяца продления не превышал день месяца добавления. В таком случае описанная в качестве примера цепочка продления будет "29 января" -> "28 февраля" -> "29 марта" -> "29 апреля" и так далее. У меня такой задачи не стояло, вы же, при необходимости, можете легко доработать функцию, добавив в нее обработку начальной даты.
  1. //---------------------------------------------------------------------
  2. // Добавление одного или нескольких календарных месяцев к TIMESTAMP
  3. //---------------------------------------------------------------------
  4. function add_month($time$num=1) {
  5.     $d=date('j',$time);  // день
  6.     $m=date('n',$time);  // месяц
  7.     $y=date('Y',$time);  // год
  8.  
  9.     // Прибавить месяц(ы)
  10.     $m+=$num;
  11.     if ($m>12) {
  12.         $y+=floor($m/12);
  13.         $m=($m%12);
  14.         // Дополнительная проверка на декабрь
  15.         if (!$m) {
  16.             $m=12;
  17.             $y--;
  18.         }
  19.     }
  20.  
  21.     // Это последний день месяца?
  22.     if ($d==date('t',$time)) {
  23.         $d=31;
  24.     }
  25.     // Открутить дату, пока она не станет корректной
  26.     while(true) {
  27.         if (checkdate($m,$d,$y)){
  28.             break;
  29.         }
  30.         $d--;
  31.     }
  32.     // Вернуть новую дату в TIMESTAMP
  33.     return mktime(0,0,0,$m,$d,$y);
  34. }
Немного модифицированный вариант функции, позволяющий корректно прибавлять не только один, но и несколько календарных месяцев. Количество месяцев передается вторым параметром $num, если этот параметр не указан, то результат работы будет аналогичен первой функции.

Поделиться ссылкой ВКонтакте Поделиться ссылкой на Facebook Поделиться ссылкой на LiveJournal Поделиться ссылкой в Мой Круг Добавить в Мой мир Добавить на ЛиРу (Liveinternet) Добавить в закладки Memori Добавить в закладки Google
Просмотров: 5028 | Комментариев: 4

Внимание! Статья опубликована больше года назад, информация могла устареть!

Комментарии

Отзывы посетителей сайта о статье
Максим (31.01.2016 в 09:27):
Спасибо за функцию. А то столько решений однотипных, которые не решают вопрос с концом месяца
user (27.11.2015 в 22:57):
Я об этом и говорю. Как я бы делал - получилась бы громадная неэлегантная процедура, ни грамма не оптимальная и даже чуток туповатая. Главное чтоб считала верно.

)) воспроизвести человеческий интеллект, лругими словами.
(Не путать с AI).

А вообще, в случае с предоставлением услуг наверное логичнее было бы взымать оплату поденно. Ну, то уж хозяин-барин.
ManHunter (27.11.2015 в 21:25):
Считать Пасху на 65525-й год и выставлять клиентам счета за календарный месяц услуг - это несколько разные задачи. Так что понятие "правильно" в разных системах счисления может сильно отличаться. Особенно когда начальству придется объяснять клиенту, почему это у него сервер отключили не 31-го, а 28-го, и после этого разницу за простой и неустойку вычтут из премии, вот тогда все сразу встанет на свои места.
user (27.11.2015 в 20:08):
ЦитатаДобавлять 30 или 31 день тоже некорректно, в коротком феврале итоговая дата после такого прибавления перемахнет на начало марта.

Между прочим, это, пожалуй, и есть самый нормальный вариант.
Прописать в алгоритме все правила изменения дат и задать начало (например, сегодня, когда ни у кого нет сомнения, что нвнче 27 ноября, пятница). А дальше пускай программа крутит алгоритм сколько надо - всё равно понадобиться может плюс-минус 50 лет, не больше, а это ерунда.

Возился когда-то с календарями, и понял, что все формулы это фуфло. Нет нормальных. Вернее, нормальная только та, что я описал - но она возможна лишь с применением ЭВМ (чем мы и занимаемся).
Куча ошибок в определении дат тому подтверждение.

Вот, между прочим, рекомендую - прога печатает настенный календарь любого года. Вплоть до 65535-го:
old-dos.ru/dl.php?id=6527

Там в архиве для примера официальный церковный календарь до 2050-го года, где пасха дважды приходится на понедельник. Считали..

Добавить комментарий

Заполните форму для добавления комментария
Имя*:
Текст комментария (не более 2000 символов)*:

*Все поля обязательны для заполнения.
Комментарии, содержащие рекламу, ненормативную лексику, оскорбления и т.п., а также флуд и сообщения не по теме, будут удаляться. Нарушителям может быть заблокирован доступ к сайту.
Наверх
Powered by PCL's Speckled Band Engine 0.2 RC3
© ManHunter / PCL, 2008-2017
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.06 сек. / MySQL: 2 (0.0033 сек.) / Память: 4.5 Mb
Наверх