DateTime::modify

date_modify

(PHP 5 >= 5.2.0, PHP 7, PHP 8)

DateTime::modify -- date_modify修改日期时间对象的值

说明

面向对象风格

public DateTime::modify ( string $modifier ) : DateTime

过程化风格

date_modify ( DateTime $object , string $modifier ) : DateTime

修改一个日期时间对象的值。 支持 DateTimeImmutable::__construct() 函数所允许的字符串。

参数

object

仅过程化风格:由 date_create() 返回的 DateTime 类型的对象。此函数会修改这个对象。

modifier

日期/时间字符串。正确格式的说明详见 日期与时间格式

返回值

返回被修改的 DateTime 对象, 或者在失败时返回 false.

范例

Example #1 DateTime::modify() 例程

面向对象风格

<?php
$date 
= new DateTime('2006-12-12');
$date->modify('+1 day');
echo 
$date->format('Y-m-d');
?>

过程化风格

<?php
$date 
date_create('2006-12-12');
date_modify($date'+1 day');
echo 
date_format($date'Y-m-d');
?>

以上例程会输出:

2006-12-13

Example #2 增加或者减少月份的时候需要注意

<?php
$date 
= new DateTime('2000-12-31');

$date->modify('+1 month');
echo 
$date->format('Y-m-d') . "\n";

$date->modify('+1 month');
echo 
$date->format('Y-m-d') . "\n";
?>

以上例程会输出:

2001-01-31
2001-03-03

参见

User Contributed Notes

wes at anonymous dot com 25-May-2021 09:12
Beware, the modify function does not treat .5 as a half minute!

echo "start with:           $str\n";
$datetimeObj = new DateTime($str);
$datetimeObj->modify('1 minutes');
echo "add 1 min:            ".$datetimeObj->format('m/d/Y h:i:s A')."\n";
$datetimeObj = new DateTime($str);
$datetimeObj->modify('.5 minutes');
echo "add .5 min:           ".$datetimeObj->format('m/d/Y h:i:s A')."\n";
$datetimeObj->modify('.53453531 minutes');
echo "add .53453531 min:    ".$datetimeObj->format('m/d/Y h:i:s A')."\n";

start with:              05/25/2021 02:53:40 PM
add 1 min:              05/25/2021 02:54:40 PM
add .5 min:             05/25/2021 02:58:40 PM
add .53453531 min: 01/12/2123 03:09:40 AM

.5 minutes actually ends up adding 5 minutes, not a half minute, and .53453531 adds 53453531 minutes.

So, if you have a function that accepts a number for adding minutes to a datetime, you have to check for < 1 and > -1, and then possibly convert that to seconds and do a seconds modification.
sg@rg 31-Mar-2020 12:48
Made a different fixed version using the month not the day

<?php

function modifyMonth(DateTime $date, string $modificator): DateTime {
   
$destMonth = $date->format('m') + $date->format('Y') * 12 + ($modificator);
   
$date->modify("$modificator months");
    while (
$date->format('m') + $date->format('Y') * 12 > $destMonth)
       
$date->modify('-1 day');
    return
$date;
}
?>

Testing :

<?php
$dates
= [
    [
'2020/02/29', '+1', '2020/03/29'],
    [
'2020/02/29 12:34', '-1', '2020/01/29 12:34'],
    [
'2020/03/29 01:01', '+1', '2020/04/29 01:01'],
    [
'2020/03/29', '-1', '2020/02/29'],
    [
'2020/03/30', '+1', '2020/04/30'],
    [
'2020/03/30 23:58', '-1', '2020/02/29 23:58'],
    [
'2020/03/31', '+1', '2020/04/30'],
    [
'2020/03/31', '-1', '2020/02/29'],
    [
'2020/03/31', '+2', '2020/05/31'],
    [
'2020/03/31 00:01', '-2', '2020/01/31 00:01'],
    [
'2020/03/01', '+1', '2020/04/01'],
    [
'2020/03/01', '-1', '2020/02/01'],
    [
'2020/02/01', '+1', '2020/03/01'],
    [
'2020/02/01', '-1', '2020/01/01'],
    [
'2020/04/01', '+1', '2020/05/01'],
    [
'2020/04/01 22:22', '-1', '2020/03/01 22:22'],
    [
'2020/12/31', '+2', '2021/02/28'],
    [
'2020/01/31', '-2', '2019/11/30'],
];

foreach (
$dates as $date) {
   
$test = new DateTime($date[0]);
   
modifyMonth($test, $date[1]);
    if (
$test != new DateTime($date[2]))
        echo
$date[0] . " " . $date[1] . " != " . $test->format('c') . "\n";
}
?>

Testing prints no error
widowmaker at example dot com 25-Oct-2019 04:27
date_default_timezone_set('Europe/Amsterdam');

