IRT note below: from PHP 7.0+, split seconds get returned, too, and will be under the `f` property.
(PHP 5 >= 5.3.0, PHP 7, PHP 8)
表示时间间隔。
时间间隔表示固定量的时间(多少年,月,天,小时等),也可以表示一个字符串格式的相对时间,当表示相对时间的时候,字符串格式是 DateTimeImmutable 和 DateTime 类的构造函数所支持的格式。
更具体的说,DateInterval 类的对象中的信息是从一个日期/时间到另一个日期/时间的指令。这个过程并不始终可逆。
创建 DateInterval 对象的常用方法是通过 DateTimeInterface::diff() 计算两个日期/时间对象之间的差异。
由于没有明确定义的方法来比较日期间隔,因此 DateInterval 实例是无法比较的。
下列列出的有效属性依赖 PHP 版本,应视为 readonly。
多少年。
多少月。
多少天。
多少小时。
多少分钟。
多少秒。
多少微秒。
如果是负的时间段,则为 1
,否则为
0
。请参见 DateInterval::format()。
如果 DateInterval 对象是由 DateTimeImmutable::diff()
或者 DateTime::diff()
函数创建的,那么它表示开始日期和结束日期之间的完整天数。否则,days 属性为 false
。
如果 DateInterval 对象是通过 DateInterval::createFromDateString()
创建,则此属性值设为 true
并且 date_string 属性将填充数据。否则,此值设为
false
且为从 y 到 f、invert 和
days 属性填充数据。
字符串,用作 DateInterval::createFromDateString() 的参数。
版本 | 说明 |
---|---|
8.2.0 | 为使用 DateInterval::createFromDateString() 方法创建的 DateInterval 实例添加了 from_string 和 date_string 属性。 |
8.2.0 |
仅从 y 到 f 、invert
和 days 将可见。
|
7.4.0 | 现在 DateInterval 无法比较;之前所有的 DateInterval 实例都认为相等。 |
7.1.0 | 增加 f 属性。 |
IRT note below: from PHP 7.0+, split seconds get returned, too, and will be under the `f` property.
Just tried the code submitted earlier on php 7.2.16:
<?php
$d1=new DateTime("2012-07-08 11:14:15.638276");
$d2=new DateTime("2012-07-08 11:14:15.889342");
$diff=$d2->diff($d1);
print_r( $diff ) ;
?>
and it now DOES return microsecond differences, in the key 'f':
<?php
DateInterval Object
(
[y] => 0
[m] => 0
[d] => 0
[h] => 0
[i] => 0
[s] => 0
[f] => 0.251066
[weekday] => 0
[weekday_behavior] => 0
[first_last_day_of] => 0
[invert] => 1
[days] => 0
[special_type] => 0
[special_amount] => 0
[have_weekday_relative] => 0
[have_special_relative] => 0
)
?>
DateInterval does not support split seconds (microseconds or milliseconds etc.) when doing a diff between two DateTime objects that contain microseconds.
So you cannot do the following, for example:
<?php
$d1=new DateTime("2012-07-08 11:14:15.638276");
$d2=new DateTime("2012-07-08 11:14:15.889342");
$diff=$d2->diff($d1);
print_r( $diff ) ;
/* returns:
DateInterval Object
(
[y] => 0
[m] => 0
[d] => 0
[h] => 0
[i] => 0
[s] => 0
[invert] => 0
[days] => 0
)
*/
?>
You get back 0 when you actually want to get 0.251066 seconds.
If you want to convert a Timespan given in Seconds into an DateInterval Object you could dot the following:
<?php
$dv = new DateInterval('PT'.$timespan.'S');
?>
but wenn you look at the object, only the $dv->s property is set.
As stated in the documentation to DateInterval::format
The DateInterval::format() method does not recalculate carry over points in time strings nor in date segments. This is expected because it is not possible to overflow values like "32 days" which could be interpreted as anything from "1 month and 4 days" to "1 month and 1 day".
If you still want to calculate the seconds into hours / days / years, etc do the following:
<?php
$d1 = new DateTime();
$d2 = new DateTime();
$d2->add(new DateInterval('PT'.$timespan.'S'));
$iv = $d2->diff($d1);
?>
$iv is an DateInterval set with days, years, hours, seconds, etc ...
Many people have commented on doing a reverse interval on a date time. I personally find a backwards year to be a little strange to think about and instead opt to work with just intervals. This is the easiest I have found.
<?php
$one_year = new DateInterval('P1Y');
$one_year_ago = new DateTime();
$one_year_ago->sub($one_year);
?>
Instead of:
<?php
$one_year_ago = new DateInterval( "P1Y" );
$one_year_ago->invert = 1;
$one_year_ago = new DateTime();
$one_year_ago->add($one_year);
?>
Please note that the hour field can be negative, especially in the context of switching to/from Daylight savings.
Example:
<?php
$tz = new DateTimeZone("America/New_York");
$date_1 = new DateTime("2019-10-30 15:00:00",$tz);
$date_2 = new DateTime("2019-11-21 14:30:20",$tz);
echo $date_2->diff($date_1)->format("%a:%h:%i:%s");
// returns 21:-1:30:20
invert flag is unreliable.
If you've created interval with \DateInterval::createFromDateString with value like '1 day ago' than actually days counter will be negative, and invert flag will be 0. Also, setting invert to 1 with negative units is not working.
Reliable solution to check if interval is negative is to actually apply it and compare:
<?php
private function isNegative(\DateInterval $interval)
{
$now = new \DateTimeImmutable();
$newTime = $now->add($interval);
return $newTime < $now;
}
?>
Also, if you want to compare some units of two intervals you should take abs() of them. Or make whole interval absolute:
<?php
private function absInterval(\DateInterval $interval)
{
$now = new \DateTimeImmutable();
$new = $now->add($interval);
$newInt = $now->diff($new);
if (1 === $newInt->invert) {
$newInt->invert = 0;
}
return $newInt;
}
?>
P.S.: tested on 5.5.12-dev and 5.5.9
This DateInterval extension allows you to write a formatted timestamp but omit the "zero values" and handle things like listing, plurals, etc.
Example input: '%y year(s)', '%m month(s)', '%d day(s)', '%h hour(s)', '%i minute(s)', '%s second(s)'
Example output: 1 year, 2 months, 16 days, 1 minute, and 15 seconds
Example input: '%y año(s)', '%m mes(es)', '%d día(s)', '%h hora(s)', '%i minuto(s)', '%s segundo(s)'
Example output: 1 año, 2 meses, 16 días, 1 minuto, y 15 segundos
<meta charset="UTF-8">
<?php
error_reporting(E_ALL);
class MyDateInterval extends DateInterval {
public
$pluralCheck = '()',
// Must be exactly 2 characters long
// The first character is the opening brace, the second the closing brace
// Text between these braces will be used if > 1, or replaced with $this->singularReplacement if = 1
$singularReplacement = '',
// Replaces $this->pluralCheck if = 1
// hour(s) -> hour
$separator = ', ',
// Delimiter between units
// 3 hours, 2 minutes
$finalSeparator = ', and ',
// Delimeter between next-to-last unit and last unit
// 3 hours, 2 minutes, and 1 second
$finalSeparator2 = ' and ';
// Delimeter between units if there are only 2 units
// 3 hours and 2 minutes
public static function createFromDateInterval (DateInterval $interval) {
$obj = new self('PT0S');
foreach ($interval as $property => $value) {
$obj->$property = $value;
}
return $obj;
}
public function formatWithoutZeroes () {
// Each argument may have only one % parameter
// Result does not handle %R or %r -- but you can retrieve that information using $this->format('%R') and using your own logic
$parts = array ();
foreach (func_get_args() as $arg) {
$pre = mb_substr($arg, 0, mb_strpos($arg, '%'));
$param = mb_substr($arg, mb_strpos($arg, '%'), 2);
$post = mb_substr($arg, mb_strpos($arg, $param)+mb_strlen($param));
$num = intval(parent::format($param));
$open = preg_quote($this->pluralCheck[0], '/');
$close = preg_quote($this->pluralCheck[1], '/');
$pattern = "/$open(.*)$close/";
list ($pre, $post) = preg_replace($pattern, $num == 1 ? $this->singularReplacement : '$1', array ($pre, $post));
if ($num != 0) {
$parts[] = $pre.$num.$post;
}
}
$output = '';
$l = count($parts);
foreach ($parts as $i => $part) {
$output .= $part.($i < $l-2 ? $this->separator : ($l == 2 ? $this->finalSeparator2 : ($i == $l-2 ? $this->finalSeparator : '')));
}
return $output;
}
}
date_default_timezone_set('America/Phoenix');
$today = new DateTime('today');
echo 'Today is ', $today->format('F d, Y h:ia'), '.<br>', PHP_EOL;
// Today is August 28, 2013 12:00am.<br>
$expiration = new DateTime('today +1 year +2 months +16 days +1 minute +15 seconds');
echo 'Expires ', $expiration->format('F d, Y h:ia'), '.<br>', PHP_EOL;
// Expires November 13, 2014 12:01am.<br>
$interval = MyDateInterval::createFromDateInterval($today->diff($expiration));
echo 'That is ', $interval->formatWithoutZeroes('%y year(s)', '%m month(s)', '%d day(s)', '%h hour(s)', '%i minute(s)', '%s second(s)'), ' from now.<br>', PHP_EOL;
// That is 1 year, 2 months, 16 days, 1 minute, and 15 seconds from now.
$interval->finalSeparator = ', y ';
$interval->finalSeparator2 = ' y ';
echo 'Que es de ', $interval->formatWithoutZeroes('%y año(s)', '%m mes(es)', '%d día(s)', '%h hora(s)', '%i minuto(s)', '%s segundo(s)'), ' a partir de ahora.';
// Que es de 1 año, 2 meses, 16 días, 1 minuto, y 15 segundos a partir de ahora.
// Is that correct? Spanish isn't my strength....
?>
If you want to format a \DateInterval to something like you input (new \DateInterval("P3W2D")) you can use one of this:
<?php
class MyDateInterval extends \DateInterval
{
/**
* formating string like ISO 8601 (PnYnMnDTnHnMnS)
*/
const INTERVAL_ISO8601 = 'P%yY%mM%dDT%hH%iM%sS';
/**
* formating the interval like ISO 8601 (PnYnMnDTnHnMnS)
*
* @return string
*/
function __toString()
{
$sReturn = 'P';
if($this->y){
$sReturn .= $this->y . 'Y';
}
if($this->m){
$sReturn .= $this->m . 'M';
}
if($this->d){
$sReturn .= $this->d . 'D';
}
if($this->h || $this->i || $this->s){
$sReturn .= 'T';
if($this->h){
$sReturn .= $this->h . 'H';
}
if($this->i){
$sReturn .= $this->i . 'M';
}
if($this->s){
$sReturn .= $this->s . 'S';
}
}
return $sReturn;
}
}
?>
example use:
<?php
$oDateIntervalValue = new MyDateInterval('P3M');
$sFormatResult = $oDateIntervalValue->format(MyDateInterval::INTERVAL_ICALENDAR); // "P0Y3M0DT0H0M0S"
$sToStringResult = (string) $oDateIntervalValue; // "P3M"
var_dump(new MyDateInterval($sFormatResult)); // object like $oDateIntervalValue
var_dump(new MyDateInterval($sToStringResult)); // object like $oDateIntervalValue
?>
Note that you CAN NOT get the exact time in seconds from a \DateInterval object.
The reason is simple. While adding the properties up, you can not determine the days of the month.
<?php
function getTTL(\DateInterval $ttl): int
{
$secs = (isset($ttl->f) ? ($ttl->f): 0)
+ ($ttl->s)
+ ($ttl->i * 60)
+ ($ttl->h * 60 * 60)
+ ($ttl->d * 60 * 60 * 24)
+ ($ttl->m * 60 * 60 * 24 * 30) //Feb. is 28 days, 29 in an intercalary year
+ ($ttl->y * 60 * 60 * 24 * 365);
return intval(round($secs));
}
?>
So, keep in mind that \DateInterval is something to add-on to time.
<?php
function getTTL(\DateInterval $ttl): int
{
// Find seconds from it's current state of time
$secs = \DateTime::createFromFormat('U', '0')->add($ttl)->format('U');
return intval(abs($secs));
}
?>
Be careful!
2020-01-04 10:00:00
2020-01-05 10:00:00
->s will return 0, because the second difference is 0.
When using DateInterval('P3M') on 30th of November you get March instead of Ferbuary.
You can create a series of dates starting with the first day of the week for each week, if you wish to populate list box on your web page with this date math. Use the absolute abs( ) function to convert negative numbers generated from dates in the past.
<?php
$TwoWeeksAgo = new DateTime(date("Ymd"));
$TwoWeeksAgo->sub(new DateInterval('P'.abs ( (7-date("N")-14)).'D'));
$LastWeek = new DateTime(date("Ymd"));
$LastWeek->sub(new DateInterval('P'.abs ( (7-date("N")-7)).'D'));
$ThisWeek = new DateTime(date("Ymd"));
$ThisWeek->add(new DateInterval('P'.abs ( (7-date("N"))).'D'));
echo 'Start of This week is '.$ThisWeek->format('l m/d/Y').'<br/>';
echo 'Start of Last week is '.$LastWeek->format('l m/d/Y').'<br/>';
echo 'Start of 2 weeks ago is '.$TwosWeekAgo->format('l m/d/Y').'<br/>';
?>
It appears that they "days" property that is populated by \DateTime::diff does not contain a float for the differences in time.
It is rounded down to the nearest whole day.
$d1 = new \DateTime("2013-07-31 10:29:00");
$d2 = new \DateTime("2013-08-02 5:32:12");
echo $d1->diff($d2)->days;
Output: 1
I had the doubt after reading this page on how to create negative intervals. So far the only solution is to create the interval and negativize it.
<?php
$date1 = new DateTime();
$eightynine_days_ago = new DateInterval( "P89D" );
$eightynine_days_ago->invert = 1; //Make it negative.
$date1->add( $eightynine_days_ago );
?>
and then $date1 is now 89 days in the past.
This information is extracted from another php comment http://www.php.net/manual/en/dateinterval.construct.php#102976 but this page seems to be the first place where people will look for it.
Note that to get the next day (like : +1 day, +1 week, ...)
<?php $date = date_create('last monday of december last year')->add(\DateInterval::createFromDateString('+1 monday')); ?>
will not return the next monday, but the same monday passed as date_create() parameter.
To get the next monday even pass +2 instead of +1 or next monday :
<?php $date = date_create('last monday of december last year')->add(\DateInterval::createFromDateString('next monday')); ?>
//create a date
$now = new \DateTime();
//create a specific date
$someDate = \DateTime::createFromFormat("Y-m-d H:i", "2019-12-19 15:27")
//Add one day
$someDate->add(new DateInterval("P1D"));
//convert to string
echo $someDate->format("Y-m-d"); //2019-12-20
It should be noted that the following code will not throw an exception or return false, or anything:
<?php
$interval = new \DateInterval::createFromDateString("this is not a date interval");
?>
Your best way to check if what you created is a "valid" interval, by doing something like the following:
<?php
$interval = new \DateInterval::createFromDateString("this is not a date interval");
if (0 == $interval->format('s')) {
throw new \LogicException("Wrong interval");
}
?>