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

Time travel #1

Closed
71 changes: 67 additions & 4 deletions Carbon/Carbon.php
Expand Up @@ -26,6 +26,8 @@ class Carbon extends \DateTime
const MINUTES_PER_HOUR = 60;
const SECONDS_PER_MINUTE = 60;

protected static $timeTravelOffsets = array();

protected static function safeCreateDateTimeZone($object)
{
if ($object instanceof \DateTimeZone) {
Expand All @@ -43,10 +45,29 @@ protected static function safeCreateDateTimeZone($object)

public function __construct($time = null, $tz = null)
{
$datetime = count(static::$timeTravelOffsets) ? "now" : $time;
if ($tz !== null) {
parent::__construct($time, self::safeCreateDateTimeZone($tz));
parent::__construct($datetime, self::safeCreateDateTimeZone($tz));
} else {
parent::__construct($time);
parent::__construct($datetime);
}

if (!count(static::$timeTravelOffsets)) {
return;
}

foreach (static::$timeTravelOffsets as $offset) {
$this->addSeconds($offset);
}

$this->modify($time);

/**
* Hack for the "first day of january 2008"
*/
$dt = new \DateTime($time);
if ("00:00:00" === $dt->format("H:i:s")) {
$this->setTime(0, 0, 0);
}
}

Expand All @@ -58,11 +79,12 @@ public static function instance(\DateTime $dt)
public static function now($tz = null)
{
if ($tz !== null) {
return new self(null, self::safeCreateDateTimeZone($tz));
return new self("now", self::safeCreateDateTimeZone($tz));
} else {
return new self();
return new self("now");
}
}

public static function create($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $tz = null)
{
$year = ($year === null) ? date('Y') : $year;
Expand Down Expand Up @@ -112,6 +134,47 @@ public static function createFromTimestampUTC($timestamp)
return new self('@'.$timestamp);
}

public static function timeTravelTo($time, \Closure $callback = null)
{
$now = static::now();
if (!($time instanceof \DateTime)) {
$time = static::now()->modify($time);
}
static::$timeTravelOffsets[] = $time->getTimestamp() - $now->getTimestamp();

if (null === $callback) {
return;
}

try {
$return = $callback();
static::restorePreviousTime();
return $return;
} catch (\Exception $e) {
static::restorePreviousTime();
throw $e;
}
}

public static function backToThePresent()
{
static::$timeTravelOffsets = array();
}

public static function nowWithoutTimeTravel($tz = null)
{
$offsets = static::$timeTravelOffsets;
static::$timeTravelOffsets = array();
$now = static::now($tz);
static::$timeTravelOffsets = $offsets;
return $now;
}

public static function restorePreviousTime()
{
array_pop(static::$timeTravelOffsets);
}

public function copy()
{
return self::instance($this);
Expand Down
154 changes: 154 additions & 0 deletions Carbon/Tests/TimeTravelTest.php
@@ -0,0 +1,154 @@
<?php

/*
* This file is part of the Carbon package.
*
* (c) Brian Nesbitt <brian@nesbot.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Carbon\Tests;

use Carbon\Carbon;

class TimeTravelTest extends TestFixture
{
public function tearDown()
{
Carbon::backToThePresent();
parent::tearDown();
}

public function testTimeTravel()
{
Carbon::timeTravelTo("2 years ago");
$this->assertSame(date("Y") - 2, Carbon::now()->year);
}

public function testTimeTravelWithAbsolute()
{
Carbon::timeTravelTo("1999-12-31");
$this->assertSame(1999, Carbon::now()->year);
}

public function testConsecutiveTimeTravellingWithRelativeToTimeTravelTime()
{
Carbon::timeTravelTo("2 years ago");
Carbon::timeTravelTo("+5 years");
$this->assertSame(date("Y") + 3, Carbon::now()->year);
}

public function testTimeTravelWithConstructorAndNow()
{
Carbon::timeTravelTo("2 years ago");
$c = new Carbon("now");
$this->assertSame(date("Y") - 2, $c->year);
}

public function testTimeTravelWithConstructorAndAbsolute()
{
Carbon::timeTravelTo("2 years ago");
$c = new Carbon("1999/12/12");
$this->assertSame(1999, $c->year);
}

public function testTimeTravelWithConstructorAndRelative()
{
Carbon::timeTravelTo("2 years ago");
$c = new Carbon("2 years ago");
$this->assertSame(date("Y") - 4, $c->year);
}

public function testTimeTravelWithConstructorAndComplex()
{
Carbon::timeTravelTo("2 years ago");
$c = new Carbon("first day of january 2008");
$this->assertCarbon($c, 2008, 1, 1, 0, 0, 0);
}

public function testTimeTravelWithConstructorAndComplexToday()
{
Carbon::timeTravelTo("2 years ago");
$c = new Carbon("today");
$this->assertCarbon($c, $c->year, $c->month, $c->day, 0, 0, 0);
}

public function testTimeTravelWithConstructorAndComplexNoon()
{
Carbon::timeTravelTo("2 years ago");
$c = new Carbon("noon");
$this->assertCarbon($c, $c->year, $c->month, $c->day, 12, 0, 0);
}

public function testRestorePreviousTime()
{
Carbon::timeTravelTo("2 years ago");
Carbon::timeTravelTo("+5 years");
Carbon::timeTravelTo("+7 years");
Carbon::restorePreviousTime();
Carbon::restorePreviousTime();
$this->assertSame(date("Y") - 2, Carbon::now()->year);
}

public function testTimeTravelToWithClosure()
{
$that = $this;
Carbon::timeTravelTo("1999-10-10", function() use ($that) {
$that->assertSame(1999, Carbon::now()->year);
});
$this->assertSame(intval(date("Y")), Carbon::now()->year);
}

public function testTimeTravelToWithClosureGetsReturnValue()
{
$ret = Carbon::timeTravelTo("1999-10-10", function() {
return 123;
});
$this->assertSame(123, $ret);
}

public function testTimeTravelToWithClosureRestoresDespiteException()
{
try {
Carbon::timeTravelTo("1999-10-10", function() {
throw new \Exception("waaa");
});
} catch (\Exception $e) {
// noop
}
$this->assertSame(intval(date("Y")), Carbon::now()->year);
}

public function testBackToThePresent()
{
Carbon::timeTravelTo("2 years ago");
Carbon::backToThePresent();
$this->assertSame(intval(date("Y")), Carbon::now()->year);
}

public function testBackToThePresentAfterNoJumps()
{
Carbon::backToThePresent();
$this->assertSame(intval(date("Y")), Carbon::now()->year);
}

public function testBackToThePresentAfterSeveralJumps()
{
Carbon::timeTravelTo("2 years ago");
Carbon::timeTravelTo("+5 years");
Carbon::timeTravelTo("+7 years");
Carbon::backToThePresent();
$this->assertSame(intval(date("Y")), Carbon::now()->year);
}

public function testNowWithTimeTravel()
{
Carbon::timeTravelTo("2 years ago");
Carbon::timeTravelTo("+5 years");
Carbon::timeTravelTo("+7 years");
$this->assertSame(intval(date("Y")), Carbon::nowWithoutTimeTravel()->year);
}

}
50 changes: 49 additions & 1 deletion readme.md
Expand Up @@ -56,6 +56,7 @@ $daysSinceEpoch = Carbon::createFromTimeStamp(0)->diffInDays();
* [Difference](#api-difference)
* [Difference for Humans](#api-humandiff)
* [Constants](#api-constants)
* [Time Travel](#time-travel)
* [About](#about)
* [Contributing](#about-contributing)
* [Author](#about-author)
Expand Down Expand Up @@ -544,6 +545,53 @@ if ($dt->dayOfWeek === Carbon::SATURDAY) {
}
```

<a name="time-travel"/>
### Time Travel

There's an experimental time travelling feature for testing purposes, ala [ruby's delorean](https://github.com/bebanjo/delorean)

```php
class SeasonalProduct
{
protected $price;

public function __construct($price)
{
$this->price = $price;
}

public function getPrice() {
$multiplier = 1;
if (Carbon::now()->month == 12) {
$multiplier = 2;
}

return $this->price * $multiplier;
}
}

$product = new SeasonalProduct(100);
Carbon::timeTravelTo("november");
echo $product->getPrice(); // 100
Carbon::timeTravelTo("december");
echo $product->getPrice(); // 200
Carbon::restorePreviousTime();
echo $product->getPrice(); // 100

// reset
Carbon::timeTravelTo("december");
Carbon::backToThePresent();
echo $product->getPrice(); // 100

// with a callback
$callback = Carbon::timeTravelTo("december", function() use ($product) {
return $product->getPrice();
});
echo $callback; // 200
echo $product->getPrice(); // 100

```

<a name="about"/>
## About

Expand Down Expand Up @@ -612,4 +660,4 @@ Carbon is licensed under the MIT License - see the `LICENSE` file for details
<a name="about-whyname"/>
### Why the name Carbon?

Read about [Carbon Dating](http://en.wikipedia.org/wiki/Radiocarbon_dating)
Read about [Carbon Dating](http://en.wikipedia.org/wiki/Radiocarbon_dating)