Я знаю о нежелательном поведении функции PHP
StrToTime
Например, при добавлении месяца (+1 месяц) к датам типа: 31.01.2011 -> 03.03.2011
Я знаю, что это не ошибка PHP , и что у этого решения есть некоторые аргументы позади этого, но по крайней мере для меня это поведение вызвало много траты времени (в прошлом и настоящем), и я лично ненавижу его.
То, что я нашел еще более странным, это то, что, например, в:
MySQL: DATE_ADD ('2011-01-31', INTERVAL 1 MONTH) возвращает 2011-02-28 или
C #, где новый DateTime (2011, 01, 31) .AddMonths (1); вернется 28.02.2011
wolframalpha.com дает 31.01.2013 + 1 month
качестве входных данных; вернется в четверг, 28 февраля 2013 г.
Он считает, что другие нашли более достойное решение глупого вопроса, который я часто видел в отчетах об ошибках PHP: «Какой будет день, если я скажу, что мы встречаемся через месяц» или что-то в этом роде. Ответ: если 31 не существует в следующем месяце, введите мне последний день этого месяца, но, пожалуйста, придерживайтесь следующего месяца.
Итак, МОЙ ВОПРОС: есть ли функция PHP (написанная кем-то), которая разрешает эту официально признанную ошибку? Поскольку я не думаю, что я единственный, кто хочет другого поведения при добавлении / вычитании месяцев.
Я особенно заинтересован в решениях, которые также работают не только на конец месяца, но и на полной замене strtotime
. Также необходимо рассмотреть также strotime +n months
.
Счастливое кодирование!
Вот алгоритм, который вы можете использовать. Это должно быть достаточно простым, чтобы реализовать себя.
t
в date()
чтобы получить последний день: date( 'tmY' )
вам нужно сказать, чтобы PHP был умнее
$the_date = strtotime('31.01.2011'); echo date('r', strtotime('last day of next month', $the_date)); $the_date = strtotime('31.03.2011'); echo date('r', strtotime('last day of next month', $the_date));
предполагая, что вы интересны только в последний день следующего месяца
ссылка – http://www.php.net/manual/en/datetime.formats.relative.php
PHP-разработчики наверняка не считают это ошибкой . Но в документах strtotime
есть несколько комментариев с решениями для вашей проблемы (см. Примеры 28-го февраля;)), то есть этот расширяющий класс DateTime :
<?php // this will give us 2010-02-28 () echo PHPDateTime::DateNextMonth(strftime('%F', strtotime("2010-01-31 00:00:00")), 31); ?>
Класс PHPDateTime:
<?php /** * IA FrameWork * @package: Classes & Object Oriented Programming * @subpackage: Date & Time Manipulation * @author: ItsAsh <ash at itsash dot co dot uk> */ final class PHPDateTime extends DateTime { // Public Methods // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /** * Calculate time difference between two dates * ... */ public static function TimeDifference($date1, $date2) $date1 = is_int($date1) ? $date1 : strtotime($date1); $date2 = is_int($date2) ? $date2 : strtotime($date2); if (($date1 !== false) && ($date2 !== false)) { if ($date2 >= $date1) { $diff = ($date2 - $date1); if ($days = intval((floor($diff / 86400)))) $diff %= 86400; if ($hours = intval((floor($diff / 3600)))) $diff %= 3600; if ($minutes = intval((floor($diff / 60)))) $diff %= 60; return array($days, $hours, $minutes, intval($diff)); } } return false; } /** * Formatted time difference between two dates * * ... */ public static function StringTimeDifference($date1, $date2) { $i = array(); list($d, $h, $m, $s) = (array) self::TimeDifference($date1, $date2); if ($d > 0) $i[] = sprintf('%d Days', $d); if ($h > 0) $i[] = sprintf('%d Hours', $h); if (($d == 0) && ($m > 0)) $i[] = sprintf('%d Minutes', $m); if (($h == 0) && ($s > 0)) $i[] = sprintf('%d Seconds', $s); return count($i) ? implode(' ', $i) : 'Just Now'; } /** * Calculate the date next month * * ... */ public static function DateNextMonth($now, $date = 0) { $mdate = array(0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); list($y, $m, $d) = explode('-', (is_int($now) ? strftime('%F', $now) : $now)); if ($date) $d = $date; if (++$m == 2) $d = (($y % 4) === 0) ? (($d <= 29) ? $d : 29) : (($d <= 28) ? $d : 28); else $d = ($d <= $mdate[$m]) ? $d : $mdate[$m]; return strftime('%F', mktime(0, 0, 0, $m, $d, $y)); } } ?>
Возникла та же проблема в последнее время и в конечном итоге написал класс, который обрабатывает добавление / вычитание различных временных интервалов для объектов DateTime.
Вот код:
https://gist.github.com/pavlepredic/6220041#file-gistfile1-php
Я использую этот класс некоторое время, и, похоже, он работает нормально, но меня действительно интересует некоторый экспертный обзор. То, что вы делаете, это создать объект TimeInterval (в вашем случае вы должны указать 1 месяц в качестве интервала), а затем вызвать метод addToDate (), убедившись, что для параметра $ preventMonthOverflow задано значение true. Код будет гарантировать, что итоговая дата не переполнится в следующий месяц.
Использование образца:
$int = new TimeInterval(1, TimeInterval::MONTH); $date = date_create('2013-01-31'); $future = $int->addToDate($date, true); echo $future->format('Ym-d');
Результирующая дата: 2013-02-28
Вот реализация улучшенной версии ответа Юханы выше:
<?php function sameDateNextMonth(DateTime $createdDate, DateTime $currentDate) { $addMon = clone $currentDate; $addMon->add(new DateInterval("P1M")); $nextMon = clone $currentDate; $nextMon->modify("last day of next month"); if ($addMon->format("n") == $nextMon->format("n")) { $recurDay = $createdDate->format("j"); $daysInMon = $addMon->format("t"); $currentDay = $currentDate->format("j"); if ($recurDay > $currentDay && $recurDay <= $daysInMon) { $addMon->setDate($addMon->format("Y"), $addMon->format("n"), $recurDay); } return $addMon; } else { return $nextMon; } }
Эта версия принимает $createdDate
соответствии с предположением о том, что вы имеете дело с повторяющимся месячным периодом, таким как подписка, которая начиналась в определенную дату, например 31-го. Это всегда занимает $createdDate
настолько поздно, что «повторяется». Даты не будут меняться на более низкие значения, поскольку они продвигаются вперед по сравнению с меньшими месяцами (например, поэтому все 29, 30 или 31 даты повторения не будут в конечном итоге застревать на 28-м после прохождения через февраль без високосного года).
Вот пример кода драйвера для проверки алгоритма:
$createdDate = new DateTime("2015-03-31"); echo "created date = " . $createdDate->format("Ymd") . PHP_EOL; $next = sameDateNextMonth($createdDate, $createdDate); echo " next date = " . $next->format("Ymd") . PHP_EOL; foreach(range(1, 12) as $i) { $next = sameDateNextMonth($createdDate, $next); echo " next date = " . $next->format("Ymd") . PHP_EOL; }
Какие результаты:
created date = 2015-03-31 next date = 2015-04-30 next date = 2015-05-31 next date = 2015-06-30 next date = 2015-07-31 next date = 2015-08-31 next date = 2015-09-30 next date = 2015-10-31 next date = 2015-11-30 next date = 2015-12-31 next date = 2016-01-31 next date = 2016-02-29 next date = 2016-03-31 next date = 2016-04-30
Я решил это так:
$startDate = date("Ymd"); $month = date("m",strtotime($startDate)); $nextmonth = date("m",strtotime("$startDate +1 month")); if((($nextmonth-$month) > 1) || ($month == 12 && $nextmonth != 1)) { $nextDate = date( 'tmY',strtotime("$initialDate +1 week")); }else { $nextDate = date("Ymd",strtotime("$initialDate +1 month")); } echo $nextDate;
Скорее похоже на ответ Юханы, но более интуитивные и менее сложные предположения. Идея такова:
Плюс сторона этого решения заключается в том, что работает на любую дату (а не только на граничные даты), а также работает для вычитания месяцев (путем помещения – вместо +). Вот пример реализации:
$start = mktime(0,0,0,1,31,2015); for ($contract = 0; $contract < 12; $contract++) { $end = strtotime('+ ' . $contract . ' months', $start); if (date('d', $start) != date('d', $end)) { $end = strtotime('- ' . date('d', $end) . ' days', $end); } echo date('dm-Y', $end) . '|'; }
Результат следующий:
31-01-2015|28-02-2015|31-03-2015|30-04-2015|31-05-2015|30-06-2015|31-07-2015|31-08-2015|30-09-2015|31-10-2015|30-11-2015|31-12-2015|
function ldom($m,$y){ //return tha last date of a given month based on the month and the year //(factors in leap years) $first_day= strtotime (date($m.'/1/'.$y)); $next_month = date('m',strtotime ( '+32 day' , $first_day)) ; $last_day= strtotime ( '-1 day' , strtotime (date($next_month.'/1/'.$y)) ) ; return $last_day; }