Поведение PHP strtotime +1 месяц
Я знаю о нежелательном поведении функции PHP
strtotime
например, при добавлении месяца (+1 месяц) к датам типа: 31.01.2011 -> 03.03.2011
Я знаю, что это не официально ошибка PHP, и что у этого решения есть некоторые аргументы, но, по крайней мере, для меня это поведение вызвало много траты времени (в прошлом и настоящем), и я лично ненавижу.
то, что я нашел еще более странным, это, например, в:
MySQL: DATE_ADD ('2011-01-31', интервал 1 месяц) возвратит 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
также должны быть рассмотрены.
удачи в кодировании!
8 ответов
вот алгоритм, который вы можете использовать. Это должно быть достаточно просто, чтобы реализовать себя.
- иметь исходную дату и дату +1 месяц в переменных
- извлеките часть месяца обеих переменных
- если разница больше, чем 1 месяц (или если оригинал декабрь, а другой не январь) измените последнюю переменную на последний день следующего месяца. Вы можете использовать, например
t
наdate()
чтобы получить последний день:date( 't.m.Y' )
что вам нужно, это сказать 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 devs конечно не рассматривайте это как ошибку. Но в 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('Y-m-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-й даты повторения не будут в конце концов застрять на 28th после прохождения через не високосный февраль).
вот код драйвера для проверки алгоритма:
$createdDate = new DateTime("2015-03-31");
echo "created date = " . $createdDate->format("Y-m-d") . PHP_EOL;
$next = sameDateNextMonth($createdDate, $createdDate);
echo " next date = " . $next->format("Y-m-d") . PHP_EOL;
foreach(range(1, 12) as $i) {
$next = sameDateNextMonth($createdDate, $next);
echo " next date = " . $next->format("Y-m-d") . 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("Y-m-d");
$month = date("m",strtotime($startDate));
$nextmonth = date("m",strtotime("$startDate +1 month"));
if((($nextmonth-$month) > 1) || ($month == 12 && $nextmonth != 1))
{
$nextDate = date( 't.m.Y',strtotime("$initialDate +1 week"));
}else
{
$nextDate = date("Y-m-d",strtotime("$initialDate +1 month"));
}
echo $nextDate;
несколько похоже на ответ Джуханы, но более интуитивный и меньше ожидаемых осложнений. Идея такая:
- сохранить исходную дату и + N месяц(ы) дата в переменных
- извлеките дневную часть обеих переменных
- если дни не совпадают, вычесть количество дней из будущего дата
положительная сторона этого решения-это то, что работает для любой даты (а не только даты границы), а также работает для вычитания месяцев (по ставлю-вместо+). Вот пример реализации:
$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('d-m-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;
}