$dt = new DateTime('27 October 2019 00:20:00');
print_r($dt);
$dt->modify('600 min');
print_r($dt);

$dt = new DateTime('27 October 2019 00:20:00');
$dt->add(date_interval_create_from_date_string('600 min'));
print_r($dt);

// produces
DateTime Object
(
    [date] => 2019-10-27 00:20:00.000000
    [timezone_type] => 3
    [timezone] => Europe/Amsterdam
)
DateTime Object
(
    [date] => 2019-10-27 10:20:00.000000
    [timezone_type] => 3
    [timezone] => Europe/Amsterdam
)
DateTime Object
(
    [date] => 2019-10-27 09:20:00.000000
    [timezone_type] => 3
    [timezone] => Europe/Amsterdam
)
vittorio dot zamparella at gmail dot com 18-Feb-2019 04:53
@Anonimous
There is no bug. Especially not any retarded one.

<?
function plusOneMonthTests($dateString, $expectation) {
    $date = new DateTime($dateString);
    echo "[".$date->format('Y-m-d')."] +1 month = [".$date->modify('+1 month')->format('Y-m-d')."] $expectation \n";
}   
plusOneMonthTests('2001-01-01', 'as expected');
plusOneMonthTests('2001-01-27', 'as expected');
plusOneMonthTests('2001-01-28', 'as expected');
plusOneMonthTests('2001-01-29', 'what would you expect?');
plusOneMonthTests('2001-01-30', 'what would you expect?');
plusOneMonthTests('2001-01-31', 'what would you expect?');
?>
Result:
[2001-01-01] +1 month = [2001-02-01] as expected
[2001-01-27] +1 month = [2001-02-27] as expected
[2001-01-28] +1 month = [2001-02-28] as expected
[2001-01-29] +1 month = [2001-03-01] what would you expect? 29 of february??
[2001-01-30] +1 month = [2001-03-02] what would you expect? or 30 of february?
[2001-01-31] +1 month = [2001-03-03] what would you expect?

As with any tool you need to know how to use it.

I think most people are looking for "the same day of the next month" (or any other number or months).

The calendar is twisted, don't blame the library.
ilyaplot at gmail dot com 15-Dec-2017 10:05
function subMonths(\Datetime $dateTime, int $months)
{
    if ($invert = $months < 0) {
        $months *= -1;
    }

    for ($i=0; $i<$months; $i++) {
        $daysOfMonth = cal_days_in_month(CAL_GREGORIAN, $dateTime->format('m'), $dateTime->format('Y'));
        $dateTime->modify(($invert ? '-' : '+') . $daysOfMonth . ' day');
    }
    return $dateTime;
}

$dateTime = new \DateTime('2004-12-31');

echo $dateTime->modify('-3 month')->format('Y-m-d'); // 2004-10-01

echo subMonths($dateTime, -3)->format('Y-m-d'); // 2004-09-30
mangotonk at gmail dot com 24-Jan-2017 05:55
a slightly more compact way of getting the month shift

<?php

     
/**
     * correctly calculates end of months when we shift to a shorter or longer month
     * workaround for http://php.net/manual/en/datetime.add.php#example-2489
     *
     * Makes the assumption that shifting from the 28th Feb +1 month is 31st March
     * Makes the assumption that shifting from the 28th Feb -1 month is 31st Jan
     * Makes the assumption that shifting from the 29,30,31 Jan +1 month is 28th (or 29th) Feb
     *
     *
     * @param DateTime $aDate
     * @param int $months positive or negative
     *
     * @return DateTime new instance - original parameter is unchanged
     */

   
function MonthShifter (DateTime $aDate,$months){
       
$dateA = clone($aDate);
       
$dateB = clone($aDate);
       
$plusMonths = clone($dateA->modify($months . ' Month'));
       
//check whether reversing the month addition gives us the original day back
       
if($dateB != $dateA->modify($months*-1 . ' Month')){
           
$result = $plusMonths->modify('last day of last month');
        } elseif(
$aDate == $dateB->modify('last day of this month')){
           
$result $plusMonths->modify('last day of this month');
        } else {
           
$result = $plusMonths;
        }
        return
$result;
    }

//TEST

$x = new DateTime('2017-01-30');
echo(
$x->format('Y-m-d')." past end of feb, but not dec<br>");
echo(
'b ' . MonthShifter($x,1)->format(('Y-m-d'))."<br>");
echo(
'c ' . MonthShifter($x,-1)->format(('Y-m-d'))."<br>");

$x = new DateTime('2017-01-15');
echo(
"<br>" . $x->format('Y-m-d')." middle of the month <br>");
echo(
'd ' . MonthShifter($x,1)->format(('Y-m-d'))."<br>");
echo(
'e ' . MonthShifter($x,-1)->format(('Y-m-d'))."<br>");

