Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add UtcDateTime class #36

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions src/UtcDateTime.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

declare(strict_types=1);

namespace Brick\DateTime;

use Brick\DateTime\Parser\DateTimeParser;
use Brick\DateTime\Parser\DateTimeParseResult;

final class UtcDateTime extends ZonedDateTime
{
public static function of(LocalDateTime $dateTime, 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::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;
}
}
27 changes: 24 additions & 3 deletions src/ZonedDateTime.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

/**
Expand All @@ -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);
}
}

/**
Expand Down Expand Up @@ -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);
}
}

/**
Expand Down Expand Up @@ -697,6 +709,15 @@ public function toDateTimeImmutable() : \DateTimeImmutable
return \DateTimeImmutable::createFromMutable($this->toDateTime());
}

public function toUtcDateTime() : UtcDateTime
{
$result = UtcDateTime::ofInstant($this->instant);
if ($result instanceof UtcDateTime) {
return $result;
}
throw new \UnexpectedValueException('Incorrect type of UtcDateTime::ofInstant. Expected: UtcDateTime, got: ' . get_class($result));
}

/**
* Serializes as a string using {@see ZonedDateTime::__toString()}.
*/
Expand Down
105 changes: 105 additions & 0 deletions tests/UtcDateTimeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<?php

declare(strict_types=1);

namespace Brick\DateTime\Tests;

use Brick\DateTime\Instant;
use Brick\DateTime\LocalDateTime;
use Brick\DateTime\LocalDate;
use Brick\DateTime\LocalTime;
use Brick\DateTime\Parser\DateTimeParseException;
use Brick\DateTime\Period;
use Brick\DateTime\Duration;
use Brick\DateTime\TimeZone;
use Brick\DateTime\TimeZoneOffset;
use Brick\DateTime\UtcDateTime;
use Brick\DateTime\ZonedDateTime;
use Brick\DateTime\DayOfWeek;
use Brick\DateTime\Clock\FixedClock;

/**
* Unit tests for class ZonedDateTime.
*/
class UtcDateTimeTest extends AbstractTestCase
{
public function testOf(): void
{
$a = ZonedDateTime::of(LocalDateTime::of(2020, 1, 2), TimeZone::utc());
$this->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']
];
}
}