Page Menu
Home
DevCentral
Search
Configure Global Search
Log In
Files
F3934831
D1641.id4190.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
10 KB
Referenced Files
None
Subscribers
None
D1641.id4190.diff
View Options
diff --git a/src/DateTime/Time.php b/src/DateTime/Time.php
new file mode 100644
--- /dev/null
+++ b/src/DateTime/Time.php
@@ -0,0 +1,130 @@
+<?php
+declare(strict_types=1);
+
+namespace Keruald\OmniTools\DateTime;
+
+use Keruald\OmniTools\Collections\Comparable;
+
+class Time implements Comparable {
+
+ /**
+ * @var int
+ */
+ private $hours;
+
+ /**
+ * @var int
+ */
+ private $minutes;
+
+ ///
+ /// Constants
+ ///
+
+ const MAX_HOURS = 24;
+
+ const MAX_MINUTES = 24 * 60;
+
+ ///
+ /// Constructors
+ ///
+
+ public function __construct (int $hours = 0, int $minutes = 0) {
+ $this->hours = $hours;
+ $this->minutes = $minutes;
+ }
+
+ public static function fromMinutes (int $minutes) : self {
+ if ($minutes < 0 || $minutes >= self::MAX_MINUTES) {
+ throw new \OutOfRangeException;
+ }
+
+ return (new Time)
+ ->setHours((int)($minutes / 60))
+ ->setMinutes($minutes % 60);
+ }
+
+ public function parse (string $expression) : self {
+ $range = explode(":", $expression);
+
+ if (count($range) < 2 || count($range) > 3) {
+ throw new \InvalidArgumentException;
+ }
+
+ return new self((int)$range[0], (int)$range[1]);
+ }
+
+ ///
+ /// Getters and setters
+ ///
+
+ public function getHours () : int {
+ return $this->hours;
+ }
+
+ public function setHours (int $hours) : self {
+ $this->hours = $hours;
+
+ return $this;
+ }
+
+ public function getMinutes () : int {
+ return $this->minutes;
+ }
+
+ public function setMinutes (int $minutes) : self {
+ $this->minutes = $minutes;
+
+ return $this;
+ }
+
+ ///
+ /// Helper methods
+ ///
+
+ public function addMinutes (int $minutes) : self {
+ $totalMinutes = $this->toMinutes() + $minutes;
+
+ if ($totalMinutes < 0 || $totalMinutes >= self::MAX_MINUTES) {
+ throw new \OutOfRangeException;
+ }
+
+ $this->setHours((int)($totalMinutes / 60))
+ ->setMinutes($totalMinutes % 60);
+
+ return $this;
+ }
+
+ public function addHours (int $hours) : self {
+ $totalHours = $this->hours + $hours;
+
+ if ($totalHours >= self::MAX_HOURS) {
+ throw new \OutOfRangeException;
+ }
+
+ $this->setHours($totalHours);
+
+ return $this;
+ }
+
+ public function toMinutes () : int {
+ return $this->hours * 60 + $this->minutes;
+ }
+
+ public function __toString () : string {
+ return sprintf("%02d:%02d", $this->hours, $this->minutes);
+ }
+
+ ///
+ /// Comparable
+ ///
+
+ public function compareTo (object $other) : int {
+ if (!$other instanceof Time) {
+ throw new \InvalidArgumentException;
+ }
+
+ return $this->toMinutes() <=> $other->toMinutes();
+ }
+
+}
diff --git a/src/DateTime/TimeRange.php b/src/DateTime/TimeRange.php
new file mode 100644
--- /dev/null
+++ b/src/DateTime/TimeRange.php
@@ -0,0 +1,91 @@
+<?php
+
+namespace Keruald\OmniTools\DateTime;
+
+class TimeRange {
+
+ /**
+ * @var Time
+ */
+ private $start;
+
+ /**
+ * @var Time
+ */
+ private $end;
+
+ ///
+ /// Constructors
+ ///
+
+ public function __construct (Time $start, Time $end) {
+ $this->start = $start;
+ $this->end = $end;
+
+ $this->normalize();
+ }
+
+ public function fromDuration (Time $start, int $hoursToAdd = 0, int $minutesToAdd = 0) {
+ $end = clone $start;
+ $end
+ ->addHours($hoursToAdd)
+ ->addMinutes($minutesToAdd);
+
+ return new self($start, $end);
+ }
+
+ public function parse (string $expression) : self {
+ $range = explode("-", $expression);
+
+ if (count($range) !== 2) {
+ throw new \InvalidArgumentException;
+ }
+
+ return new self(Time::Parse($range[0]), Time::Parse($range[1]));
+ }
+
+ ///
+ /// Getters and setters
+ ///
+
+ public function getStart () : Time {
+ return $this->start;
+ }
+
+ public function setStart (Time $start) : self {
+ $this->start = $start;
+
+ return $this->normalize();
+ }
+
+ public function getEnd () : Time {
+ return $this->end;
+ }
+
+ public function setEnd (Time $end) : self {
+ $this->end = $end;
+
+ return $this->normalize();
+ }
+
+ ///
+ /// Helper functions
+ ///
+
+ public function normalize () : self {
+ if ($this->start->compareTo($this->end) > 0) {
+ $swap = $this->end;
+ $this->end = $this->start;
+ $this->start = $swap;
+ }
+
+ return $this;
+ }
+
+ public function overlapsWith (TimeRange $other) : bool {
+ return $this->getEnd()->compareTo($other->getStart()) == 1
+ &&
+ $other->getEnd()->compareTo($this->getStart()) == 1;
+ }
+
+}
diff --git a/tests/DateTime/TimeRangeTest.php b/tests/DateTime/TimeRangeTest.php
new file mode 100644
--- /dev/null
+++ b/tests/DateTime/TimeRangeTest.php
@@ -0,0 +1,93 @@
+<?php
+
+namespace Keruald\OmniTools\Tests\DateTime;
+
+use Keruald\OmniTools\DateTime\Time;
+use Keruald\OmniTools\DateTime\TimeRange;
+use PHPUnit\Framework\TestCase;
+
+class TimeRangeTest extends TestCase {
+
+ /**
+ * @var Time
+ */
+ private $start;
+
+ /**
+ * @var Time
+ */
+ private $end;
+
+ /**
+ * @var TimeRange;
+ */
+ private $range;
+
+ protected function setUp () {
+ $this->start = new Time(8, 0);
+ $this->end = new Time(11, 36);
+
+ $this->range = new TimeRange($this->start, $this->end);
+ }
+
+ public function testGetStart () {
+ $this->assertEquals($this->start, $this->range->getStart());
+ }
+
+ public function testGetEnd () {
+ $this->assertEquals($this->end, $this->range->getEnd());
+ }
+
+ public function testNormalize () {
+ $range = new TimeRange($this->end, $this->start);
+ $this->assertEquals($this->start, $range->getStart());
+ }
+
+ public function testParse () {
+ $range = TimeRange::Parse("08:00-11:36");
+ $this->assertEquals($this->start, $range->getStart());
+ $this->assertEquals($this->end, $range->getEnd());
+ }
+
+ /**
+ * @dataProvider provideOverlappingRanges
+ */
+ public function testOverlap ($range1, $range2, $isOverlap) {
+ $this->assertEquals(
+ $isOverlap,
+ TimeRange::parse($range1)->overlapsWith(TimeRange::parse($range2))
+ );
+ }
+
+ ///
+ /// Data providers
+ ///
+
+ public function provideOverlappingRanges() : iterable {
+
+ /**
+ * Two time ranges are overlapping if, THEIR LIMITS EXCLUDED,
+ * they don't have a common element.
+ *
+ * ]start1, end1[ ∩ ]start2, end2[ ≠ ø
+ */
+
+ yield ["09:00-11:00", "09:00-11:00", true];
+ yield ["09:00-11:00", "10:00-12:00", true];
+ yield ["10:00-12:00", "09:00-11:00", true];
+ yield ["09:00-12:00", "10:00-11:00", true];
+ yield ["10:00-11:00", "09:00-12:00", true];
+ yield ["09:00-10:00", "11:00-12:00", false];
+ yield ["11:00-12:00", "09:00-10:00", false];
+ yield ["09:00-10:00", "10:00-11:00", false];
+ yield ["10:00-11:00", "09:00-10:00", false];
+ yield ["10:00-10:00", "09:00-11:00", true];
+ yield ["09:00-11:00", "10:00-10:00", true];
+ yield ["09:00-09:00", "09:00-10:00", false];
+ yield ["10:00-10:00", "09:00-10:00", false];
+ yield ["09:00-10:00", "09:00-09:00", false];
+ yield ["09:00-10:00", "10:00-10:00", false];
+ yield ["09:00-09:00", "09:00-09:00", false];
+ }
+
+}
diff --git a/tests/DateTime/TimeTest.php b/tests/DateTime/TimeTest.php
new file mode 100644
--- /dev/null
+++ b/tests/DateTime/TimeTest.php
@@ -0,0 +1,86 @@
+<?php
+
+namespace Keruald\OmniTools\Tests\DateTime;
+
+use Keruald\OmniTools\DateTime\Time;
+use PHPUnit\Framework\TestCase;
+
+class TimeTest extends TestCase {
+
+ /**
+ * @var Time
+ */
+ private $time;
+
+ protected function setUp () : void {
+ $this->time = new Time(8, 6);
+ }
+
+ ///
+ /// Tests
+ ///
+
+ public function testGetHours () : void {
+ $this->assertSame(8, $this->time->getHours());
+ }
+
+ public function testGetMinutes () : void {
+ $this->assertSame(6, $this->time->getMinutes());
+ }
+
+ public function testSetHours () : void {
+ $this->time->setHours(9);
+ $this->assertSame(9, $this->time->getHours());
+ }
+
+ public function testSetMinutes () : void {
+ $this->time->setMinutes(7);
+ $this->assertSame(7, $this->time->getMinutes());
+ }
+
+ public function testAddMinutes () : void {
+ $this->time->addMinutes(7);
+ $this->assertSame(13, $this->time->getMinutes());
+ }
+
+ public function testAddMinutesWithHourOverlap () : void {
+ $this->time->addMinutes(55);
+ $this->assertSame(9, $this->time->getHours());
+ $this->assertSame(1, $this->time->getMinutes());
+ }
+
+ public function testAddMinutesWithDayOverlap () : void {
+ $this->expectException(\OutOfRangeException::class);
+ $this->time->addMinutes(2000);
+ }
+
+ public function testAddHours () : void {
+ $this->time->addHours(5);
+ $this->assertSame(13, $this->time->getHours());
+ }
+
+ public function testAddHoursWithDayOverlap () : void {
+ $this->expectException(\OutOfRangeException::class);
+ $this->time->addHours(16);
+ }
+
+ public function testToString () : void {
+ $this->assertSame("08:06", (string)$this->time);
+ }
+
+ public function testFromMinutes () : void {
+ $this->assertEquals($this->time, Time::fromMinutes(486));
+ }
+
+ public function testCompareTo () : void {
+ $this->assertEquals(0, $this->time->compareTo(new Time(8, 6)));
+ $this->assertEquals(-1, $this->time->compareTo(new Time(9, 0)));
+ $this->assertEquals(1, $this->time->compareTo(new Time(3, 0)));
+ }
+
+ public function testParse () {
+ $this->assertEquals($this->time, Time::Parse("08:06"));
+ $this->assertEquals($this->time, Time::Parse("08:06:00"));
+ }
+
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Tue, Dec 24, 06:28 (16 h, 35 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2313713
Default Alt Text
D1641.id4190.diff (10 KB)
Attached To
Mode
D1641: Allow to manipulate time ranges and check overlaps
Attached
Detach File
Event Timeline
Log In to Comment