$x = new DateTime('2017-02-28');
echo(
"<br>" . $x->format('Y-m-d')." end of Feb<br>");
echo(
'f ' . MonthShifter($x,1)->format(('Y-m-d'))."<br>");
echo(
'g ' . MonthShifter($x,-1)->format(('Y-m-d'))."<br>");

$x = new DateTime('2017-01-31');
echo(
"<br>" $x->format('Y-m-d')." end of Jan<br>");
echo(
'h ' . MonthShifter($x,1)->format(('Y-m-d'))."<br>");
echo(
'i ' . MonthShifter($x,-1)->format(('Y-m-d'))."<br>");

$x = new DateTime('2017-01-31');
echo(
"<br>" $x->format('Y-m-d')." end of Jan +/- 1 years diff, leap year respected<br>");
echo(
'j ' . MonthShifter($x,13)->format(('Y-m-d'))."<br>");
echo(
'k ' . MonthShifter($x,-11)->format(('Y-m-d'))."<br>");

//returns

2017-01-30 past end of feb, but not dec
b 2017
-02-28
c 2016
-12-30

2017
-01-15 middle of the month
d 2017
-02-15
e 2016
-12-15

2017
-02-28end of Feb
f 2017
-03-31
g 2017
-01-31

2017
-01-31end of Jan
h 2017
-02-28
i 2016
-12-31

2017
-01-31end of Jan +/- 1 years diff, leap year respected
j 2018
-02-28
k 2016
-02-29
sinus at sinpi dot net 29-Nov-2016 10:54
A very simple way to ensure we do not cross over month boundaries when adding months is to just go back a few days if the day number got reset:

<?php
function addMonths($date,$months) {
 
$orig_day = $date->format("d");
 
$date->modify("+".$months." months");
  while (
$date->format("d")<$orig_day && $date->format("d")<5) {
   
$date->modify("-1 day");
  }
}

for (
$i=0;$i<5;$i++) {
 
$d = new DateTime("2000-01-10");
 
addmonths($d,$i);
  echo
$d->format("Y-m-d")."<br>";
}
for (
$i=0;$i<5;$i++) {
 
$d = new DateTime("2000-01-31");
 
addmonths($d,$i);
  echo
$d->format("Y-m-d")."<br>";
}
?>

prints:
2000-01-10
2000-02-10
2000-03-10
2000-04-10
2000-05-10
2000-01-31
2000-02-29
2000-03-31
2000-04-30
2000-05-31
www dot wesley at gmail dot com 04-Jul-2015 08:31
This is an improvement of @jenspj's answer

<?php

$d
= new DateTime('2007-12-31');

function
addMonths($date, $months)
{
   
$years = floor(abs($months / 12));
   
$leap = 29 <= $date->format('d');
   
$m = 12 * (0 <= $months?1:-1);
    for (
$a = 1;$a < $years;++$a) {
       
$date = addMonths($date, $m);
    }
   
$months -= ($a - 1) * $m;
   
   
$init = clone $date;
    if (
0 != $months) {
       
$modifier = $months . ' months';
       
       
$date->modify($modifier);
        if (
$date->format('m') % 12 != (12 + $months + $init->format('m')) % 12) {
           
$day = $date->format('d');
           
$init->modify("-{$day} days");
        }
       
$init->modify($modifier);
    }
   
   
$y = $init->format('Y');
    if (
$leap && ($y % 4) == 0 && ($y % 100) != 0 && 28 == $init->format('d')) {
       
$init->modify('+1 day');
    }
    return
$init;
}

function
addYears($date, $years)
{
    return
addMonths($date, 12 * $years);
}

echo
$d->format('F j,Y') . ' N<br />';
$d = addMonths($d, +1);
echo
$d->format('F j,Y') . ' +1M<br />';
$d = addMonths($d, +1);
echo
$d->format('F j,Y') . ' +1M<br />';
$d = addYears($d, +60);
echo
$d->format('F j,Y') . ' +60Y<br />';
$d = addYears($d, -59);
echo
$d->format('F j,Y') . ' -59Y<br />';
66Ton99 13-May-2014 03:31
Extension for DateTime class which solves problem of adding or subtracting months

https://gist.github.com/66Ton99/60571ee49bf1906aaa1c
admin at wmfoi dot com dot br 08-Apr-2014 08:22
The changelog says: "5.3.0 - Changed the return value on success from NULL to DateTime".

That means that you can't do a Fluid Interface design with it in PHP 5.2.

In other words, this will not work in 5.2:

<?php
$DateTime
=new DateTime();
echo
$DateTime->modify('+1 day')->format('d');
?>
jay dot removethis at grooveshark dot com 29-Oct-2013 05:46
Due to DST and the way DateTime internally handles dates, it's possible to get stuck in a time loop.

