diff --git a/src/UtcDateTime.php b/src/UtcDateTime.php new file mode 100644 index 0000000..0081237 --- /dev/null +++ b/src/UtcDateTime.php @@ -0,0 +1,71 @@ +isEqualTo(TimeZone::utc())) { + throw new \InvalidArgumentException('Create UtcDateTime with not UTC timezone is not supported'); + } + return parent::of($dateTime, $timeZone); + } + + public static function ofInstant(Instant $instant, TimeZone $timeZone = null): ZonedDateTime + { + if ($timeZone === null) { + $timeZone = TimeZone::utc(); + } + if (!$timeZone->isEqualTo(TimeZone::utc())) { + throw new \InvalidArgumentException('Create UtcDateTime with not UTC timezone is not supported'); + } + return parent::ofInstant($instant, $timeZone); + } + + public static function now(TimeZone $timeZone = null, ?Clock $clock = null): ZonedDateTime + { + if ($timeZone === null) { + $timeZone = TimeZone::utc(); + } + if (!$timeZone->isEqualTo(TimeZone::utc())) { + throw new \InvalidArgumentException('Create UtcDateTime with not UTC timezone is not supported'); + } + return parent::now($timeZone, $clock); + } + + public static function from(DateTimeParseResult $result): ZonedDateTime + { + $result = parent::from($result); + if (!$result->getTimeZone()->isEqualTo(TimeZone::utc())) { + $result = $result->withTimeZoneSameInstant(TimeZone::utc()); + } + return $result; + } + + public static function parse(string $text, ?DateTimeParser $parser = null): ZonedDateTime + { + $result = parent::parse($text, $parser); + if (!$result->getTimeZone()->isEqualTo(TimeZone::utc())) { + $result = $result->withTimeZoneSameInstant(TimeZone::utc()); + } + return $result; + } + + public static function fromDateTime(\DateTimeInterface $dateTime): ZonedDateTime + { + $result = parent::fromDateTime($dateTime); + if (!$result->getTimeZone()->isEqualTo(TimeZone::utc())) { + $result = $result->withTimeZoneSameInstant(TimeZone::utc()); + } + return $result; + } +} \ No newline at end of file diff --git a/src/ZonedDateTime.php b/src/ZonedDateTime.php index 9eb66d3..731c484 100644 --- a/src/ZonedDateTime.php +++ b/src/ZonedDateTime.php @@ -108,7 +108,11 @@ public static function of(LocalDateTime $dateTime, TimeZone $timeZone) : ZonedDa // DateTime does not support nanos of seconds, so we just copy the nanos back from the original date-time. $dateTime = LocalDateTime::parse($dt->format('Y-m-d\TH:i:s'))->withNano($dateTime->getNano()); - return new ZonedDateTime($dateTime, $timeZoneOffset, $timeZone, $instant); + if ($timeZone->isEqualTo(TimeZone::utc())) { + return new UtcDateTime($dateTime, $timeZoneOffset, $timeZone, $instant); + } else { + return new ZonedDateTime($dateTime, $timeZoneOffset, $timeZone, $instant); + } } /** @@ -135,7 +139,11 @@ public static function ofInstant(Instant $instant, TimeZone $timeZone) : ZonedDa $timeZoneOffset = TimeZoneOffset::ofTotalSeconds($dateTime->getOffset()); } - return new ZonedDateTime($localDateTime, $timeZoneOffset, $timeZone, $instant); + if ($timeZone->isEqualTo(TimeZone::utc())) { + return new UtcDateTime($localDateTime, $timeZoneOffset, $timeZone, $instant); + } else { + return new ZonedDateTime($localDateTime, $timeZoneOffset, $timeZone, $instant); + } } /** @@ -222,7 +230,11 @@ public static function fromDateTime(\DateTimeInterface $dateTime) : ZonedDateTim $instant = Instant::of($dateTime->getTimestamp(), $localDateTime->getNano()); - return new ZonedDateTime($localDateTime, $timeZoneOffset, $timeZone, $instant); + if ($timeZone->isEqualTo(TimeZone::utc())) { + return new UtcDateTime($localDateTime, $timeZoneOffset, $timeZone, $instant); + } else { + return new ZonedDateTime($localDateTime, $timeZoneOffset, $timeZone, $instant); + } } /** @@ -697,6 +709,12 @@ public function toDateTimeImmutable() : \DateTimeImmutable return \DateTimeImmutable::createFromMutable($this->toDateTime()); } + public function toUtcDateTime() : UtcDateTime + { + /** @noinspection PhpIncompatibleReturnTypeInspection */ + return UtcDateTime::ofInstant($this->instant); + } + /** * Serializes as a string using {@see ZonedDateTime::__toString()}. */ diff --git a/tests/UtcDateTimeTest.php b/tests/UtcDateTimeTest.php new file mode 100644 index 0000000..265fd58 --- /dev/null +++ b/tests/UtcDateTimeTest.php @@ -0,0 +1,105 @@ +assertInstanceOf(UtcDateTime::class, $a); + $b = UtcDateTime::of(LocalDateTime::of(2020, 1, 2), TimeZone::utc()); + $this->assertInstanceOf(UtcDateTime::class, $b); + $c = UtcDateTime::of(LocalDateTime::of(2020, 1, 2)); + $this->assertInstanceOf(UtcDateTime::class, $c); + + $this->assertEquals($b, $a); + $this->assertEquals($c, $a); + $this->assertEquals($c, $b); + } + + public function testOfError(): void + { + $this->expectException(\InvalidArgumentException::class); + UtcDateTime::of(LocalDateTime::of(2020, 1, 2), TimeZone::parse('Europe/Moscow')); + } + + /** + * @dataProvider providerFromDateTime + * @param string $dateTimeString + * @param string $timeZone + * @param string $expected + * @throws \Exception + */ + public function testFromDateTime(string $dateTimeString, string $timeZone, string $expected): void + { + $dateTime = new \DateTime($dateTimeString, new \DateTimeZone($timeZone)); + $this->assertIs(UtcDateTime::class, $expected, UtcDateTime::fromDateTime($dateTime)); + } + + public function providerFromDateTime() : array + { + return [ + ['2018-07-21 14:09:10.23456', 'America/Los_Angeles', '2018-07-21T21:09:10.23456Z'], + ['2019-01-21 17:59', 'America/Los_Angeles', '2019-01-22T01:59Z'], + ['2019-01-23 09:10:11.123', '+05:30', '2019-01-23T03:40:11.123Z'], + ]; + } + + /** + * @dataProvider providerParse + * + * @param string $text The string to parse. + * @param string $date The expected date string. + * @param string $time The expected time string. + * @param string $offset The expected time-zone offset. + * @param string $zone The expected time-zone, should be the same as offset when no region is specified. + */ + public function testParse(string $text, string $date, string $time, string $offset, string $zone): void + { + $zonedDateTime = UtcDateTime::parse($text); + + $this->assertInstanceOf(UtcDateTime::class, $zonedDateTime); + + $this->assertSame($date, (string) $zonedDateTime->getDate()); + $this->assertSame($time, (string) $zonedDateTime->getTime()); + $this->assertSame($offset, (string) $zonedDateTime->getTimeZoneOffset()); + $this->assertSame($zone, (string) $zonedDateTime->getTimeZone()); + } + + public function providerParse() : array + { + return [ + ['2001-02-03T01:02Z', '2001-02-03', '01:02', 'Z', 'Z'], + ['2001-02-03T01:02:03Z', '2001-02-03', '01:02:03', 'Z', 'Z'], + ['2001-02-03T01:02:03.456Z', '2001-02-03', '01:02:03.456', 'Z', 'Z'], + ['2001-02-03T01:02-03:00', '2001-02-03', '04:02', 'Z', 'Z'], + ['2001-02-03T01:02:03+04:00', '2001-02-02', '21:02:03', 'Z', 'Z'], + + //['2001-02-03T01:02:03.456+12:34:56', '2001-02-03', '01:02:03.456', 'Z', 'Z'], + ['2001-02-03T01:02Z[Europe/London]', '2001-02-03', '01:02', 'Z', 'Z'], + ['2001-02-03T01:02+00:00[Europe/London]', '2001-02-03', '01:02', 'Z', 'Z'], + ['2001-02-03T01:02:03-00:00[Europe/London]', '2001-02-03', '01:02:03', 'Z', 'Z'], + ['2001-02-03T01:02:03.456+00:00[Europe/London]', '2001-02-03', '01:02:03.456', 'Z', 'Z'] + ]; + } +}