-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #36 from atournayre/feature/duration
feat(Duration): Add a Duration representation
- Loading branch information
Showing
4 changed files
with
212 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# ADR 0020: Duration | ||
|
||
## Status | ||
Accepted | ||
|
||
## Context | ||
We need to represent a duration. | ||
|
||
## Decision | ||
We will create a `Duration` class to represent a duration. | ||
|
||
### Implementation Details | ||
|
||
1. **Duration**: | ||
- `Duration` to represent a duration. | ||
|
||
## Consequences | ||
|
||
- **Benefits**: | ||
- Provides a consistent interface to represent a duration. | ||
- Provides methods to manipulate durations. | ||
- Facilitates testing by providing a `Duration` class. | ||
|
||
- **Drawbacks**: | ||
- Introduces additional complexity with the need to create and manage the `Duration` class. | ||
- Requires developers to be familiar with the `Duration` class. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Atournayre\Common\VO; | ||
|
||
use Atournayre\Primitives\Collection; | ||
|
||
final class Duration | ||
{ | ||
private const MILLISECONDS_IN_SECOND = 1000; | ||
|
||
private const SECONDS_IN_MINUTE = 60; | ||
|
||
private const MINUTES_IN_HOUR = 60; | ||
|
||
private const HOURS_IN_DAY = 24; | ||
|
||
private int $milliseconds; | ||
|
||
private function __construct(int $milliseconds) | ||
{ | ||
$this->milliseconds = $milliseconds; | ||
} | ||
|
||
/** | ||
* @api | ||
*/ | ||
public static function of(int $milliseconds): self | ||
{ | ||
return new self($milliseconds); | ||
} | ||
|
||
/** | ||
* @api | ||
*/ | ||
public function asIs(): int | ||
{ | ||
return $this->milliseconds; | ||
} | ||
|
||
/** | ||
* @api | ||
*/ | ||
public function milliseconds(): int | ||
{ | ||
return $this->milliseconds; | ||
} | ||
|
||
/** | ||
* @api | ||
*/ | ||
public function inSeconds(): float | ||
{ | ||
return $this->milliseconds / self::MILLISECONDS_IN_SECOND; | ||
} | ||
|
||
/** | ||
* @api | ||
*/ | ||
public function inMinutes(): float | ||
{ | ||
return $this->milliseconds / self::MILLISECONDS_IN_SECOND / self::SECONDS_IN_MINUTE; | ||
} | ||
|
||
/** | ||
* @api | ||
*/ | ||
public function inHours(): float | ||
{ | ||
return $this->milliseconds / self::MILLISECONDS_IN_SECOND / self::SECONDS_IN_MINUTE / self::MINUTES_IN_HOUR; | ||
} | ||
|
||
/** | ||
* @api | ||
*/ | ||
public function inDays(): float | ||
{ | ||
return $this->milliseconds / self::MILLISECONDS_IN_SECOND / self::SECONDS_IN_MINUTE / self::MINUTES_IN_HOUR / self::HOURS_IN_DAY; | ||
} | ||
|
||
/** | ||
* @api | ||
*/ | ||
public function forHumanReading(string $glue = ' '): string | ||
{ | ||
$days = floor($this->milliseconds / self::MILLISECONDS_IN_SECOND / self::SECONDS_IN_MINUTE / self::MINUTES_IN_HOUR / self::HOURS_IN_DAY); | ||
$hours = floor($this->milliseconds / self::MILLISECONDS_IN_SECOND / self::SECONDS_IN_MINUTE / self::MINUTES_IN_HOUR) % self::HOURS_IN_DAY; | ||
$minutes = floor($this->milliseconds / self::MILLISECONDS_IN_SECOND / self::SECONDS_IN_MINUTE) % self::MINUTES_IN_HOUR; | ||
$seconds = floor($this->milliseconds / self::MILLISECONDS_IN_SECOND) % self::SECONDS_IN_MINUTE; | ||
$milliseconds = $this->milliseconds % self::MILLISECONDS_IN_SECOND; | ||
|
||
$pushCallback = fn ($value): bool => $value >= 0; | ||
$pluralCallback = fn ($value, $singular, $plural): string => $value <= 1 ? $singular : $plural; | ||
|
||
return Collection::of() | ||
->push($days, $pushCallback) | ||
->push($pluralCallback($days, 'day', 'days').$glue, $pushCallback) | ||
->push($hours, $pushCallback) | ||
->push($pluralCallback($hours, 'hour', 'hours').$glue, $pushCallback) | ||
->push($minutes, $pushCallback) | ||
->push($pluralCallback($minutes, 'minute', 'minutes').$glue, $pushCallback) | ||
->push($seconds, $pushCallback) | ||
->push($pluralCallback($seconds, 'second', 'seconds').$glue, $pushCallback) | ||
->push($milliseconds, $pushCallback) | ||
->push($pluralCallback($milliseconds, 'millisecond', 'milliseconds'), $pushCallback) | ||
->join(' ') | ||
; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Atournayre\Tests\Unit\Common\VO; | ||
|
||
use Atournayre\Common\VO\Duration; | ||
use PHPUnit\Framework\TestCase; | ||
|
||
final class DurationTest extends TestCase | ||
{ | ||
/** | ||
* @return array<string, array<int>> | ||
*/ | ||
public function dataProvider(): array | ||
{ | ||
$test1 = 1 * 24 * 60 * 60 * 1000 // 1 day | ||
+ 4 * 60 * 60 * 1000 // 4 hours | ||
+ 31 * 60 * 1000 // 31 minutes | ||
+ 12 * 1000 // 12 seconds | ||
+ 456; // 456 milliseconds | ||
|
||
$test2 = 2 * 24 * 60 * 60 * 1000 // 2 days | ||
+ 1 * 60 * 60 * 1000 // 1 hour | ||
+ 0 * 60 * 1000 // 0 minute | ||
+ 1 * 1000 // 1 second | ||
+ 0; // 0 millisecond | ||
|
||
return [ | ||
'1 day 4 hours 31 minutes 12 seconds 456 milliseconds' => [ | ||
$test1, | ||
$test1, | ||
$test1 / 1000, | ||
$test1 / 1000 / 60, | ||
$test1 / 1000 / 60 / 60, | ||
$test1 / 1000 / 60 / 60 / 24, | ||
'', // glue for human reading | ||
'1 day 4 hours 31 minutes 12 seconds 456 milliseconds', | ||
], | ||
'2 days 1 hour 0 minute 1 second 0 millisecond' => [ | ||
$test2, | ||
$test2, | ||
$test2 / 1000, | ||
$test2 / 1000 / 60, | ||
$test2 / 1000 / 60 / 60, | ||
$test2 / 1000 / 60 / 60 / 24, | ||
',', // glue for human reading | ||
'2 days, 1 hour, 0 minute, 1 second, 0 millisecond', | ||
], | ||
]; | ||
} | ||
|
||
/** | ||
* @dataProvider dataProvider | ||
*/ | ||
public function testDuration( | ||
int $milliseconds, | ||
int $expectedMilliseconds, | ||
float $expectedSeconds, | ||
float $expectedMinutes, | ||
float $expectedHours, | ||
float $expectedDays, | ||
string $glueForHumanReading, | ||
string $expectedHuman | ||
): void { | ||
$duration = Duration::of($milliseconds); | ||
|
||
self::assertEquals($expectedMilliseconds, $duration->asIs()); | ||
self::assertEquals($expectedSeconds, $duration->inSeconds()); | ||
self::assertEquals($expectedMinutes, $duration->inMinutes()); | ||
self::assertEquals($expectedHours, $duration->inHours()); | ||
self::assertEquals($expectedDays, $duration->inDays()); | ||
self::assertEquals($expectedHuman, $duration->forHumanReading($glueForHumanReading)); | ||
} | ||
} |