PHP strtotime +1个月的行为

时间:2011-08-19 09:44:17

标签: php strtotime

我知道PHP函数的不良行为

  

的strtotime

例如,将日期(+1个月)添加到日期,例如:31.01.2011 - > 2011年3月3日

知道它不是正式的PHP bug ,并且这个解决方案背后有一些论据,但至少对我来说,这种行为造成了很多浪费时间(在过去和现在)我个人讨厌它。


我发现甚至更奇怪的是例如:

MySQL: DATE_ADD('2011-01-31',INTERVAL 1 MONTH)返回2011-02-28 或

C#其中new DateTime(2011,01,31).AddMonths(1);将于28.02.2011返回

wolframalpha.com31.01.2013 + 1 month作为输入;将于2013年2月28日星期四返回

在我看来,其他人已经找到了一个更合适的解决方案来解决这个愚蠢的问题我在PHP错误报告中看到了很多,“如果我说我们会在一个月内见面,那会是什么日子现在“或类似的东西。答案是:如果下个月不存在31,请告诉我该月的最后一天,但请坚持下个月。


所以我的问题是:是否有一个PHP函数(由某人编写)解决了这个没有官方认可的错误?因为我不认为我是唯一一个在添加/减去月份时想要另一种行为的人。

我特别感兴趣的是解决方案不仅适用于本月底,而且完全取代strtotime。此外,案件strotime +n months也应该处理。

快乐的编码!

8 个答案:

答案 0 :(得分:15)

你需要的是告诉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

答案 1 :(得分:9)

PHP开发人员don't consider this as bug。但在strtotime's docs中,对于您的问题的解决方案几乎没有评论(请查看2月28日的示例;)),即this one延长DateTime class

<?php
// this will give us 2010-02-28 ()
echo PHPDateTime::DateNextMonth(strftime('%F', strtotime("2010-01-31 00:00:00")), 31);
?>

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

}
?>

答案 2 :(得分:7)

这是您可以使用的算法。它应该足够简单来实现自己。

  • 原始日期和变量的+1个月日期
  • 提取两个变量的月份部分
  • 如果差异大于1个月(或者原件是12月而另一个不是1月),请将后一个变量更改为下个月的最后一天。您可以在t中使用date()来获取最后一天:date( 't.m.Y' )

答案 3 :(得分:3)

最近遇到了同样的问题,最后编写了一个处理向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年2月28日

答案 4 :(得分:2)

以上是Juhana's answer的改进版本的实现:

<?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这么晚才会再次发生在&#34;日期不会转移到较低的价值,因为它们被推进到较低价值的月份(例如,因此所有29日,30日或31日的复发日期最终都会在通过非飞跃后第28次被卡住年2月)。

以下是测试算法的一些驱动程序代码:

$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

答案 5 :(得分:0)

我已经通过这种方式解决了它:

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

答案 6 :(得分:0)

与Juhana的答案有些相似,但更直观,更少并发症。想法是这样的:

  1. 将原始日期和+ n个月(s)日期存储在变量中
  2. 提取两个变量的日期部分
  3. 如果天数不匹配,则减去未来日期的天数
  4. 此解决方案的另一方面适用于任何日期(不仅仅是边框日期),它也适用于减去月份(通过put - 而不是+)。 以下是一个示例实现:

    $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|
    

答案 7 :(得分:0)

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;       

}