Skip to content

Commit

Permalink
rewrote safe\DatetimeImmutable to use an inner datetimeImmutable
Browse files Browse the repository at this point in the history
  • Loading branch information
Kharhamel committed Sep 23, 2019
1 parent 9f1c30c commit 1f8d457
Show file tree
Hide file tree
Showing 2 changed files with 148 additions and 43 deletions.
106 changes: 93 additions & 13 deletions generator/tests/DateTimeImmutableTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,21 @@ protected function setUp()
require_once __DIR__ . '/../../lib/DateTimeImmutable.php';
}

public function testSafeDatetimeImmutableCrashOnError(): void
public function testCreateFromFormatCrashOnError(): void
{
$this->expectException(DatetimeException::class);
$datetime = DateTimeImmutable::createFromFormat('lol', 'super');
}

public function testSafeDatetimeImmutablePreserveTimeAndTimezone(): void
public function testConstructorPreserveTimeAndTimezone(): void
{
$timezone = new \DateTimeZone('Pacific/Chatham');
$datetime = new DateTimeImmutable('now', $timezone);
$this->assertInstanceOf(DateTimeImmutable::class, $datetime);
$this->assertEquals($timezone, $datetime->getTimezone());
}

public function testCreateFromFormatPreserveTimeAndTimezone(): void
{
$timezone = new \DateTimeZone('Pacific/Chatham');
$datetime = DateTimeImmutable::createFromFormat('d-m-Y', '20-03-2006', $timezone);
Expand All @@ -32,24 +40,96 @@ public function testSafeDatetimeImmutablePreserveTimeAndTimezone(): void
$this->assertEquals($timezone, $datetime->getTimezone());
}

public function testSafeDatetimeImmutableSetDate(): void
public function testSafeDatetimeImmutableIsImmutable(): void
{
$datetime1 = new DateTimeImmutable();
$datetime2 = $datetime1->add(new \DateInterval('P1W'));

$this->assertNotEquals($datetime1, $datetime2);
}

public function testSetDate(): void
{
$datetime = new DateTimeImmutable();
$datetime = new \DateTimeImmutable();
$safeDatetime = new DateTimeImmutable();
$datetime = $datetime->setDate(2017, 4, 6);
$this->assertInstanceOf(DateTimeImmutable::class, $datetime);
$this->assertEquals(2017, $datetime->format('Y'));
$this->assertEquals(4, $datetime->format('n'));
$this->assertEquals(6, $datetime->format('j'));
$safeDatetime = $safeDatetime->setDate(2017, 4, 6);
$this->assertInstanceOf(DateTimeImmutable::class, $safeDatetime);
$this->assertEquals($datetime->format('Y-m-d'), $safeDatetime->format('Y-m-d'));
}

//todo: test an error case
public function testSetIsoDate(): void
{
$datetime = new \DateTimeImmutable();
$safeDatetime = new DateTimeImmutable();
$datetime = $datetime->setISODate(2017, 4, 6);
$safeDatetime = $safeDatetime->setISODate(2017, 4, 6);
$this->assertInstanceOf(DateTimeImmutable::class, $safeDatetime);
$this->assertEquals($datetime->format('Y-m-d'), $safeDatetime->format('Y-m-d'));
}

public function testSafeDatetimeImmutableModify(): void
public function testModify(): void
{
$datetime = new DateTimeImmutable();
$datetime = new \DateTimeImmutable();
$datetime = $datetime->setDate(2017, 4, 6);
$datetime = $datetime->modify('+1 day');
$this->assertInstanceOf(DateTimeImmutable::class, $datetime);
$this->assertEquals('7-4-2017', $datetime->format('j-n-Y'));
$safeDatime = new DateTimeImmutable();
$safeDatime = $safeDatime->setDate(2017, 4, 6);
$safeDatime = $safeDatime->modify('+1 day');
$this->assertInstanceOf(DateTimeImmutable::class, $safeDatime);
$this->assertEquals($datetime->format('j-n-Y'), $safeDatime->format('j-n-Y'));
}

