增加/减少一个月的安全方法


Safe way to increment / decrement a month

我在PHP中看到了很多关于DateTime的帖子,说要小心添加/减去天数/月份。甚至PHP手册也警告过它。我想要一个函数,它将始终将日期递增/递减到下个月/上个月。实际日期(1-28/31)与我无关。

正因为如此,我想过在每次增加/减少月份之前始终将日期设置为 14 日,因为这给出了大约 13 天的误差幅度,因此不可能跳过整个月(通过在月份的任一极端使用天数)

但我觉得这是一个糟糕的实现,但我还没有看到任何似乎可以正确处理这个问题的解决方案。事实上,SO上许多接受投票答案的线程几乎都遭受了跳过几个月的困扰。

如果日子无关紧要,你可以一个简单的模运算:

$cur_month = 6; // 0 - 11
$next_month = $cur_month + 1 % 12;
$prev_month = $cur_month + 11 % 12; // + 11 is like doing + 12 - 1

如果你也想要年份,你可以检查一下($month === 0$month === 11

我使用自定义类来增加/减少日期:

class Dates
{
    public function __construct() {}
    public function add_date($_date, $_years, $_months, $_days, $_spacer, $_leading_zeros = 1) {
        $values = explode($_spacer, $_date);
        for($i = 0;$i < 3;$i++) {$values[$i] = round($values[$i]);}
        $values[2] += $_days;
        if($values[2] > 31) {
            $aux = floor($values[2] / 31);
            while($values[2] > 31) {$values[2] -= 31;}
            $values[1] += $aux;
        }
        $values[1] += $_months;
        $values[0] += $_years;
        if($values[1] > 12) {
            $aux = floor($values[1] / 12);
            while($values[1] > 12) {$values[1] -= 12;}
            $values[0] += $aux;
        }
        $leapYear = (($values[0] % 4 == 0) && (($values[0] % 100 != 0) || ($values[0] % 400 == 0))) ? 29 : 28;
        $days = Array(0, 31, $leapYear, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
        if($values[2] > $days[$values[1]]) {
            $values[2] -= $days[$values[1]];
            $values[1]++;
        }
        if($_leading_zeros) {return $this -> leading_zeros($values[0].$_spacer.$values[1].$_spacer.$values[2], $_spacer);}
        else {return $values[0].$_spacer.$values[1].$_spacer.$values[2];}
    }
    public function subtract_date($_date, $_years, $_months, $_days, $_spacer, $_leading_zeros = 1) {
        $values=explode($_spacer, $_date);
        for($i = 0;$i < 3;$i++){$values[$i] = round($values[$i]);}
        $days = Array(0, 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
        $values[0] -= $_years;
        $values[1] -= $_months;
        while($values[1] < 1) {
            $values[1] += 12;
            $values[0]--;
        }
        $values[2] -= $_days;
        while($values[2] < 1) {     
            $values[1]--;
            if($values[1] == 0) {
                $values[1] = 12;
                $values[0]--;
            }
            if($values[1] == 2) {$days[2] = (($values[0] % 4 == 0) && (($values[0] % 100 != 0) || ($values[0] % 400 == 0))) ? 29 : 28;}
            $values[2] += $days[$values[1]];
        }
        if($_leading_zeros) {return $this -> leading_zeros($values[0].$_spacer.$values[1].$_spacer.$values[2], $_spacer);}
        else {return $values[0].$_spacer.$values[1].$_spacer.$values[2];}
    }
    public function leading_zeros($_date, $_spacer) {
        $_date = $_spacer.$_date.$_spacer;
        for($i = 1;$i < 10;$i++) {
            while(strstr($_date, $_spacer.$i.$_spacer)){$_date = str_replace($_spacer.$i.$_spacer, $_spacer.'0'.$i.$_spacer, $_date);}
        }
        $_date = substr($_date, 1);
        $_date = substr($_date, 0, -1);
        return $_date;
    }
}

我的答案是基于增加一个月,而不是基于间隔。由于我不知道您的日期是什么样子的,因此我假设它们是day.month.Yeard.m.Y的格式。因此,由于您的要求非常非常简单(除非我错过了什么),您可以执行以下操作:

$start = 1;
$end = 24;
$template = '14.{{m}}.2014';
$tz = new DateTimeZone('Europe/London');
for($i = $start; $i < $end; $i++)
{
    $date = str_replace('{{m}}', $i, $template);
    $dt = DateTime::createFromFormat('d.m.Y', $date, $tz);
    printf("'nDate: %s", $dt->format('d.m.Y'));
}

输出:

Date: 14.01.2014
Date: 14.02.2014
Date: 14.03.2014
Date: 14.04.2014
Date: 14.05.2014
Date: 14.06.2014
Date: 14.07.2014
Date: 14.08.2014
Date: 14.09.2014
Date: 14.10.2014
Date: 14.11.2014
Date: 14.12.2014
Date: 14.01.2015
Date: 14.02.2015
Date: 14.03.2015
Date: 14.04.2015
Date: 14.05.2015
Date: 14.06.2015
Date: 14.07.2015
Date: 14.08.2015
Date: 14.09.2015
Date: 14.10.2015
Date: 14.11.2015

您可以使用 strtotime 安全地执行此操作:

$ts = strtotime($yourDate);
// increment
$newTs = strtotime('first day of +1 month', $ts);
// decrement
$newTs = strtotime('first day of -1 month', $ts);
相关文章: