[doc-en] master: Document that DateInterval addition depends on how the interval was created (#5830)
| From: | Louis-Arnaud via GitHub | Date: | Thu, 03 Sep 2026 14:15:06 +0000 |
| Subject: | [doc-en] master: Document that DateInterval addition depends on how the interval was created (#5830) | ||
| Groups: | php.doc.cvs | ||
| Request: | Send a blank email to doc-cvs+get-23713@lists.php.net to get a copy of this message | ||
Author: Louis-Arnaud (lacatoire)
Committer: GitHub (web-flow)
Pusher: lacatoire
Date: 2026-09-03T16:15:01+02:00
Commit: https://github.com/php/doc-en/commit/18c0977d5ec59f4207cb0b306c5473508bf640ed
Raw diff: https://github.com/php/doc-en/commit/18c0977d5ec59f4207cb0b306c5473508bf640ed.diff
Document that DateInterval addition depends on how the interval was created (#5830)
* Unwrap examples and lists from para in the DateTime reference
An example, a variablelist or a simplelist that is alone in a para
should be a direct child of its container.
* Document that DateInterval addition depends on how the interval was made
DateTimeImmutable::add and DateTime::add apply an interval in one of two
ways, picked from how the interval was created rather than from what it
holds:
new DateInterval('PT24H') -> elapsed time
DateInterval::createFromDateString('24 hours') -> component values
DateTimeInterface::diff() -> component values
The two agree on any ordinary day and differ by an hour across a DST
transition, which is not documented anywhere. The example on the
DateInterval::createFromDateString page stated the opposite outright
("Each set of intervals is equal.").
Add a third example to the Date/Time Arithmetic page contrasting the two,
state the rule on the DateInterval class page, note it where the equality
was claimed, and link the arithmetic page from
DateTimeImmutable::add/sub.
Output verified on PHP 8.5.4.
* Scope the elapsed-time wording to the time part of the interval
Only the time part of an interval (hours, minutes, seconds,
microseconds) is applied as elapsed time when the interval comes from
DateInterval::__construct. The date part goes through the same calendar
arithmetic in both cases, so P1D and '1 day' never diverge:
2015-11-01 00:00:00 -04:00 America/New_York
+ P1D -> 2015-11-02 00:00:00 -05:00
+ 1 day -> 2015-11-02 00:00:00 -05:00
Over 400 days of hourly start times, PT24H and '24 hours' diverge 46
times while P1D and '1 day' never do. Of the seven sets listed in the
first createFromDateString example, only the two carrying a time part
can diverge at all.
* Drop weeks from the list of date components
DateInterval has no week component: new DateInterval('P1W') yields d=7,
and property_exists($i, 'w') is false.
Changed paths:
M reference/datetime/dateinterval.xml
M reference/datetime/dateinterval/createfromdatestring.xml
M reference/datetime/datetimeimmutable/add.xml
M reference/datetime/datetimeimmutable/sub.xml
M reference/datetime/examples.xml
Diff:
diff --git a/reference/datetime/dateinterval.xml b/reference/datetime/dateinterval.xml
index 1b6fdeed4798..e42ff185aef9 100644
--- a/reference/datetime/dateinterval.xml
+++ b/reference/datetime/dateinterval.xml
@@ -28,6 +28,20 @@
is by calculating the difference between two date/time objects through
<methodname>DateTimeInterface::diff</methodname>.
</para>
+ <simpara>
+ How the time part of an interval, that is hours, minutes, seconds and
+ microseconds, is applied when the interval is added to, or subtracted
+ from, a date/time object depends on how the interval was created. For an
+ interval created with <methodname>DateInterval::__construct</methodname>
+ the time part covers elapsed time, whereas for an interval created with
+ <methodname>DateInterval::createFromDateString</methodname>, or returned
+ by <methodname>DateTimeInterface::diff</methodname>, it increments or
+ decrements the individual component values. The date part, that is years,
+ months and days, always increments or decrements the individual component
+ values. The two therefore only give a different result when a
+ timezone transition falls within the time part of the interval; see
+ <link linkend="datetime.examples-arithmetic">Date/Time Arithmetic</link>.
+ </simpara>
<para>
Since there is no well defined way to compare date intervals,
<classname>DateInterval</classname> instances are
diff --git a/reference/datetime/dateinterval/createfromdatestring.xml
b/reference/datetime/dateinterval/createfromdatestring.xml
index 886cac0d345b..7598a71d0395 100644
--- a/reference/datetime/dateinterval/createfromdatestring.xml
+++ b/reference/datetime/dateinterval/createfromdatestring.xml
@@ -28,26 +28,24 @@
<refsect1 role="parameters">
&reftitle.parameters;
- <para>
- <variablelist>
- <varlistentry>
- <term><parameter>datetime</parameter></term>
- <listitem>
- <para>
- A date with relative parts. Specifically, the
- <link linkend="datetime.formats.relative">relative formats</link>
supported
- by the parser used for <classname>DateTimeImmutable</classname>,
- <classname>DateTime</classname>, and <function>strtotime</function>
- will be used to construct the DateInterval.
- </para>
- <para>
- To use an ISO-8601 format string like <literal>P7D</literal>, you must
- use the <methodname>DateInterval::__construct</methodname>.
- </para>
- </listitem>
- </varlistentry>
- </variablelist>
- </para>
+ <variablelist>
+ <varlistentry>
+ <term><parameter>datetime</parameter></term>
+ <listitem>
+ <simpara>
+ A date with relative parts. Specifically, the
+ <link linkend="datetime.formats.relative">relative formats</link>
supported
+ by the parser used for <classname>DateTimeImmutable</classname>,
+ <classname>DateTime</classname>, and <function>strtotime</function>
+ will be used to construct the DateInterval.
+ </simpara>
+ <simpara>
+ To use an ISO-8601 format string like <literal>P7D</literal>, you must
+ use the <methodname>DateInterval::__construct</methodname>.
+ </simpara>
+ </listitem>
+ </varlistentry>
+ </variablelist>
</refsect1>
<refsect1 role="returnvalues">
@@ -110,13 +108,12 @@
<refsect1 role="examples">
&reftitle.examples;
- <para>
- <example>
- <title>Parsing valid date intervals</title>
- <programlisting role="php" annotations="non-interactive">
+ <example>
+ <title>Parsing valid date intervals</title>
+ <programlisting role="php" annotations="non-interactive">
<![CDATA[
<?php
-// Each set of intervals is equal.
+// Each set of intervals describes the same duration.
$i = new DateInterval('P1D');
$i = DateInterval::createFromDateString('1 day');
@@ -138,13 +135,23 @@ $i = DateInterval::createFromDateString('1 day + 12 hours');
$i = new DateInterval('PT3600S');
$i = DateInterval::createFromDateString('3600 seconds');
]]>
- </programlisting>
- </example>
- </para>
- <para>
- <example>
- <title>Parsing combinations and negative intervals</title>
- <programlisting role="php">
+ </programlisting>
+ </example>
+ <note>
+ <simpara>
+ The intervals in a set that have a time part, such as
+ <literal>P1DT12H</literal> and <literal>1 day + 12 hours</literal>, are
+ not interchangeable when they are added to a date/time object and a
+ timezone transition falls within that time part: for an interval created
+ with <methodname>DateInterval::__construct</methodname> the time part
+ covers elapsed time, whereas for an interval created with this method it
+ increments or decrements the individual component values. See
+ <link linkend="datetime.examples-arithmetic">Date/Time Arithmetic</link>.
+ </simpara>
+ </note>
+ <example>
+ <title>Parsing combinations and negative intervals</title>
+ <programlisting role="php">
<![CDATA[
<?php
$i = DateInterval::createFromDateString('62 weeks + 1 day + 2 weeks + 2 hours + 70
minutes');
@@ -153,20 +160,18 @@ echo $i->format('%d %h %i'), "\n";
$i = DateInterval::createFromDateString('1 year - 10 days');
echo $i->format('%y %d'), "\n";
]]>
- </programlisting>
- &example.outputs;
- <screen>
+ </programlisting>
+ &example.outputs;
+ <screen>
<![CDATA[
449 2 70
1 -10
]]>
- </screen>
- </example>
- </para>
- <para>
- <example>
- <title>Parsing special relative date intervals</title>
- <programlisting role="php">
+ </screen>
+ </example>
+ <example>
+ <title>Parsing special relative date intervals</title>
+ <programlisting role="php">
<![CDATA[
<?php
$i = DateInterval::createFromDateString('last day of next month');
@@ -175,9 +180,9 @@ var_dump($i);
$i = DateInterval::createFromDateString('last weekday');
var_dump($i);
]]>
- </programlisting>
- &example.outputs.82;
- <screen role="php">
+ </programlisting>
+ &example.outputs.82;
+ <screen role="php">
<![CDATA[
object(DateInterval)#1 (2) {
["from_string"]=>
@@ -192,9 +197,9 @@ object(DateInterval)#2 (2) {
string(12) "last weekday"
}
]]>
- </screen>
- &example.outputs.8.similar;
- <screen role="php">
+ </screen>
+ &example.outputs.8.similar;
+ <screen role="php">
<![CDATA[
object(DateInterval)#1 (16) {
["y"]=>
@@ -265,9 +270,8 @@ object(DateInterval)#2 (16) {
int(1)
}
]]>
- </screen>
- </example>
- </para>
+ </screen>
+ </example>
</refsect1>
</refentry>
diff --git a/reference/datetime/datetimeimmutable/add.xml
b/reference/datetime/datetimeimmutable/add.xml
index 5d2595cb0a74..7715645771b4 100644
--- a/reference/datetime/datetimeimmutable/add.xml
+++ b/reference/datetime/datetimeimmutable/add.xml
@@ -117,6 +117,7 @@ echo $newDate2->format('Y-m-d') . "\n";
<member><function>DateTimeImmutable::sub</function></member>
<member><function>DateTimeImmutable::diff</function></member>
<member><function>DateTimeImmutable::modify</function></member>
+ <member><link linkend="datetime.examples-arithmetic">Date/Time
Arithmetic</link></member>
</simplelist>
</refsect1>
</refentry>
diff --git a/reference/datetime/datetimeimmutable/sub.xml
b/reference/datetime/datetimeimmutable/sub.xml
index 8b4c2f66a6f4..6ed595a4fed7 100644
--- a/reference/datetime/datetimeimmutable/sub.xml
+++ b/reference/datetime/datetimeimmutable/sub.xml
@@ -155,6 +155,7 @@ echo $newDate2->format('Y-m-d') . "\n";
<member><function>DateTimeImmutable::add</function></member>
<member><function>DateTimeImmutable::diff</function></member>
<member><function>DateTimeImmutable::modify</function></member>
+ <member><link linkend="datetime.examples-arithmetic">Date/Time
Arithmetic</link></member>
</simplelist>
</refsect1>
diff --git a/reference/datetime/examples.xml b/reference/datetime/examples.xml
index c58489eabed7..e8139edd67c5 100644
--- a/reference/datetime/examples.xml
+++ b/reference/datetime/examples.xml
@@ -10,14 +10,13 @@
The following examples show some pitfalls of Date/Time arithmetic with regard
to DST transitions and months having different numbers of days.
</para>
- <para>
- <example>
- <title>DateTimeImmutable::add/sub add intervals which cover elapsed time</title>
- <simpara>
- Adding PT24H over a DST transition will appear to add 23/25 hours (for
- most timezones).
- </simpara>
- <programlisting role="php">
+ <example>
+ <title>DateTimeImmutable::add/sub add intervals which cover elapsed time</title>
+ <simpara>
+ Adding PT24H over a DST transition will appear to add 23/25 hours (for
+ most timezones).
+ </simpara>
+ <programlisting role="php">
<![CDATA[
<?php
$dt = new DateTimeImmutable("2015-11-01 00:00:00", new
DateTimeZone("America/New_York"));
@@ -25,25 +24,23 @@ echo "Start: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;
$dt = $dt->add(new DateInterval("PT3H"));
echo "End: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;
]]>
- </programlisting>
- &example.outputs;
- <screen>
+ </programlisting>
+ &example.outputs;
+ <screen>
<![CDATA[
Start: 2015-11-01 00:00:00 -04:00
End: 2015-11-01 02:00:00 -05:00
]]>
- </screen>
- </example>
- </para>
- <para>
- <example>
- <title>DateTimeImmutable::modify and strtotime increment or decrement individual
component values</title>
- <simpara>
- Adding +24 hours over a DST transition will add exactly 24 hours as seen in
- the date/time string (unless the start or end time is on a transition
- point).
- </simpara>
- <programlisting role="php">
+ </screen>
+ </example>
+ <example>
+ <title>DateTimeImmutable::modify and strtotime increment or decrement individual component
values</title>
+ <simpara>
+ Adding +24 hours over a DST transition will add exactly 24 hours as seen in
+ the date/time string (unless the start or end time is on a transition
+ point).
+ </simpara>
+ <programlisting role="php">
<![CDATA[
<?php
$dt = new DateTimeImmutable("2015-11-01 00:00:00", new
DateTimeZone("America/New_York"));
@@ -51,24 +48,52 @@ echo "Start: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;
$dt = $dt->modify("+24 hours");
echo "End: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;
]]>
- </programlisting>
- &example.outputs;
- <screen>
+ </programlisting>
+ &example.outputs;
+ <screen>
<![CDATA[
Start: 2015-11-01 00:00:00 -04:00
End: 2015-11-02 00:00:00 -05:00
]]>
- </screen>
- </example>
- </para>
- <para>
- <example>
- <title>Adding or subtracting times can over- or underflow dates</title>
- <simpara>
- Like where January 31st + 1 month will result in March 2nd (leap year) or
- 3rd (normal year).
- </simpara>
- <programlisting role="php">
+ </screen>
+ </example>
+ <example>
+ <title>DateInterval::createFromDateString creates intervals which increment or decrement
component values</title>
+ <simpara>
+ <methodname>DateInterval::createFromDateString</methodname> uses the
+ date/time parser, and the time part of the resulting interval increments
+ or decrements the individual component values. For an interval created
+ with <methodname>DateInterval::__construct</methodname> the time part
+ covers elapsed time instead, so the two are not interchangeable when a
+ timezone transition falls within that time part. The date part is applied
+ the same way by both, so <literal>P1D</literal> and
+ <literal>1 day</literal> always give the same result.
+ </simpara>
+ <programlisting role="php">
+<![CDATA[
+<?php
+$dt = new DateTimeImmutable("2015-11-01 00:00:00", new
DateTimeZone("America/New_York"));
+echo "Start: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;
+echo "PT24H: ", $dt->add(new
DateInterval("PT24H"))->format("Y-m-d H:i:s P"), PHP_EOL;
+echo "24 hours: ", $dt->add(DateInterval::createFromDateString("24
hours"))->format("Y-m-d H:i:s P"), PHP_EOL;
+]]>
+ </programlisting>
+ &example.outputs;
+ <screen>
+<![CDATA[
+Start: 2015-11-01 00:00:00 -04:00
+PT24H: 2015-11-01 23:00:00 -05:00
+24 hours: 2015-11-02 00:00:00 -05:00
+]]>
+ </screen>
+ </example>
+ <example>
+ <title>Adding or subtracting times can over- or underflow dates</title>
+ <simpara>
+ Like where January 31st + 1 month will result in March 2nd (leap year) or
+ 3rd (normal year).
+ </simpara>
+ <programlisting role="php">
<![CDATA[
<?php
echo "Normal year:\n"; // February has 28 days
@@ -83,9 +108,9 @@ echo "Start: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;
$dt = $dt->modify("+1 month");
echo "End: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;
]]>
- </programlisting>
- &example.outputs;
- <screen>
+ </programlisting>
+ &example.outputs;
+ <screen>
<![CDATA[
Normal year:
Start: 2015-01-31 00:00:00 -05:00
@@ -94,12 +119,12 @@ Leap year:
Start: 2016-01-31 00:00:00 -05:00
End: 2016-03-02 00:00:00 -05:00
]]>
- </screen>
- <simpara>
- To get the last day of the next month (i.e. to prevent the overflow),
- the <literal>last day of</literal> format is available.
- </simpara>
- <programlisting role="php">
+ </screen>
+ <simpara>
+ To get the last day of the next month (i.e. to prevent the overflow),
+ the <literal>last day of</literal> format is available.
+ </simpara>
+ <programlisting role="php">
<![CDATA[
<?php
echo "Normal year:\n"; // February has 28 days
@@ -114,9 +139,9 @@ echo "Start: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;
$dt = $dt->modify("last day of next month");
echo "End: ", $dt->format("Y-m-d H:i:s P"), PHP_EOL;
]]>
- </programlisting>
- &example.outputs;
- <screen>
+ </programlisting>
+ &example.outputs;
+ <screen>
<![CDATA[
Normal year:
Start: 2015-01-31 00:00:00 -05:00
@@ -125,9 +150,8 @@ Leap year:
Start: 2016-01-31 00:00:00 -05:00
End: 2016-02-29 00:00:00 -05:00
]]>
- </screen>
- </example>
- </para>
+ </screen>
+ </example>
</section>
</chapter>