For example:

<?php
$dt
= new DateTime('2012-03-11 3:00AM');
echo
$dt->format('YmdH') . "\n";
$dt->modify("-1 hour");
echo
$dt->format('YmdH') . "\n";
$dt->modify("-1 hour");
echo
$dt->format('YmdH') . "\n";
?>

prints out:

2012031103
2012031103
2012031103

if your timezone is set to America/New_York.
php at lanar dot com dot au 26-Oct-2013 06:50
modify() ignores any timezone information in the data while the DateTime constructor does not.

$dt = new DateTime( '2013-10-26T11:00:00+11:00' )
will create a +11 timezone while
$dt->modify( '2013-10-26T11:00:00+02:00' )
does not change the timezone or the time.

<?php
$dt
= new DateTime( '2013-10-26T15:00:00Australia/Melbourne' ) ;
echo
"\n", $dt->format( "c" ) ;
echo
"\nTimezone '", $dt->getTimezone()->getName() . "'." ;
// modify $dt to 1 am new york which is 3 pm melbourne
$dt->modify( '2013-10-26T01:00:00America/New_York' ) ;
// result is 1 am melbourne time, not 3 pm
echo "\n", $dt->format( "c" ) ;
echo
"\nTimezone '", $dt->getTimezone()->getName() . "'." ;
?>
Output
2013-10-26T15:00:00+11:00
Timezone 'Australia/Melbourne'.
2013-10-26T01:00:00+11:00
Timezone 'Australia/Melbourne'.
Jenny jsimonds@atomic jet packs dot com 15-Apr-2012 09:04
Note: This method modifies the object in-place. So if you want to calculate a new date but assign the new value to a different object, this will NOT work:

<?php
$numMinutes
= 25;
$oDateA = new DateTime('2012-01-01 12:00:00');

print
"
Original:<br>
oDateA = 
{$oDateA->format('Y-m-d H-i-s')}<br>
"
;

$oDateB = $oDateA->modify ("+{$numMinutes} minutes");

print
"
plus
{$numMinutes} minutes:<br>
oDateA = 
{$oDateA->format('Y-m-d H-i-s')}<br>
oDateB = 
{$oDateB->format('Y-m-d H-i-s')}<br>
"
;
?>
...produces this:
oDateA = 2012-01-01 12-00-00
plus 25 minutes:
oDateA = 2012-01-01 12-25-00
oDateB = 2012-01-01 12-25-00

Use something like this instead:
<?php
$numMinutes
= 25;
$oDateA = new DateTime('2012-01-01 12:00:00');

print
"
<p>
Original:<br>
oDateA = 
{$oDateA->format('Y-m-d H-i-s')}<br>
"
;

$oDateB = clone $oDateA;
$oDateB->modify ("+{$numMinutes} minutes");

print
"
plus
{$numMinutes} minutes:<br>
oDateA = 
{$oDateA->format('Y-m-d H-i-s')}<br>
oDateB = 
{$oDateB->format('Y-m-d H-i-s')}<br>
"
;
?>

... produces this:
oDateA = 2012-01-01 12-00-00
plus 25 minutes:
oDateA = 2012-01-01 12-00-00
oDateB = 2012-01-01 12-25-00
jenspj at msn dot com 20-Feb-2012 02:34
These functions makes sure that adding months or years always ends up in the month you would expect.  Works for positive and negative values

<?php
     
      
    $date
=new DateTime();
   
$date->setDate(2008,2,29);
   
    function
addMonths($date,$months){
        
       
$init=clone $date;
       
$modifier=$months.' months';
       
$back_modifier =-$months.' months';
       
       
$date->modify($modifier);
       
$back_to_init= clone $date;
       
$back_to_init->modify($back_modifier);
       
        while(
$init->format('m')!=$back_to_init->format('m')){
       
$date->modify('-1 day')    ;
       
$back_to_init= clone $date;
       
$back_to_init->modify($back_modifier);   
        }
       
       
/*
        if($months<0&&$date->format('m')>$init->format('m'))
        while($date->format('m')-12-$init->format('m')!=$months%12)
        $date->modify('-1 day');
        else
        if($months>0&&$date->format('m')<$init->format('m'))
        while($date->format('m')+12-$init->format('m')!=$months%12)
        $date->modify('-1 day');
        else
        while($date->format('m')-$init->format('m')!=$months%12)
        $date->modify('-1 day');
        */
       
   
}
    
    function
addYears($date,$years){
       
       
$init=clone $date;
       
$modifier=$years.' years';
       
$date->modify($modifier);
       
        while(
$date->format('m')!=$init->format('m'))
       
$date->modify('-1 day');
       
       
    }
   
   
   
   
addMonths($date,-1);
    
addYears($date,3);
   
   
    echo
$date->format('F j,Y');
    
 
?>