Skip to content

Commit 353b31a

Browse files
committed
add UtcDateTime class
1 parent b4e45bf commit 353b31a

File tree

3 files changed

+197
-3
lines changed

3 files changed

+197
-3
lines changed

src/UtcDateTime.php

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Brick\DateTime;
6+
7+
use Brick\DateTime\Parser\DateTimeParser;
8+
use Brick\DateTime\Parser\DateTimeParseResult;
9+
10+
final class UtcDateTime extends ZonedDateTime
11+
{
12+
public static function of(LocalDateTime $dateTime, TimeZone $timeZone = null): ZonedDateTime
13+
{
14+
if ($timeZone === null) {
15+
$timeZone = TimeZone::utc();
16+
}
17+
if (!$timeZone->isEqualTo(TimeZone::utc())) {
18+
throw new \InvalidArgumentException('Create UtcDateTime with not UTC timezone is not supported');
19+
}
20+
return parent::of($dateTime, $timeZone);
21+
}
22+
23+
public static function ofInstant(Instant $instant, TimeZone $timeZone = null): ZonedDateTime
24+
{
25+
if ($timeZone === null) {
26+
$timeZone = TimeZone::utc();
27+
}
28+
if (!$timeZone->isEqualTo(TimeZone::utc())) {
29+
throw new \InvalidArgumentException('Create UtcDateTime with not UTC timezone is not supported');
30+
}
31+
return parent::ofInstant($instant, $timeZone);
32+
}
33+
34+
public static function now(TimeZone $timeZone = null, ?Clock $clock = null): ZonedDateTime
35+
{
36+
if ($timeZone === null) {
37+
$timeZone = TimeZone::utc();
38+
}
39+
if (!$timeZone->isEqualTo(TimeZone::utc())) {
40+
throw new \InvalidArgumentException('Create UtcDateTime with not UTC timezone is not supported');
41+
}
42+
return parent::now($timeZone, $clock);
43+
}
44+
45+
public static function from(DateTimeParseResult $result): ZonedDateTime
46+
{
47+
$result = parent::from($result);
48+
if (!$result->getTimeZone()->isEqualTo(TimeZone::utc())) {
49+
$result = $result->withTimeZoneSameInstant(TimeZone::utc());
50+
}
51+
return $result;
52+
}
53+
54+
public static function parse(string $text, ?DateTimeParser $parser = null): ZonedDateTime
55+
{
56+
$result = parent::parse($text, $parser);
57+
if (!$result->getTimeZone()->isEqualTo(TimeZone::utc())) {
58+
$result = $result->withTimeZoneSameInstant(TimeZone::utc());
59+
}
60+
return $result;
61+
}
62+
63+
public static function fromDateTime(\DateTimeInterface $dateTime): ZonedDateTime
64+
{
65+
$result = parent::fromDateTime($dateTime);
66+
if (!$result->getTimeZone()->isEqualTo(TimeZone::utc())) {
67+
$result = $result->withTimeZoneSameInstant(TimeZone::utc());
68+
}
69+
return $result;
70+
}
71+
}

src/ZonedDateTime.php

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,11 @@ public static function of(LocalDateTime $dateTime, TimeZone $timeZone) : ZonedDa
108108
// DateTime does not support nanos of seconds, so we just copy the nanos back from the original date-time.
109109
$dateTime = LocalDateTime::parse($dt->format('Y-m-d\TH:i:s'))->withNano($dateTime->getNano());
110110

111-
return new ZonedDateTime($dateTime, $timeZoneOffset, $timeZone, $instant);
111+
if ($timeZone->isEqualTo(TimeZone::utc())) {
112+
return new UtcDateTime($dateTime, $timeZoneOffset, $timeZone, $instant);
113+
} else {
114+
return new ZonedDateTime($dateTime, $timeZoneOffset, $timeZone, $instant);
115+
}
112116
}
113117

114118
/**
@@ -135,7 +139,11 @@ public static function ofInstant(Instant $instant, TimeZone $timeZone) : ZonedDa
135139
$timeZoneOffset = TimeZoneOffset::ofTotalSeconds($dateTime->getOffset());
136140
}
137141

138-
return new ZonedDateTime($localDateTime, $timeZoneOffset, $timeZone, $instant);
142+
if ($timeZone->isEqualTo(TimeZone::utc())) {
143+
return new UtcDateTime($localDateTime, $timeZoneOffset, $timeZone, $instant);
144+
} else {
145+
return new ZonedDateTime($localDateTime, $timeZoneOffset, $timeZone, $instant);
146+
}
139147
}
140148

141149
/**
@@ -222,7 +230,11 @@ public static function fromDateTime(\DateTimeInterface $dateTime) : ZonedDateTim
222230

223231
$instant = Instant::of($dateTime->getTimestamp(), $localDateTime->getNano());
224232

225-
return new ZonedDateTime($localDateTime, $timeZoneOffset, $timeZone, $instant);
233+
if ($timeZone->isEqualTo(TimeZone::utc())) {
234+
return new UtcDateTime($localDateTime, $timeZoneOffset, $timeZone, $instant);
235+
} else {
236+
return new ZonedDateTime($localDateTime, $timeZoneOffset, $timeZone, $instant);
237+
}
226238
}
227239

228240
/**
@@ -697,6 +709,12 @@ public function toDateTimeImmutable() : \DateTimeImmutable
697709
return \DateTimeImmutable::createFromMutable($this->toDateTime());
698710
}
699711

712+
public function toUtcDateTime() : UtcDateTime
713+
{
714+
/** @noinspection PhpIncompatibleReturnTypeInspection */
715+
return UtcDateTime::ofInstant($this->instant);
716+
}
717+
700718
/**
701719
* Serializes as a string using {@see ZonedDateTime::__toString()}.
702720
*/