public function testSetTimestamp(): void
{
$datetime = new \DateTimeImmutable('2000-01-01');
$safeDatime = new DateTimeImmutable('2000-01-01');
$datetime = $datetime = $datetime->setTimestamp(12);
$safeDatime = $safeDatime->setTimestamp(12);

$this->assertEquals($datetime->getTimestamp(), $safeDatime->getTimestamp());
}

public function testSetTimezone(): void
{
$timezone = new \DateTimeZone('Pacific/Chatham');
$datetime = new \DateTimeImmutable('2000-01-01');
$safeDatime = new DateTimeImmutable('2000-01-01');
$datetime = $datetime->setTimezone($timezone);
$safeDatime = $safeDatime->setTimezone($timezone);

$this->assertEquals($datetime->getTimezone(), $safeDatime->getTimezone());
}

public function testSetTime(): void
{
$datetime = new \DateTimeImmutable('2000-01-01');
$safeDatime = new DateTimeImmutable('2000-01-01');
$datetime = $datetime->setTime(2, 3, 1, 5);
$safeDatime = $safeDatime->setTime(2, 3, 1, 5);

$this->assertEquals($datetime->format('H-i-s-u'), $safeDatime->format('H-i-s-u'));
}

public function testAdd(): void
{
$interval = new \DateInterval('P1M');
$datetime = new \DateTimeImmutable('2000-01-01');
$safeDatime = new DateTimeImmutable('2000-01-01');
$datetime = $datetime->add($interval);
$safeDatime = $safeDatime->add($interval);

$this->assertEquals($datetime->getTimestamp(), $safeDatime->getTimestamp());
}

public function testSub(): void
{
$interval = new \DateInterval('P1M');
$datetime = new \DateTimeImmutable('2000-01-01');
$safeDatime = new DateTimeImmutable('2000-01-01');
$datetime = $datetime->sub($interval);
$safeDatime = $safeDatime->sub($interval);

$this->assertEquals($datetime->getTimestamp(), $safeDatime->getTimestamp());
}
}
85 changes: 55 additions & 30 deletions lib/DateTimeImmutable.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,35 @@
use DateTimeZone;
use Safe\Exceptions\DatetimeException;

