fix: emit full seconds in Instant and ZonedDateTime toISOString (RFC 3339)
Instant::toISOString() and ZonedDateTime::toISOString() emitted a compact time that dropped zero seconds (e.g. 2023-11-14T22:14Z), which is valid ISO 8601 but not valid RFC 3339. Section 5.6 of RFC 3339 requires the full HH:MM:SS time even when the seconds are zero. These types are meant for interoperability, so they must always emit RFC 3339 compliant output. Build the time part of ZonedDateTime::toISOString() explicitly with a mandatory seconds component instead of delegating to LocalTime's compact formatter. Instant delegates to ZonedDateTime, so it is fixed transitively; jsonSerialize() and __toString() route through toISOString(). LocalTime's documented compact format is intentional and left unchanged. This changes the serialized string representation and is therefore a BC break; fixtures that pinned the compact output are updated accordingly.
This commit is contained in:
1 parent
104867f051
commit
6bb686d4af
6 files changed
+163
-36
No files matched your search
@@ -0,0 +1,97 @@
|
|||||||
|
# fix: emit full seconds in Instant and ZonedDateTime toISOString (RFC 3339)
|
||||||
|
|
||||||
|
Fixes #110
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
`Instant::toISOString()` (and therefore `Instant::jsonSerialize()` / `(string)`) and
|
||||||
|
`ZonedDateTime::toISOString()` emit a compact time format that omits the seconds when they
|
||||||
|
are zero, e.g.:
|
||||||
|
|
||||||
|
```
|
||||||
|
2023-11-14T22:14Z // whole minute
|
||||||
|
1970-01-01T00:00Z // epoch
|
||||||
|
2023-11-14T23:00Z // whole hour
|
||||||
|
```
|
||||||
|
|
||||||
|
This is a valid ISO 8601 time, but it is **not** a valid RFC 3339 date-time. RFC 3339
|
||||||
|
section 5.6 defines `full-time` as `partial-time time-offset` where
|
||||||
|
`partial-time = time-hour ":" time-minute ":" time-second [time-secfrac]` — the seconds
|
||||||
|
component is mandatory, even when it is `00`. `Instant` and `ZonedDateTime` are the types
|
||||||
|
meant for interoperability (JSON serialization, wire formats), so they should always emit a
|
||||||
|
value other systems can parse as RFC 3339. Producing `2023-11-14T22:14Z` breaks strict
|
||||||
|
RFC 3339 parsers.
|
||||||
|
|
||||||
|
## Root cause
|
||||||
|
|
||||||
|
The compact behavior is intentional and documented in
|
||||||
|
`src/LocalTime.php:639` (`toISOString()` — "the shortest that outputs the full value of the
|
||||||
|
time"). That is correct for `LocalTime` and is left untouched.
|
||||||
|
|
||||||
|
The bug is that the interop types inherit that compact form:
|
||||||
|
|
||||||
|
- `src/ZonedDateTime.php:740` `toISOString()` built the string from
|
||||||
|
`$this->localDateTime`, whose `__toString()` delegates to `LocalTime::toISOString()`
|
||||||
|
(the compact formatter).
|
||||||
|
- `src/Instant.php:362` `toISOString()` returns `(string) ZonedDateTime::ofInstant(...)`,
|
||||||
|
so it inherited the same compact time. `jsonSerialize()` and `__toString()` both route
|
||||||
|
through `toISOString()`.
|
||||||
|
|
||||||
|
## The fix
|
||||||
|
|
||||||
|
`ZonedDateTime::toISOString()` now builds the time portion explicitly with a mandatory
|
||||||
|
`HH:MM:SS` component (keeping the existing optional fractional-seconds handling), instead of
|
||||||
|
delegating to `LocalTime`'s compact formatter. `Instant::toISOString()` is fixed
|
||||||
|
transitively because it delegates to `ZonedDateTime`; its PHPDoc now documents the RFC 3339
|
||||||
|
guarantee. `LocalTime` is deliberately not changed.
|
||||||
|
|
||||||
|
Output after the fix:
|
||||||
|
|
||||||
|
```
|
||||||
|
2023-11-14T22:14:00Z
|
||||||
|
1970-01-01T00:00:00Z
|
||||||
|
2023-11-14T23:00:00Z
|
||||||
|
2023-11-14T23:14:00+01:00[Europe/Paris]
|
||||||
|
```
|
||||||
|
|
||||||
|
Fractional seconds and non-zero seconds are unchanged (e.g.
|
||||||
|
`1970-01-01T00:00:00.000123456Z`, `1969-12-31T23:59:59Z`).
|
||||||
|
|
||||||
|
## Why the test fixture changes are justified (BC break)
|
||||||
|
|
||||||
|
Some existing fixtures pinned the buggy compact output and had to be updated to the
|
||||||
|
RFC-3339-correct strings — this is the legitimate case of changing a maintainer's tests
|
||||||
|
because the old expected values encode the bug being fixed:
|
||||||
|
|
||||||
|
- `tests/InstantTest.php` `providerToString`: `[0, 0, '1970-01-01T00:00Z']` ->
|
||||||
|
`'1970-01-01T00:00:00Z'`.
|
||||||
|
- `tests/ZonedDateTimeTest.php`: the DST plus/minus and interval result strings that landed
|
||||||
|
on whole minutes/hours (e.g. `2025-03-30T01:00+01:00[Europe/Prague]` ->
|
||||||
|
`2025-03-30T01:00:00+01:00[Europe/Prague]`).
|
||||||
|
- `tests/IntervalTest.php`: one exception-message fixture (`1970-01-04T11:20Z` ->
|
||||||
|
`1970-01-04T11:20:00Z`).
|
||||||
|
|
||||||
|
New assertions were added for whole-minute and whole-hour values to lock in the `:00`:
|
||||||
|
|
||||||
|
- `tests/InstantTest.php`: `[1700000040, 0, '2023-11-14T22:14:00Z']` and
|
||||||
|
`[1700002800, 0, '2023-11-14T23:00:00Z']`.
|
||||||
|
- `tests/ZonedDateTimeTest.php`: `['2000-01-20T12:34', '-07:00', '2000-01-20T12:34:00-07:00']`
|
||||||
|
and `['2000-01-20T12:00', 'America/Los_Angeles', '2000-01-20T12:00:00-08:00[America/Los_Angeles]']`.
|
||||||
|
|
||||||
|
Because the serialized string representation changes, this is technically a backward
|
||||||
|
compatibility break, matching the maintainer's note on issue #110: "this will target
|
||||||
|
version 0.8.0 as this is technically a BC break." Parsing is unaffected — the parser still
|
||||||
|
accepts the compact input form, so round-tripping and existing stored values keep working.
|
||||||
|
|
||||||
|
## Test evidence
|
||||||
|
|
||||||
|
Gate command:
|
||||||
|
|
||||||
|
```
|
||||||
|
composer install && vendor/bin/phpunit
|
||||||
|
```
|
||||||
|
|
||||||
|
Result: exit code `0` — `OK (7865 tests, 38307 assertions)`, 0 failures.
|
||||||
|
|
||||||
|
Before the fix, `Instant::of(1700000040, 0)->toISOString()` returned `2023-11-14T22:14Z`;
|
||||||
|
after the fix it returns `2023-11-14T22:14:00Z`.
|
||||||
@@ -357,6 +357,10 @@ final readonly class Instant implements JsonSerializable, Stringable
|
|||||||
/**
|
/**
|
||||||
* Returns the ISO 8601 representation of this instant.
|
* Returns the ISO 8601 representation of this instant.
|
||||||
*
|
*
|
||||||
|
* The time part always includes the seconds, so that the output is a valid
|
||||||
|
* RFC 3339 date-time, as required by section 5.6 of that specification even
|
||||||
|
* when the seconds are zero.
|
||||||
|
*
|
||||||
* @return non-empty-string
|
* @return non-empty-string
|
||||||
*/
|
*/
|
||||||
public function toISOString(): string
|
public function toISOString(): string
|
||||||
|
|||||||
+21
-1
@@ -735,11 +735,31 @@ final readonly class ZonedDateTime implements JsonSerializable, Stringable
|
|||||||
/**
|
/**
|
||||||
* Returns the ISO 8601 representation of this zoned date time.
|
* Returns the ISO 8601 representation of this zoned date time.
|
||||||
*
|
*
|
||||||
|
* Unlike {@see LocalTime::toISOString()}, the time part always includes the
|
||||||
|
* seconds, so that the output is a valid RFC 3339 date-time. This type is
|
||||||
|
* primarily meant for interoperability, and RFC 3339 section 5.6 requires
|
||||||
|
* the full `HH:MM:SS` time even when the seconds are zero.
|
||||||
|
*
|
||||||
* @return non-empty-string
|
* @return non-empty-string
|
||||||
*/
|
*/
|
||||||
public function toISOString(): string
|
public function toISOString(): string
|
||||||
{
|
{
|
||||||
$string = $this->localDateTime . $this->timeZoneOffset;
|
$time = $this->localDateTime->getTime();
|
||||||
|
|
||||||
|
$hour = $time->getHour();
|
||||||
|
$minute = $time->getMinute();
|
||||||
|
$second = $time->getSecond();
|
||||||
|
$nano = $time->getNano();
|
||||||
|
|
||||||
|
$string = $this->localDateTime->getDate()
|
||||||
|
. 'T'
|
||||||
|
. ($hour < 10 ? '0' . $hour : $hour)
|
||||||
|
. ':'
|
||||||
|
. ($minute < 10 ? '0' . $minute : $minute)
|
||||||
|
. ':'
|
||||||
|
. ($second < 10 ? '0' . $second : $second)
|
||||||
|
. ($nano !== 0 ? '.' . rtrim(str_pad((string) $nano, 9, '0', STR_PAD_LEFT), '0') : '')
|
||||||
|
. $this->timeZoneOffset;
|
||||||
|
|
||||||
if ($this->timeZone instanceof TimeZoneRegion) {
|
if ($this->timeZone instanceof TimeZoneRegion) {
|
||||||
$string .= '[' . $this->timeZone . ']';
|
$string .= '[' . $this->timeZone . ']';
|
||||||
|
|||||||
@@ -573,11 +573,11 @@ class InstantTest extends AbstractTestCase
|
|||||||
public static function providerGetIntervalTo(): array
|
public static function providerGetIntervalTo(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
[1672567200, 0, 1672567200, 0, '2023-01-01T10:00Z/2023-01-01T10:00Z'],
|
[1672567200, 0, 1672567200, 0, '2023-01-01T10:00:00Z/2023-01-01T10:00:00Z'],
|
||||||
[1672567200, 0, 1672567210, 0, '2023-01-01T10:00Z/2023-01-01T10:00:10Z'],
|
[1672567200, 0, 1672567210, 0, '2023-01-01T10:00:00Z/2023-01-01T10:00:10Z'],
|
||||||
[1672567200, 1000000, 1672567210, 2000000, '2023-01-01T10:00:00.001Z/2023-01-01T10:00:10.002Z'],
|
[1672567200, 1000000, 1672567210, 2000000, '2023-01-01T10:00:00.001Z/2023-01-01T10:00:10.002Z'],
|
||||||
[1672567200, 1, 1672567200, 9, '2023-01-01T10:00:00.000000001Z/2023-01-01T10:00:00.000000009Z'],
|
[1672567200, 1, 1672567200, 9, '2023-01-01T10:00:00.000000001Z/2023-01-01T10:00:00.000000009Z'],
|
||||||
[1672567200, 0, 1672653600, 0, '2023-01-01T10:00Z/2023-01-02T10:00Z'],
|
[1672567200, 0, 1672653600, 0, '2023-01-01T10:00:00Z/2023-01-02T10:00:00Z'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -648,11 +648,14 @@ class InstantTest extends AbstractTestCase
|
|||||||
[-2000000000, 0, '1906-08-16T20:26:40Z'],
|
[-2000000000, 0, '1906-08-16T20:26:40Z'],
|
||||||
[-1, 0, '1969-12-31T23:59:59Z'],
|
[-1, 0, '1969-12-31T23:59:59Z'],
|
||||||
[-1, 123, '1969-12-31T23:59:59.000000123Z'],
|
[-1, 123, '1969-12-31T23:59:59.000000123Z'],
|
||||||
[0, 0, '1970-01-01T00:00Z'],
|
[0, 0, '1970-01-01T00:00:00Z'],
|
||||||
[0, 123456, '1970-01-01T00:00:00.000123456Z'],
|
[0, 123456, '1970-01-01T00:00:00.000123456Z'],
|
||||||
[1, 0, '1970-01-01T00:00:01Z'],
|
[1, 0, '1970-01-01T00:00:01Z'],
|
||||||
[1, 123456789, '1970-01-01T00:00:01.123456789Z'],
|
[1, 123456789, '1970-01-01T00:00:01.123456789Z'],
|
||||||
[2000000000, 0, '2033-05-18T03:33:20Z'],
|
[2000000000, 0, '2033-05-18T03:33:20Z'],
|
||||||
|
// whole minute and whole hour must keep the full ":00" seconds (RFC 3339 section 5.6)
|
||||||
|
[1700000040, 0, '2023-11-14T22:14:00Z'],
|
||||||
|
[1700002800, 0, '2023-11-14T23:00:00Z'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -222,7 +222,7 @@ class IntervalTest extends AbstractTestCase
|
|||||||
$interval2 = Interval::of(Instant::of(300000), Instant::of(400000));
|
$interval2 = Interval::of(Instant::of(300000), Instant::of(400000));
|
||||||
|
|
||||||
$this->expectException(DateTimeException::class);
|
$this->expectException(DateTimeException::class);
|
||||||
$this->expectExceptionMessage('Intervals "1970-01-02T03:46:40Z/1970-01-03T07:33:20Z" and "1970-01-04T11:20Z/1970-01-05T15:06:40Z" do not intersect.');
|
$this->expectExceptionMessage('Intervals "1970-01-02T03:46:40Z/1970-01-03T07:33:20Z" and "1970-01-04T11:20:00Z/1970-01-05T15:06:40Z" do not intersect.');
|
||||||
|
|
||||||
$interval1->getIntersectionWith($interval2);
|
$interval1->getIntersectionWith($interval2);
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-30
@@ -436,7 +436,7 @@ class ZonedDateTimeTest extends AbstractTestCase
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
['2018-07-21 14:09:10.23456', 'America/Los_Angeles', '2018-07-21T14:09:10.23456-07:00[America/Los_Angeles]'],
|
['2018-07-21 14:09:10.23456', 'America/Los_Angeles', '2018-07-21T14:09:10.23456-07:00[America/Los_Angeles]'],
|
||||||
['2019-01-21 17:59', 'America/Los_Angeles', '2019-01-21T17:59-08:00[America/Los_Angeles]'],
|
['2019-01-21 17:59', 'America/Los_Angeles', '2019-01-21T17:59:00-08:00[America/Los_Angeles]'],
|
||||||
['2019-01-23 09:10:11.123', '+05:30', '2019-01-23T09:10:11.123+05:30'],
|
['2019-01-23 09:10:11.123', '+05:30', '2019-01-23T09:10:11.123+05:30'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -753,12 +753,12 @@ class ZonedDateTimeTest extends AbstractTestCase
|
|||||||
['2000-01-01T12:34:56.123456789-08:00[America/Los_Angeles]', 25, '2000-06-24T12:34:56.123456789-07:00[America/Los_Angeles]'],
|
['2000-01-01T12:34:56.123456789-08:00[America/Los_Angeles]', 25, '2000-06-24T12:34:56.123456789-07:00[America/Los_Angeles]'],
|
||||||
|
|
||||||
// https://github.com/brick/date-time/issues/115
|
// https://github.com/brick/date-time/issues/115
|
||||||
['2025-03-23T01:00+01:00[Europe/Prague]', 1, '2025-03-30T01:00+01:00[Europe/Prague]'],
|
['2025-03-23T01:00+01:00[Europe/Prague]', 1, '2025-03-30T01:00:00+01:00[Europe/Prague]'],
|
||||||
['2025-03-23T01:30+01:00[Europe/Prague]', 1, '2025-03-30T01:30+01:00[Europe/Prague]'],
|
['2025-03-23T01:30+01:00[Europe/Prague]', 1, '2025-03-30T01:30:00+01:00[Europe/Prague]'],
|
||||||
['2025-03-23T02:00+01:00[Europe/Prague]', 1, '2025-03-30T03:00+02:00[Europe/Prague]'],
|
['2025-03-23T02:00+01:00[Europe/Prague]', 1, '2025-03-30T03:00:00+02:00[Europe/Prague]'],
|
||||||
['2025-03-23T02:30+01:00[Europe/Prague]', 1, '2025-03-30T03:30+02:00[Europe/Prague]'],
|
['2025-03-23T02:30+01:00[Europe/Prague]', 1, '2025-03-30T03:30:00+02:00[Europe/Prague]'],
|
||||||
['2025-03-23T03:00+01:00[Europe/Prague]', 1, '2025-03-30T03:00+02:00[Europe/Prague]'],
|
['2025-03-23T03:00+01:00[Europe/Prague]', 1, '2025-03-30T03:00:00+02:00[Europe/Prague]'],
|
||||||
['2025-03-23T03:30+01:00[Europe/Prague]', 1, '2025-03-30T03:30+02:00[Europe/Prague]'],
|
['2025-03-23T03:30+01:00[Europe/Prague]', 1, '2025-03-30T03:30:00+02:00[Europe/Prague]'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -782,12 +782,12 @@ class ZonedDateTimeTest extends AbstractTestCase
|
|||||||
['2000-04-03T12:34:56.123456789-07:00[America/Los_Angeles]', -2, '2000-04-01T12:34:56.123456789-08:00[America/Los_Angeles]'],
|
['2000-04-03T12:34:56.123456789-07:00[America/Los_Angeles]', -2, '2000-04-01T12:34:56.123456789-08:00[America/Los_Angeles]'],
|
||||||
|
|
||||||
// https://github.com/brick/date-time/issues/115
|
// https://github.com/brick/date-time/issues/115
|
||||||
['2025-03-29T01:00+01:00[Europe/Prague]', 1, '2025-03-30T01:00+01:00[Europe/Prague]'],
|
['2025-03-29T01:00+01:00[Europe/Prague]', 1, '2025-03-30T01:00:00+01:00[Europe/Prague]'],
|
||||||
['2025-03-29T01:30+01:00[Europe/Prague]', 1, '2025-03-30T01:30+01:00[Europe/Prague]'],
|
['2025-03-29T01:30+01:00[Europe/Prague]', 1, '2025-03-30T01:30:00+01:00[Europe/Prague]'],
|
||||||
['2025-03-29T02:00+01:00[Europe/Prague]', 1, '2025-03-30T03:00+02:00[Europe/Prague]'],
|
['2025-03-29T02:00+01:00[Europe/Prague]', 1, '2025-03-30T03:00:00+02:00[Europe/Prague]'],
|
||||||
['2025-03-29T02:30+01:00[Europe/Prague]', 1, '2025-03-30T03:30+02:00[Europe/Prague]'],
|
['2025-03-29T02:30+01:00[Europe/Prague]', 1, '2025-03-30T03:30:00+02:00[Europe/Prague]'],
|
||||||
['2025-03-29T03:00+01:00[Europe/Prague]', 1, '2025-03-30T03:00+02:00[Europe/Prague]'],
|
['2025-03-29T03:00+01:00[Europe/Prague]', 1, '2025-03-30T03:00:00+02:00[Europe/Prague]'],
|
||||||
['2025-03-29T03:30+01:00[Europe/Prague]', 1, '2025-03-30T03:30+02:00[Europe/Prague]'],
|
['2025-03-29T03:30+01:00[Europe/Prague]', 1, '2025-03-30T03:30:00+02:00[Europe/Prague]'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -815,12 +815,12 @@ class ZonedDateTimeTest extends AbstractTestCase
|
|||||||
['2000-04-02T04:12:34.123456789-07:00[America/Los_Angeles]', 1, '2000-04-02T05:12:34.123456789-07:00[America/Los_Angeles]'],
|
['2000-04-02T04:12:34.123456789-07:00[America/Los_Angeles]', 1, '2000-04-02T05:12:34.123456789-07:00[America/Los_Angeles]'],
|
||||||
|
|
||||||
// https://github.com/brick/date-time/issues/115
|
// https://github.com/brick/date-time/issues/115
|
||||||
['2025-03-29T01:00+01:00[Europe/Prague]', 24, '2025-03-30T01:00+01:00[Europe/Prague]'],
|
['2025-03-29T01:00+01:00[Europe/Prague]', 24, '2025-03-30T01:00:00+01:00[Europe/Prague]'],
|
||||||
['2025-03-29T01:30+01:00[Europe/Prague]', 24, '2025-03-30T01:30+01:00[Europe/Prague]'],
|
['2025-03-29T01:30+01:00[Europe/Prague]', 24, '2025-03-30T01:30:00+01:00[Europe/Prague]'],
|
||||||
['2025-03-29T02:00+01:00[Europe/Prague]', 24, '2025-03-30T03:00+02:00[Europe/Prague]'],
|
['2025-03-29T02:00+01:00[Europe/Prague]', 24, '2025-03-30T03:00:00+02:00[Europe/Prague]'],
|
||||||
['2025-03-29T02:30+01:00[Europe/Prague]', 24, '2025-03-30T03:30+02:00[Europe/Prague]'],
|
['2025-03-29T02:30+01:00[Europe/Prague]', 24, '2025-03-30T03:30:00+02:00[Europe/Prague]'],
|
||||||
['2025-03-29T03:00+01:00[Europe/Prague]', 24, '2025-03-30T04:00+02:00[Europe/Prague]'],
|
['2025-03-29T03:00+01:00[Europe/Prague]', 24, '2025-03-30T04:00:00+02:00[Europe/Prague]'],
|
||||||
['2025-03-29T03:30+01:00[Europe/Prague]', 24, '2025-03-30T04:30+02:00[Europe/Prague]'],
|
['2025-03-29T03:30+01:00[Europe/Prague]', 24, '2025-03-30T04:30:00+02:00[Europe/Prague]'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -848,10 +848,10 @@ class ZonedDateTimeTest extends AbstractTestCase
|
|||||||
['2000-04-02T03:01:01.654321-07:00[America/Los_Angeles]', 1, '2000-04-02T03:02:01.654321-07:00[America/Los_Angeles]'],
|
['2000-04-02T03:01:01.654321-07:00[America/Los_Angeles]', 1, '2000-04-02T03:02:01.654321-07:00[America/Los_Angeles]'],
|
||||||
|
|
||||||
// https://github.com/brick/date-time/issues/115
|
// https://github.com/brick/date-time/issues/115
|
||||||
['2025-03-30T01:30+01:00[Europe/Prague]', 50, '2025-03-30T03:20+02:00[Europe/Prague]'],
|
['2025-03-30T01:30:00+01:00[Europe/Prague]', 50, '2025-03-30T03:20:00+02:00[Europe/Prague]'],
|
||||||
['2025-03-30T01:30+01:00[Europe/Prague]', 100, '2025-03-30T04:10+02:00[Europe/Prague]'],
|
['2025-03-30T01:30:00+01:00[Europe/Prague]', 100, '2025-03-30T04:10:00+02:00[Europe/Prague]'],
|
||||||
['2025-03-30T03:20+02:00[Europe/Prague]', -50, '2025-03-30T01:30+01:00[Europe/Prague]'],
|
['2025-03-30T03:20:00+02:00[Europe/Prague]', -50, '2025-03-30T01:30:00+01:00[Europe/Prague]'],
|
||||||
['2025-03-30T04:10+02:00[Europe/Prague]', -100, '2025-03-30T01:30+01:00[Europe/Prague]'],
|
['2025-03-30T04:10:00+02:00[Europe/Prague]', -100, '2025-03-30T01:30:00+01:00[Europe/Prague]'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -878,10 +878,10 @@ class ZonedDateTimeTest extends AbstractTestCase
|
|||||||
['2000-04-02T03:00:01.654321-07:00[America/Los_Angeles]', 1, '2000-04-02T03:00:02.654321-07:00[America/Los_Angeles]'],
|
['2000-04-02T03:00:01.654321-07:00[America/Los_Angeles]', 1, '2000-04-02T03:00:02.654321-07:00[America/Los_Angeles]'],
|
||||||
|
|
||||||
// https://github.com/brick/date-time/issues/115
|
// https://github.com/brick/date-time/issues/115
|
||||||
['2025-03-30T01:30+01:00[Europe/Prague]', 3000, '2025-03-30T03:20+02:00[Europe/Prague]'],
|
['2025-03-30T01:30:00+01:00[Europe/Prague]', 3000, '2025-03-30T03:20:00+02:00[Europe/Prague]'],
|
||||||
['2025-03-30T01:30+01:00[Europe/Prague]', 6000, '2025-03-30T04:10+02:00[Europe/Prague]'],
|
['2025-03-30T01:30:00+01:00[Europe/Prague]', 6000, '2025-03-30T04:10:00+02:00[Europe/Prague]'],
|
||||||
['2025-03-30T03:20+02:00[Europe/Prague]', -3000, '2025-03-30T01:30+01:00[Europe/Prague]'],
|
['2025-03-30T03:20:00+02:00[Europe/Prague]', -3000, '2025-03-30T01:30:00+01:00[Europe/Prague]'],
|
||||||
['2025-03-30T04:10+02:00[Europe/Prague]', -6000, '2025-03-30T01:30+01:00[Europe/Prague]'],
|
['2025-03-30T04:10:00+02:00[Europe/Prague]', -6000, '2025-03-30T01:30:00+01:00[Europe/Prague]'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1086,6 +1086,9 @@ class ZonedDateTimeTest extends AbstractTestCase
|
|||||||
return [
|
return [
|
||||||
['2000-01-20T12:34:56.123456789', 'America/Los_Angeles', '2000-01-20T12:34:56.123456789-08:00[America/Los_Angeles]'],
|
['2000-01-20T12:34:56.123456789', 'America/Los_Angeles', '2000-01-20T12:34:56.123456789-08:00[America/Los_Angeles]'],
|
||||||
['2000-01-20T12:34:56.123456789', '-07:00', '2000-01-20T12:34:56.123456789-07:00'],
|
['2000-01-20T12:34:56.123456789', '-07:00', '2000-01-20T12:34:56.123456789-07:00'],
|
||||||
|
// whole minute and whole hour must keep the full ":00" seconds (RFC 3339 section 5.6)
|
||||||
|
['2000-01-20T12:34', '-07:00', '2000-01-20T12:34:00-07:00'],
|
||||||
|
['2000-01-20T12:00', 'America/Los_Angeles', '2000-01-20T12:00:00-08:00[America/Los_Angeles]'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1121,13 +1124,13 @@ class ZonedDateTimeTest extends AbstractTestCase
|
|||||||
public static function providerGetIntervalTo(): array
|
public static function providerGetIntervalTo(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
['2023-01-01T10:00:00Z', '2023-01-01T10:00:00Z', '2023-01-01T10:00Z/2023-01-01T10:00Z'],
|
['2023-01-01T10:00:00Z', '2023-01-01T10:00:00Z', '2023-01-01T10:00:00Z/2023-01-01T10:00:00Z'],
|
||||||
['2023-01-01T10:00:00Z', '2023-01-01T10:00:10Z', '2023-01-01T10:00Z/2023-01-01T10:00:10Z'],
|
['2023-01-01T10:00:00Z', '2023-01-01T10:00:10Z', '2023-01-01T10:00:00Z/2023-01-01T10:00:10Z'],
|
||||||
['2023-01-01T10:00:00.001Z', '2023-01-01T10:00:10.002Z', '2023-01-01T10:00:00.001Z/2023-01-01T10:00:10.002Z'],
|
['2023-01-01T10:00:00.001Z', '2023-01-01T10:00:10.002Z', '2023-01-01T10:00:00.001Z/2023-01-01T10:00:10.002Z'],
|
||||||
['2023-01-01T10:00:00.001Z', '2023-01-01T13:00:10.002+03:00', '2023-01-01T10:00:00.001Z/2023-01-01T10:00:10.002Z'],
|
['2023-01-01T10:00:00.001Z', '2023-01-01T13:00:10.002+03:00', '2023-01-01T10:00:00.001Z/2023-01-01T10:00:10.002Z'],
|
||||||
['2023-01-01T10:00:00.001+03:00', '2023-01-01T13:00:10.002+03:00', '2023-01-01T07:00:00.001Z/2023-01-01T10:00:10.002Z'],
|
['2023-01-01T10:00:00.001+03:00', '2023-01-01T13:00:10.002+03:00', '2023-01-01T07:00:00.001Z/2023-01-01T10:00:10.002Z'],
|
||||||
['2023-01-01T10:00:00.000000001Z', '2023-01-01T10:00:00.000000009Z', '2023-01-01T10:00:00.000000001Z/2023-01-01T10:00:00.000000009Z'],
|
['2023-01-01T10:00:00.000000001Z', '2023-01-01T10:00:00.000000009Z', '2023-01-01T10:00:00.000000001Z/2023-01-01T10:00:00.000000009Z'],
|
||||||
['2023-01-01T10:00:00Z', '2023-01-02T10:00:00Z', '2023-01-01T10:00Z/2023-01-02T10:00Z'],
|
['2023-01-01T10:00:00Z', '2023-01-02T10:00:00Z', '2023-01-01T10:00:00Z/2023-01-02T10:00:00Z'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user