DateTime::modify
date_modify
(PHP 5 >= 5.2.0)
DateTime::modify -- date_modify — Изменение временной метки
Описание
Объектно-ориентированный стиль
Процедурный стиль
Изменяет метку времени объекта DateTime путем добавления или вычитания времени в формате, принятом для функции strtotime().
Список параметров
-
object
-
Только для процедурного стиля: Объект DateTime, возвращаемый date_create(). Функция изменяет этот объект.
-
modify
-
Строка даты/времени. Объяснение корректных форматов дано в Форматы даты и времени.
Возвращаемые значения
Возвращает объект DateTime для применения в цепи методов или FALSE
в случае возникновения ошибки.
Список изменений
Версия | Описание |
---|---|
5.3.6 | Стало возможным применять абсолютные значения объектов даты/времени. Раньше использовались только относительные значения полей даты/времени. |
5.3.0 | Изменено
значение успешной работы функции с NULL на DateTime. |
Примеры
Пример #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
Пример #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
Смотрите также
- strtotime() - Преобразует текстовое представление даты на английском языке в метку времени Unix
- DateTime::add() - Добавляет заданное количество дней, месяцев, лет, часов, минут и секунд к объекту DateTime
- DateTime::sub() - Вычитает заданное количество дней, месяцев, лет, часов, минут и секунд из времени объекта DateTime
- DateTime::setDate() - Установка даты
- DateTime::setISODate() - Установка ISO даты
- DateTime::setTime() - Установка времени
- DateTime::setTimestamp() - Устанавливает дату и время, основываясь на метке времени Unix
- PHP Руководство
- Функции по категориям
- Индекс функций
- Справочник функций
- Расширения для работы с датой и временем
- Дата и Время
- DateTime::add
- Функция DateTime::__construct() - Конструктор класса DateTime
- Функция DateTime::createFromFormat() - Создает и возвращает экземпляр класса DateTime, соответствующий заданному формату
- Функция DateTime::getLastErrors() - Возвращает предупреждения и ошибки
- Функция DateTime::modify() - Изменение временной метки
- Функция DateTime::__set_state() - Обработчик __set_state
- Функция DateTime::setDate() - Установка даты
- Функция DateTime::setISODate() - Установка ISO даты
- Функция DateTime::setTime() - Установка времени
- Функция DateTime::setTimestamp() - Устанавливает дату и время, основываясь на метке времени Unix
- Функция DateTime::setTimezone() - Установка временной зоны для объекта класса DateTime
- DateTime::sub
Коментарии
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');
?>
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
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'.
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.
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');
?>
Extension for DateTime class which solves problem of adding or subtracting months
https://gist.github.com/66Ton99/60571ee49bf1906aaa1c
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 />';
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
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 datetime.add#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
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
@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.
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
)
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
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.
Keep in mind that method chaining is only available from PHP 5.3, so with older versions, this will not work:
<?php
$exp_date = new DateTime();
echo $exp_date->modify("-10 month")->format("Y-m-d");
?>
This will work:
<?php
$exp_date = new DateTime();
$exp_date->modify("-10 month");
echo $exp_date->format("Y-m-d");
?>