Page Menu
Home
DevCentral
Search
Configure Global Search
Log In
Files
F3751521
D1641.id.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.id.diff
View Options
diff --git a/omnitools/src/DateTime/Time.php b/omnitools/src/DateTime/Time.php
new file mode 100644
--- /dev/null
+++ b/omnitools/src/DateTime/Time.php
@@ -0,0 +1,123 @@
+<?php
+declare(strict_types=1);
+
+namespace Keruald\OmniTools\DateTime;
+
+use Keruald\OmniTools\Collections\Comparable;
+
+class Time implements Comparable {
+
+ private int $hours;
+ private int $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 static 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/omnitools/src/DateTime/TimeRange.php b/omnitools/src/DateTime/TimeRange.php
new file mode 100644
--- /dev/null
+++ b/omnitools/src/DateTime/TimeRange.php
@@ -0,0 +1,90 @@
+<?php
+
+namespace Keruald\OmniTools\DateTime;
+
+class TimeRange {
+
+ private Time $start;
+ private Time $end;
+
+ ///
+ /// Constructors
+ ///
+
+ public function __construct (Time $start, Time $end) {
+ $this->start = $start;
+ $this->end = $end;
+
+ $this->normalize();
+ }
+
+ public static function fromDuration (Time $start,
+ int $hoursToAdd = 0,
+ int $minutesToAdd = 0) : self {
+ $end = clone $start;
+ $end
+ ->addHours($hoursToAdd)
+ ->addMinutes($minutesToAdd);
+
+ return new self($start, $end);
+ }
+
+ public static 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;
+ }
+
+ public function countMinutes () : int {
+ return $this->end->toMinutes() - $this->start->toMinutes();
+ }
+
+}
diff --git a/omnitools/tests/DateTime/TimeRangeTest.php b/omnitools/tests/DateTime/TimeRangeTest.php
new file mode 100644
--- /dev/null
+++ b/omnitools/tests/DateTime/TimeRangeTest.php
@@ -0,0 +1,94 @@
+<?php
+
+namespace Keruald\OmniTools\Tests\DateTime;
+
+use Keruald\OmniTools\DateTime\Time;
+use Keruald\OmniTools\DateTime\TimeRange;
+use PHPUnit\Framework\Attributes\DataProvider;
+use PHPUnit\Framework\TestCase;
+
+class TimeRangeTest extends TestCase {
+
+ private Time $start;
+
+ private Time $end;
+
+ private TimeRange $range;
+
+ protected function setUp () : void {
+ $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());
+ }
+
+ public function testFromDuration () {
+ $this->assertEquals(
+ $this->end,
+ TimeRange::fromDuration($this->start, 3, 36)->getEnd()
+ );
+ }
+
+ public function testDuration () {
+ $this->assertEquals(216, $this->range->countMinutes());
+ }
+
+ #[DataProvider('provideOverlappingRanges')]
+ public function testOverlap ($range1, $range2, $isOverlap) {
+ $this->assertEquals(
+ $isOverlap,
+ TimeRange::parse($range1)->overlapsWith(TimeRange::parse($range2))
+ );
+ }
+
+ ///
+ /// Data providers
+ ///
+
+ public static 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/omnitools/tests/DateTime/TimeTest.php b/omnitools/tests/DateTime/TimeTest.php
new file mode 100644
--- /dev/null
+++ b/omnitools/tests/DateTime/TimeTest.php
@@ -0,0 +1,83 @@
+<?php
+
+namespace Keruald\OmniTools\Tests\DateTime;
+
+use Keruald\OmniTools\DateTime\Time;
+use PHPUnit\Framework\TestCase;
+
+class TimeTest extends TestCase {
+
+ private Time $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
Mon, Nov 18, 09:45 (19 h, 21 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2250120
Default Alt Text
D1641.id.diff (10 KB)
Attached To
Mode
D1641: Allow to manipulate time ranges and check overlaps
Attached
Detach File
Event Timeline
Log In to Comment