diff --git a/phpunit.xml b/phpunit.xml
index 4d37631..78aa5f8 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -3,5 +3,8 @@
tests/Models
+
+ tests/TimeKeeperTest.php
+
diff --git a/src/TimeKeeper.php b/src/TimeKeeper.php
new file mode 100644
index 0000000..cc4a1e8
--- /dev/null
+++ b/src/TimeKeeper.php
@@ -0,0 +1,105 @@
+adjustedEpoch = $gameEpochCopy->add(
+ $this->interval(0, 0, 0, 0, $gameOffsetSeconds)
+ );
+ $this->theBeginning = new \DateTime("0000-01-01 UTC");
+
+ $this->secondsPerGameDay = (float) $this->secondsPerDay / $gameDaysPerDay;
+ $this->secondsPerGameYear = $this->secondsPerGameDay * 365;
+ $this->secondsPerGameHour = $this->secondsPerGameDay / 24;
+ $this->secondsPerGameMinute = $this->secondsPerGameHour / 60;
+ $this->secondsPerGameSecond = $this->secondsPerGameMinute / 60;
+ }
+
+ public function isNewDay(DateTime $lastInteractionTime): bool {
+ if ($lastInteractionTime == null) {
+ return true;
+ }
+ $t1 = $this->gameTime();
+ $t2 = $this->convertToGameTime($lastInteractionTime);
+ $d1 = $t1->format("Y-m-d");
+ $d2 = $t2->format("Y-m-d");
+
+ return $d1 != $d2;
+ }
+
+ public function gameTime(): \DateTime {
+ return $this->convertToGameTime(new \DateTime());
+ }
+
+ public function convertFromGameTime(
+ \DateTime $time
+ ): \DateTime {
+ // Game dates are in the distant past, better not use getTimestamp().
+ $i = $this->theBeginning->diff($time);
+
+ $seconds = 0;
+ $seconds += $i->days * $this->secondsPerGameDay;
+ $seconds += $i->h * $this->secondsPerGameHour;
+ $seconds += $i->i * $this->secondsPerGameMinute;
+ $seconds += $i->s * $this->secondsPerGameSecond;
+
+ $ret = clone($this->adjustedEpoch);
+ return $ret->add($this->interval(0, 0, 0, 0, (int) $seconds));
+ }
+
+ public function convertToGameTime(
+ \DateTime $time
+ ): \DateTime {
+ $timeUnix = $time->getTimestamp();
+ $epochUnix = $this->adjustedEpoch->getTimestamp();
+
+ $interval = $timeUnix - $epochUnix;
+
+ $years = (int) ($interval / $this->secondsPerGameYear);
+ $interval -= $years * $this->secondsPerGameYear;
+
+ $days = (int) ($interval / $this->secondsPerGameDay);
+ $interval -= $days * $this->secondsPerGameDay;
+
+ $hours = (int) ($interval / $this->secondsPerGameHour);
+ $interval -= $hours * $this->secondsPerGameHour;
+
+ $minutes = (int) ($interval / $this->secondsPerGameMinute);
+ $interval -= $minutes * $this->secondsPerGameMinute;
+
+ $seconds = (int) ($interval / $this->secondsPerGameSecond);
+ $interval -= $seconds * $this->secondsPerGameSecond;
+
+ $ret = clone($this->theBeginning);
+ return $ret->add(
+ $this->interval($years, $days, $hours, $minutes, $seconds)
+ );
+ }
+
+ private function interval(
+ int $years,
+ int $days,
+ int $hours,
+ int $minutes,
+ int $seconds
+ ): \DateInterval {
+ return new \DateInterval(
+ 'P'.$years.'Y'.$days.'DT'.$hours.'H'.$minutes.'M'.$seconds.'S'
+ );
+ }
+}
diff --git a/tests/TimeKeeperTest.php b/tests/TimeKeeperTest.php
new file mode 100644
index 0000000..0ff20c0
--- /dev/null
+++ b/tests/TimeKeeperTest.php
@@ -0,0 +1,152 @@
+gameEpoch = new \DateTime('2015-07-27 00:00:00 PDT');;
+ $this->gameOffsetSeconds = 0;
+ $this->gameDaysPerDay = 2;
+ }
+
+ public function testConvertToBasicConversion() {
+ $this->gameDaysPerDay = 1;
+ $keeper = new TimeKeeper($this->gameEpoch, $this->gameOffsetSeconds, $this->gameDaysPerDay);
+
+ $date = new \DateTime('2015-07-27 23:59:59 PDT');
+ $converted = $keeper->convertToGameTime($date);
+ $this->assertEquals('0000-01-01 23:59:59', $converted->format('Y-m-d H:i:s'));
+
+ $date = new \DateTime('2015-07-27 12:00:00 PDT');
+ $converted = $keeper->convertToGameTime($date);
+ $this->assertEquals('0000-01-01 12:00:00', $converted->format('Y-m-d H:i:s'));
+ }
+
+ public function testConvertToRespectsGameDaysPerDayUpperBound() {
+ $date = new \DateTime('2015-07-27 05:59:59 PDT');
+
+ $this->gameDaysPerDay = 4;
+ $keeper = new TimeKeeper($this->gameEpoch, $this->gameOffsetSeconds, $this->gameDaysPerDay);
+
+ $converted = $keeper->convertToGameTime($date);
+ $this->assertEquals('0000-01-01', $converted->format('Y-m-d'));
+ }
+
+ public function testConvertToRespectsGameDaysPerDayNextDay() {
+ $date = new \DateTime('2015-07-27 06:00:00 PDT');
+
+ $this->gameDaysPerDay = 4;
+ $keeper = new TimeKeeper($this->gameEpoch, $this->gameOffsetSeconds, $this->gameDaysPerDay);
+
+ $converted = $keeper->convertToGameTime($date);
+ $this->assertEquals('0000-01-02', $converted->format('Y-m-d'));
+ }
+
+ public function testConvertToRespectsGameDaysPerDayNextDayUpperBound() {
+ $date = new \DateTime('2015-07-27 11:59:59 PDT');
+
+ $this->gameDaysPerDay = 4;
+ $keeper = new TimeKeeper($this->gameEpoch, $this->gameOffsetSeconds, $this->gameDaysPerDay);
+
+ $converted = $keeper->convertToGameTime($date);
+ $this->assertEquals('0000-01-02', $converted->format('Y-m-d'));
+ }
+
+ public function testDetectsNewDay() {
+ }
+
+ public function testConvertToRespectsGameOffset() {
+ $date = new \DateTime('2015-07-27 01:01:15 PDT');
+
+ $this->gameOffsetSeconds = 60*60;
+ $this->gameDaysPerDay = 1;
+ $keeper = new TimeKeeper($this->gameEpoch, $this->gameOffsetSeconds, $this->gameDaysPerDay);
+
+ $converted = $keeper->convertToGameTime($date);
+ $this->assertEquals('0000-01-01 00:01:15', $converted->format('Y-m-d H:i:s'));
+ }
+
+ public function testConvertToRespectsGameOffsetUpperBound() {
+ $date = new \DateTime('2015-07-28 00:59:59 PDT');
+
+ $this->gameOffsetSeconds = 60*60;
+ $this->gameDaysPerDay = 1;
+ $keeper = new TimeKeeper($this->gameEpoch, $this->gameOffsetSeconds, $this->gameDaysPerDay);
+
+ $converted = $keeper->convertToGameTime($date);
+ $this->assertEquals('0000-01-01 23:59:59', $converted->format('Y-m-d H:i:s'));
+ }
+
+ public function testConvertToRespectsGameOffsetNextDay() {
+ $date = new \DateTime('2015-07-28 01:00:00 PDT');
+
+ $this->gameOffsetSeconds = 60*60;
+ $this->gameDaysPerDay = 1;
+ $keeper = new TimeKeeper($this->gameEpoch, $this->gameOffsetSeconds, $this->gameDaysPerDay);
+
+ $converted = $keeper->convertToGameTime($date);
+ $this->assertEquals('0000-01-02', $converted->format('Y-m-d'));
+ }
+
+ public function testConvertToRespectsGameOffsetNextDayUpperBound() {
+ $date = new \DateTime('2015-07-29 00:59:59 PDT');
+
+ $this->gameOffsetSeconds = 60*60;
+ $this->gameDaysPerDay = 1;
+ $keeper = new TimeKeeper($this->gameEpoch, $this->gameOffsetSeconds, $this->gameDaysPerDay);
+
+ $converted = $keeper->convertToGameTime($date);
+ $this->assertEquals('0000-01-02', $converted->format('Y-m-d'));
+ }
+
+ public function testConvertFromBasicConversion() {
+ $date = new \DateTime('0000-01-02 00:00:00 UTC');
+
+ $this->gameDaysPerDay = 1;
+ $keeper = new TimeKeeper($this->gameEpoch, $this->gameOffsetSeconds, $this->gameDaysPerDay);
+
+ $converted = $keeper->convertFromGameTime($date);
+ $this->assertEquals("2015-07-28", $converted->format('Y-m-d'));
+ }
+
+ public function testConvertFromRespectsGameOffsetNextDay() {
+ $epoch = new \DateTime('2015-07-27 00:00:00 PDT');
+ $date = new \DateTime('0000-01-02 23:59:59 UTC');
+
+ $this->gameEpoch = $epoch;
+ $this->gameOffsetSeconds = 60*60;
+ $this->gameDaysPerDay = 1;
+ $keeper = new TimeKeeper($this->gameEpoch, $this->gameOffsetSeconds, $this->gameDaysPerDay);
+
+ $converted = $keeper->convertFromGameTime($date);
+ $this->assertEquals("2015-07-29 00:59:59", $converted->format('Y-m-d H:i:s'));
+ }
+
+ public function testConvertFromRespectsGameDaysPerDayNextDay() {
+ $epoch = new \DateTime('2015-07-27 00:00:00 PDT');
+ $date = new \DateTime('0000-01-02 23:59:59 UTC');
+
+ $this->gameEpoch = $epoch;
+ $this->gameDaysPerDay = 4;
+ $keeper = new TimeKeeper($this->gameEpoch, $this->gameOffsetSeconds, $this->gameDaysPerDay);
+
+ $converted = $keeper->convertFromGameTime($date);
+ $this->assertEquals("2015-07-27 11:59:59", $converted->format('Y-m-d H:i:s'));
+ }
+
+ public function testGameTimeSanity() {
+ $keeper = new TimeKeeper($this->gameEpoch, $this->gameOffsetSeconds, $this->gameDaysPerDay);
+ $this->assertNotNull($keeper->gameTime());
+ }
+}