Skip to content

Commit

Permalink
Merge pull request #36 from atournayre/feature/duration
Browse files Browse the repository at this point in the history
feat(Duration): Add a Duration representation
  • Loading branch information
atournayre authored Oct 2, 2024
2 parents 7fd5552 + a9075c4 commit 83f8439
Show file tree
Hide file tree
Showing 4 changed files with 212 additions and 0 deletions.
26 changes: 26 additions & 0 deletions docs/adr/ADR-0020-Duration.md
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.
1 change: 1 addition & 0 deletions docs/architecture-decision-records.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@
| [ADR 0017](adr/ADR-0017-Is.md) | Is | 2024-08-22 | Accepted | Check sameness |
| [ADR 0018](adr/ADR-0018-Absolute-Numeric.md) | Absolute value for numeric | 2024-09-01 | Accepted | Absolute value for numeric |
| [ADR 0019](adr/ADR-0019-Collection-Validation.md) | Collection validation | 2024-09-07 | Accepted | Collection validation |
| [ADR 0020](adr/ADR-0020-Duration.md) | Duration | 2024-10-02 | Accepted | Duration |
110 changes: 110 additions & 0 deletions src/Common/VO/Duration.php
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(' ')
;
}
}
75 changes: 75 additions & 0 deletions tests/Unit/Common/VO/DurationTest.php
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));
}
}

0 comments on commit 83f8439

Please sign in to comment.