diff --git a/.gitignore b/.gitignore
index c7eea7f..0c4e8bf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,9 @@
### MacOs ###
.DS_Store
+### PhpStorm ###
+/.idea
+
### Composer ###
composer.lock
composer.phar
diff --git a/composer.json b/composer.json
index 2a3bd38..b399ba6 100644
--- a/composer.json
+++ b/composer.json
@@ -3,7 +3,8 @@
"files": [ "src/classnames/function.php" ],
"psr-4": {
"ClassNames\\": "src/classnames",
- "MergeCoverage\\": "src/merge-coverage"
+ "MergeCoverage\\": "src/merge-coverage",
+ "TimeOfDay\\": "src/time-of-day"
}
},
"require-dev": {
diff --git a/phpunit.xml b/phpunit.xml
index 8e37f4a..eccb588 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -13,6 +13,9 @@
src/classnames/Tests
+
+ src/time-of-day/Tests
+
diff --git a/src/time-of-day/AbstractTime.php b/src/time-of-day/AbstractTime.php
new file mode 100644
index 0000000..ed4ca54
--- /dev/null
+++ b/src/time-of-day/AbstractTime.php
@@ -0,0 +1,56 @@
+ $hours || $hours > 23) => throw new \ValueError('Hour is not valid'),
+ (0 > $minutes || $minutes > 59) => throw new \ValueError('Minute is not valid'),
+ (0 > $seconds || $seconds > 59) => throw new \ValueError('Second is not valid'),
+ (0 > $milliseconds || $milliseconds > 999) => throw new \ValueError('Millisecond is not valid'),
+ default => 'Everything is ok'
+ };
+
+ return new static(
+ $hours * self::MILLISECONDS_IN_A_HOUR
+ + $minutes * self::MILLISECONDS_IN_A_MINUTE
+ + $seconds * 1000
+ + $milliseconds
+ );
+ }
+
+ protected function hours(): int
+ {
+ return intdiv($this->counter, self::MILLISECONDS_IN_A_HOUR);
+ }
+
+ protected function minutes(): int
+ {
+ $rest = $this->counter % self::MILLISECONDS_IN_A_HOUR;
+ return intdiv($rest, self::MILLISECONDS_IN_A_MINUTE);
+ }
+
+ protected function seconds(): int
+ {
+ $rest = $this->counter % self::MILLISECONDS_IN_A_MINUTE;
+ return intdiv($rest, 1000);
+ }
+
+ protected function milliseconds(): int
+ {
+ return $this->counter % 1000;
+ }
+}
\ No newline at end of file
diff --git a/src/time-of-day/Duration.php b/src/time-of-day/Duration.php
new file mode 100644
index 0000000..840267e
--- /dev/null
+++ b/src/time-of-day/Duration.php
@@ -0,0 +1,29 @@
+ $counter) {
+ throw new \ValueError('Milliseconds should be a positive integer');
+ }
+ $this->counter = $counter;
+ }
+
+ public function equals(Duration $other): bool
+ {
+ return $this->counter === $other->counter;
+ }
+
+ public function isGreater(Duration $other): bool
+ {
+ return $this->counter > $other->counter;
+ }
+
+ public function isLess(Duration $other): bool
+ {
+ return $this->counter < $other->counter;
+ }
+}
\ No newline at end of file
diff --git a/src/time-of-day/Tests/DurationTest.php b/src/time-of-day/Tests/DurationTest.php
new file mode 100644
index 0000000..bad994a
--- /dev/null
+++ b/src/time-of-day/Tests/DurationTest.php
@@ -0,0 +1,93 @@
+isGreater($bar));
+ self::assertTrue($bar->isLess($foo));
+ }
+
+ #[Test]
+ public function equalsComparison(): void
+ {
+ $foo = Duration::fromParts(12, 34 , 0);
+ $bar = Duration::fromParts(12, 34);
+
+ self::assertTrue($foo->equals($bar));
+ }
+
+ #[Test]
+ public function equalsComparisonBetweenChildrenObjects(): void
+ {
+ $foo = Duration::fromParts(12, 34 , 0);
+ $bar = TimeOfDay::fromParts(12, 34);
+
+ self::expectException(\TypeError::class);
+ self::assertTrue($foo->equals($bar));
+ }
+}
\ No newline at end of file
diff --git a/src/time-of-day/Tests/TimeOfDayTest.php b/src/time-of-day/Tests/TimeOfDayTest.php
new file mode 100644
index 0000000..d252933
--- /dev/null
+++ b/src/time-of-day/Tests/TimeOfDayTest.php
@@ -0,0 +1,140 @@
+format('H:i:s'), $result->format('H:i:s'));
+ }
+
+ #[Test]
+ public function plop(): void
+ {
+ $time = new TimeOfDay(45296000);
+ $result = $time->onDay(new \DateTimeImmutable('2000-01-01'));
+
+ $expected = new \DateTimeImmutable('2000-01-01 12:34:56');
+
+ self::assertEquals($expected, $result);
+ }
+
+ #[Test]
+ public function negativeMilliseconds(): void
+ {
+ $result = new TimeOfDay(-45296000);
+ self::assertEquals(new TimeOfDay(41104000), $result);
+
+ $result = new TimeOfDay(-131696000);
+ self::assertEquals(new TimeOfDay(41104000), $result);
+ }
+
+ #[Test]
+ public function comparison(): void
+ {
+ $foo = TimeOfDay::fromParts(12, 34 , 56);
+ $bar = TimeOfDay::fromParts(12);
+
+ self::assertTrue($foo->isAfter($bar));
+ self::assertTrue($bar->isBefore($foo));
+ }
+
+ #[Test]
+ public function equalsComparison(): void
+ {
+ $foo = TimeOfDay::fromParts(12, 34 , 0);
+ $bar = TimeOfDay::fromParts(12, 34);
+
+ self::assertTrue($foo->equals($bar));
+ }
+
+ #[Test]
+ public function fromParts(): void
+ {
+ $result = TimeOfDay::fromParts(12);
+ $expected = new TimeOfDay(12 * 60 * 60 * 1000);
+ self::assertEquals($expected, $result);
+
+ $result = TimeOfDay::fromParts(12,34);
+ $expected = new TimeOfDay(12 * 60 * 60 * 1000 + 34 * 60 * 1000);
+ self::assertEquals($expected, $result);
+
+ $result = TimeOfDay::fromParts(12, 34 , 56 );
+ $expected = new TimeOfDay(12 * 60 * 60 * 1000 + 34 * 60 * 1000 + 56 * 1000);
+ self::assertEquals($expected, $result);
+
+ $result = TimeOfDay::fromParts(0);
+ $expected = new TimeOfDay(0);
+ self::assertEquals($expected, $result);
+ }
+
+ #[Test]
+ public function hourNotValidTooBig(): void
+ {
+ self::expectException(\ValueError::class);
+ self::expectExceptionMessage('Hour is not valid');
+ TimeOfDay::fromParts(24);
+ }
+
+ #[Test]
+ public function hourNotValidTooSmall(): void
+ {
+ self::expectException(\ValueError::class);
+ self::expectExceptionMessage('Hour is not valid');
+ TimeOfDay::fromParts(-3);
+ }
+
+ #[Test]
+ public function minuteNotValid(): void
+ {
+ self::expectException(\ValueError::class);
+ self::expectExceptionMessage('Minute is not valid');
+ TimeOfDay::fromParts(12, 60);
+ }
+
+ #[Test]
+ public function secondNotValid(): void
+ {
+ self::expectException(\ValueError::class);
+ self::expectExceptionMessage('Second is not valid');
+ TimeOfDay::fromParts(12, 34 , 60);
+ }
+
+ #[Test]
+ public function passedSince(): void
+ {
+ $time = new TimeOfDay(45296000);
+ $duration = $time->passedSince(new TimeOfDay(44296000));
+
+ self::assertEquals(new Duration(1000000), $duration);
+ }
+
+ #[Test]
+ public function was(): void
+ {
+ $time = new TimeOfDay(45296000);
+ $was = $time->was(new Duration(1000000));
+
+ self::assertEquals(new TimeOfDay(44296000), $was);
+ }
+
+ #[Test]
+ public function willBe(): void
+ {
+ $time = new TimeOfDay(45296000);
+ $was = $time->willBe(new Duration(1000000));
+
+ self::assertEquals(new TimeOfDay(46296000), $was);
+ }
+}
diff --git a/src/time-of-day/TimeOfDay.php b/src/time-of-day/TimeOfDay.php
new file mode 100644
index 0000000..3000365
--- /dev/null
+++ b/src/time-of-day/TimeOfDay.php
@@ -0,0 +1,57 @@
+counter = $rest + (0 > $rest ? self::MILLISECONDS_IN_A_DAY : 0);
+ }
+
+ public function passedSince(TimeOfDay $other)
+ {
+ return new Duration($this->counter - $other->counter);
+ }
+
+ public function was(Duration $duration): TimeOfDay
+ {
+ return new self($this->counter - $duration->counter);
+ }
+
+ public function willBe(Duration $duration): TimeOfDay
+ {
+ return new self($this->counter + $duration->counter);
+ }
+
+ public function equals(TimeOfDay $other): bool
+ {
+ return $this->counter === $other->counter;
+ }
+
+ public function isBefore(TimeOfDay $other): bool
+ {
+ return $this->counter < $other->counter;
+ }
+
+ public function isAfter(TimeOfDay $other): bool
+ {
+ return $this->counter > $other->counter;
+ }
+
+ public function format(string $format): string
+ {
+ return $this->onDay(new \DateTimeImmutable())->format($format);
+ }
+
+ public function onDay(\DateTimeImmutable $day): \DateTimeImmutable
+ {
+ return ($day)->setTime(
+ $this->hours(),
+ $this->minutes(),
+ $this->seconds(),
+ $this->milliseconds(),
+ );
+ }
+}
\ No newline at end of file