//this class is used to implement a safe version of the DatetimeImmutable class
/**
* This class is used to implement a safe version of the DatetimeImmutable class.
* While it technically overloads \DateTimeImmutable for typehint compatibility,
* it is actually used as a wrapper of \DateTimeImmutable, mostly to be able to overwrite functions like getTimestamp() while still being able to edit milliseconds via setTime().
*/
class DateTimeImmutable extends \DateTimeImmutable
{
/**
* @var \DateTimeImmutable
*/
private $innerDateTime;

public function __construct($time = 'now', $timezone = null)
{
parent::__construct();
$this->innerDateTime = new parent($time, $timezone);
}

//switch from regular datetime to safe version
private static function createFromRegular(\DateTimeImmutable $datetime): self
{
return new self($datetime->format('Y-m-d H:i:s'), $datetime->getTimezone());
$safeDatetime = new self();
$safeDatetime->innerDateTime = $datetime;
return $safeDatetime;
}

/////////////////////////////////////////////////////////////////////////////
// overload functions with false errors

public static function createFromFormat($format, $time, DateTimeZone $timezone = null): self
{
$datetime = parent::createFromFormat($format, $time, $timezone);
Expand All @@ -28,54 +48,38 @@ public static function createFromFormat($format, $time, DateTimeZone $timezone =

public function format($format): string
{
$result = parent::format($format);
/** @var string|false $result */
$result = $this->innerDateTime->format($format);
if ($result === false) {
throw DatetimeException::createFromPhpError();
}
return $result;
}

/**
* @param DateTimeInterface $datetime2 <p>The date to compare to.</p>
* @param bool $absolute [optional] <p>Should the interval be forced to be positive?</p>
* @return DateInterval
*/
public function diff($datetime2, $absolute = false): DateInterval
{
/** @var \DateInterval|false $result */
$result = parent::diff($datetime2, $absolute);
$result = $this->innerDateTime->diff($datetime2, $absolute);
if ($result === false) {
throw DatetimeException::createFromPhpError();
}
return $result;
}

/**
* @param string $modify <p>A date/time string. Valid formats are explained in
* {@link https://secure.php.net/manual/en/datetime.formats.php Date and Time Formats}.</p>
* @return DateTimeImmutable
*/
public function modify($modify): self
{
/** @var \DateTimeImmutable|false $result */
$result = parent::modify($modify);
$result = $this->innerDateTime->modify($modify);
if ($result === false) {
throw DatetimeException::createFromPhpError();
}
return self::createFromRegular($result); //we have to recreate a safe datetime because modify create a new instance of \DateTimeImmutable
}

/**
* @param int $year <p>Year of the date.</p>
* @param int $month <p>Month of the date.</p>
* @param int $day <p>Day of the date.</p>
* @return DateTimeImmutable
*
*/
public function setDate($year, $month, $day): self
{
/** @var \DateTimeImmutable|false $result */
$result = parent::setDate($year, $month, $day);
$result = $this->innerDateTime->setDate($year, $month, $day);
if ($result === false) {
throw DatetimeException::createFromPhpError();
}
Expand All @@ -85,7 +89,7 @@ public function setDate($year, $month, $day): self
public function setISODate($year, $week, $day = 1): self
{
/** @var \DateTimeImmutable|false $result */
$result = parent::setISODate($year, $week, $day);
$result = $this->innerDateTime->setISODate($year, $week, $day);
if ($result === false) {
throw DatetimeException::createFromPhpError();
}
Expand All @@ -95,7 +99,7 @@ public function setISODate($year, $week, $day = 1): self
public function setTime($hour, $minute, $second = 0, $microseconds = 0): self
{
/** @var \DateTimeImmutable|false $result */
$result = parent::setTime($hour, $minute, $second, $microseconds);
$result = $this->innerDateTime->setTime($hour, $minute, $second, $microseconds);
if ($result === false) {
throw DatetimeException::createFromPhpError();
}
Expand All @@ -105,7 +109,7 @@ public function setTime($hour, $minute, $second = 0, $microseconds = 0): self
public function setTimestamp($unixtimestamp): self
{
/** @var \DateTimeImmutable|false $result */
$result = parent::setTimestamp($unixtimestamp);
$result = $this->innerDateTime->setTimestamp($unixtimestamp);
if ($result === false) {
throw DatetimeException::createFromPhpError();
}
Expand All @@ -115,7 +119,7 @@ public function setTimestamp($unixtimestamp): self
public function setTimezone($timezone): self
{
/** @var \DateTimeImmutable|false $result */
$result = parent::setTimezone($timezone);
$result = $this->innerDateTime->setTimezone($timezone);
if ($result === false) {
throw DatetimeException::createFromPhpError();
}
Expand All @@ -125,18 +129,29 @@ public function setTimezone($timezone): self
public function sub($interval): self
{
/** @var \DateTimeImmutable|false $result */
$result = parent::sub($interval);
$result = $this->innerDateTime->sub($interval);
if ($result === false) {
throw DatetimeException::createFromPhpError();
}
return self::createFromRegular($result);
}

//theses functions are overload to actually return a safe instance, since datetimeimmutable re-instante itself
public function getOffset(): int
{
/** @var int|false $result */
$result = $this->innerDateTime->getOffset();
if ($result === false) {
throw DatetimeException::createFromPhpError();
}
return $result;
}

//////////////////////////////////////////////////////////////////////////////////////////
//overload getters to use the inner datetime immutable instead of itself

public function add($interval): self
{
return self::createFromRegular(parent::add($interval));
return self::createFromRegular($this->innerDateTime->add($interval));
}

public static function createFromMutable($dateTime): self
Expand All @@ -148,4 +163,14 @@ public static function __set_state(array $array): self
{
return self::createFromRegular(parent::__set_state($array));
}

public function getTimezone(): DateTimeZone
{
return $this->innerDateTime->getTimezone();
}

public function getTimestamp(): int
{
return $this->innerDateTime->getTimestamp();
}
}

0 comments on commit 1f8d457

Please sign in to comment.