tests/UtcDateTimeTest.php

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Brick\DateTime\Tests;
6+
7+
use Brick\DateTime\Instant;
8+
use Brick\DateTime\LocalDateTime;
9+
use Brick\DateTime\LocalDate;
10+
use Brick\DateTime\LocalTime;
11+
use Brick\DateTime\Parser\DateTimeParseException;
12+
use Brick\DateTime\Period;
13+
use Brick\DateTime\Duration;
14+
use Brick\DateTime\TimeZone;
15+
use Brick\DateTime\TimeZoneOffset;
16+
use Brick\DateTime\UtcDateTime;
17+
use Brick\DateTime\ZonedDateTime;
18+
use Brick\DateTime\DayOfWeek;
19+
use Brick\DateTime\Clock\FixedClock;
20+
21+
/**
22+
* Unit tests for class ZonedDateTime.
23+
*/
24+
class UtcDateTimeTest extends AbstractTestCase
25+
{
26+
public function testOf(): void
27+
{
28+
$a = ZonedDateTime::of(LocalDateTime::of(2020, 1, 2), TimeZone::utc());
29+
$this->assertInstanceOf(UtcDateTime::class, $a);
30+
$b = UtcDateTime::of(LocalDateTime::of(2020, 1, 2), TimeZone::utc());
31+
$this->assertInstanceOf(UtcDateTime::class, $b);
32+
$c = UtcDateTime::of(LocalDateTime::of(2020, 1, 2));
33+
$this->assertInstanceOf(UtcDateTime::class, $c);
34+
35+
$this->assertEquals($b, $a);
36+
$this->assertEquals($c, $a);
37+
$this->assertEquals($c, $b);
38+
}
39+
40+
public function testOfError(): void
41+
{
42+
$this->expectException(\InvalidArgumentException::class);
43+
UtcDateTime::of(LocalDateTime::of(2020, 1, 2), TimeZone::parse('Europe/Moscow'));
44+
}
45+
46+
/**
47+
* @dataProvider providerFromDateTime
48+
* @param string $dateTimeString
49+
* @param string $timeZone
50+
* @param string $expected
51+
* @throws \Exception
52+
*/
53+
public function testFromDateTime(string $dateTimeString, string $timeZone, string $expected): void
54+
{
55+
$dateTime = new \DateTime($dateTimeString, new \DateTimeZone($timeZone));
56+
$this->assertIs(UtcDateTime::class, $expected, UtcDateTime::fromDateTime($dateTime));
57+
}
58+
59+
public function providerFromDateTime() : array
60+
{
61+
return [
62+
['2018-07-21 14:09:10.23456', 'America/Los_Angeles', '2018-07-21T21:09:10.23456Z'],
63+
['2019-01-21 17:59', 'America/Los_Angeles', '2019-01-22T01:59Z'],
64+
['2019-01-23 09:10:11.123', '+05:30', '2019-01-23T03:40:11.123Z'],
65+
];
66+
}
67+
68+
/**
69+
* @dataProvider providerParse
70+
*
71+
* @param string $text The string to parse.
72+
* @param string $date The expected date string.
73+
* @param string $time The expected time string.
74+
* @param string $offset The expected time-zone offset.
75+
* @param string $zone The expected time-zone, should be the same as offset when no region is specified.
76+
*/
77+
public function testParse(string $text, string $date, string $time, string $offset, string $zone): void
78+
{
79+
$zonedDateTime = UtcDateTime::parse($text);
80+
81+
$this->assertInstanceOf(UtcDateTime::class, $zonedDateTime);
82+
83+
$this->assertSame($date, (string) $zonedDateTime->getDate());
84+
$this->assertSame($time, (string) $zonedDateTime->getTime());
85+
$this->assertSame($offset, (string) $zonedDateTime->getTimeZoneOffset());
86+
$this->assertSame($zone, (string) $zonedDateTime->getTimeZone());
87+
}
88+
89+
public function providerParse() : array
90+
{
91+
return [
92+
['2001-02-03T01:02Z', '2001-02-03', '01:02', 'Z', 'Z'],
93+
['2001-02-03T01:02:03Z', '2001-02-03', '01:02:03', 'Z', 'Z'],
94+
['2001-02-03T01:02:03.456Z', '2001-02-03', '01:02:03.456', 'Z', 'Z'],
95+
['2001-02-03T01:02-03:00', '2001-02-03', '04:02', 'Z', 'Z'],
96+
['2001-02-03T01:02:03+04:00', '2001-02-02', '21:02:03', 'Z', 'Z'],
97+
98+
//['2001-02-03T01:02:03.456+12:34:56', '2001-02-03', '01:02:03.456', 'Z', 'Z'],
99+
['2001-02-03T01:02Z[Europe/London]', '2001-02-03', '01:02', 'Z', 'Z'],
100+
['2001-02-03T01:02+00:00[Europe/London]', '2001-02-03', '01:02', 'Z', 'Z'],
101+
['2001-02-03T01:02:03-00:00[Europe/London]', '2001-02-03', '01:02:03', 'Z', 'Z'],
102+
['2001-02-03T01:02:03.456+00:00[Europe/London]', '2001-02-03', '01:02:03.456', 'Z', 'Z']
103+
];
104+
}
105+
}

0 commit comments

Comments
 (0)