feat(services): add duration convertor
This commit is contained in:
parent
7c92c90fa8
commit
114271d724
64
app/Services/Utils/DurationConvertor.php
Normal file
64
app/Services/Utils/DurationConvertor.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Utils;
|
||||
|
||||
use Brick\DateTime\Duration;
|
||||
use Brick\DateTime\LocalTime;
|
||||
|
||||
/*
|
||||
* Converts given dates and times into a Duration object.
|
||||
*/
|
||||
class DurationConvertor
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a given duration into working hours considering the given start and end workday. Duration is calculated from startWorkday
|
||||
* @param Duration $duration
|
||||
* @param LocalTime $startWorkday
|
||||
* @param LocalTime $endWorkday
|
||||
* @param \DateTimeInterface $startTime
|
||||
* @return DurationConvertorResult
|
||||
*/
|
||||
public function convertIntoWorkDuration(Duration $duration, LocalTime $startWorkday, LocalTime $endWorkday, \DateTimeInterface $startTime): DurationConvertorResult
|
||||
{
|
||||
if ($endWorkday->isEqualTo($startWorkday)) {
|
||||
throw new \InvalidArgumentException("Start and end workday must be different");
|
||||
}
|
||||
|
||||
$workDuration = $duration;
|
||||
$endDuration = Duration::zero();
|
||||
$workDaySeconds = $endWorkday->toSecondOfDay() - $startWorkday->toSecondOfDay();
|
||||
|
||||
// handle first day
|
||||
$startDateTime = \DateTimeImmutable::createFromInterface($startTime)->setTime($startWorkday->getHour(), $startWorkday->getMinute());
|
||||
$availSecondsFirstDay = $workDaySeconds - ($startTime->getTimestamp() - $startDateTime->getTimestamp());
|
||||
|
||||
if ($availSecondsFirstDay >= $workDuration->toSeconds()) {
|
||||
// first day is enough time
|
||||
$endDuration->plusSeconds($workDaySeconds - $availSecondsFirstDay);
|
||||
|
||||
return new DurationConvertorResult(
|
||||
false,
|
||||
$endDuration->plusSeconds($workDuration->toSeconds())
|
||||
);
|
||||
} else {
|
||||
$workDuration = $workDuration->minusSeconds($availSecondsFirstDay);
|
||||
$endDuration = $endDuration->plus(Duration::ofDays(1));
|
||||
}
|
||||
|
||||
while ($workDuration->toSeconds() > $workDaySeconds) {
|
||||
$endDuration = $endDuration->plus(Duration::ofDays(1));
|
||||
$workDuration = $workDuration->minusSeconds($workDaySeconds);
|
||||
}
|
||||
|
||||
return new DurationConvertorResult(
|
||||
true,
|
||||
$endDuration->plus($workDuration)
|
||||
);
|
||||
}
|
||||
}
|
19
app/Services/Utils/DurationConvertorResult.php
Normal file
19
app/Services/Utils/DurationConvertorResult.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Utils;
|
||||
|
||||
use Brick\DateTime\Duration;
|
||||
|
||||
/**
|
||||
* Results of DurationConvertor::convertIntoWorkDuration
|
||||
*/
|
||||
final readonly class DurationConvertorResult
|
||||
{
|
||||
public function __construct(
|
||||
public bool $overlaps,
|
||||
public Duration $duration,
|
||||
) {
|
||||
}
|
||||
}
|
49
tests/Unit/Services/Utils/DurationConvertorTest.php
Normal file
49
tests/Unit/Services/Utils/DurationConvertorTest.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Services\Utils;
|
||||
|
||||
use App\Services\Utils\DurationConvertor;
|
||||
use Brick\DateTime\Duration;
|
||||
use Brick\DateTime\LocalTime;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use Tests\TestCase;
|
||||
|
||||
class DurationConvertorTest extends TestCase
|
||||
{
|
||||
#[DataProvider('durationsProvider')]
|
||||
public function testDurationConversionIntoWorkingDays(float $expectedMinutes, string $duration, string $startWorkday, string $endWorkday, string $startTime): void
|
||||
{
|
||||
$converter = new DurationConvertor();
|
||||
$date = \DateTimeImmutable::createFromFormat("H:i", $startTime);
|
||||
|
||||
if ($date === false) {
|
||||
throw new \InvalidArgumentException("Invalid start time format");
|
||||
}
|
||||
|
||||
$this->assertEquals($expectedMinutes, $converter->convertIntoWorkDuration(
|
||||
Duration::parse($duration),
|
||||
LocalTime::parse($startWorkday),
|
||||
LocalTime::parse($endWorkday),
|
||||
$date
|
||||
)->duration->toMinutes());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<int, string|float>>
|
||||
*/
|
||||
public static function durationsProvider(): array
|
||||
{
|
||||
return [
|
||||
[(1.5 + 24) * 60, 'P0DT3H', '08:00', '09:30', '08:00'], // 1.5 hours and one day (half work per day)
|
||||
[(24 * 2 + 1) * 60, 'P0DT3H', '08:00', '09:00', '08:00'], // 1 hour and two days (1/3 per day)
|
||||
[3 * 24 * 60 + 30, 'P0DT3H30M', '08:00', '09:00', '8:00'], // full 3 days and 30M
|
||||
[3 * 60, 'P0DT3H', '08:00', '16:00', '08:00'], // 3 hours
|
||||
[4 * 60, 'P0DT4H', '08:00', '16:00', '08:00'], // 4 hours
|
||||
[8 * 60, 'P0DT8H', '08:00', '16:00', '08:00'], // 8 hours (one full day)
|
||||
[24 * 60 + 2, 'P0DT8H2M', '08:00', '16:00', '08:00'], // 8 hours and 2 minutes (one full day and 2 minutes)
|
||||
[24 * 60 + 1, 'P0DT0H2M', '08:00', '16:00', '15:59'] // 1 minute first day, one minute second day because startTime
|
||||
];
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user