Skip to content

Commit

Permalink
Merge pull request #202 from spatie/feature/precise-time-for-next-pre…
Browse files Browse the repository at this point in the history
…vious

Make comparison microsecond-precise
  • Loading branch information
kylekatarnls committed Aug 7, 2022
2 parents 3ef6eb8 + ea6f3b5 commit befffe9
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 20 deletions.
8 changes: 4 additions & 4 deletions src/Helpers/RangeFinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ trait RangeFinder
{
protected function findRangeInFreeTime(Time $time, TimeRange $timeRange)
{
if ($timeRange->start()->isAfter($time)) {
if ($time->isBefore($timeRange->start())) {
return $timeRange;
}
}
Expand All @@ -25,7 +25,7 @@ protected function findOpenInFreeTime(Time $time, TimeRange $timeRange)

protected function findOpenRangeInWorkingHours(Time $time, TimeRange $timeRange)
{
if ($timeRange->start()->isBefore($time)) {
if ($time->isAfter($timeRange->start())) {
return $timeRange;
}
}
Expand Down Expand Up @@ -64,7 +64,7 @@ protected function findCloseInFreeTime(Time $time, TimeRange $timeRange)

protected function findPreviousRangeInFreeTime(Time $time, TimeRange $timeRange)
{
if ($timeRange->end()->isBefore($time)) {
if ($time->isAfter($timeRange->end())) {
return $timeRange;
}
}
Expand All @@ -82,7 +82,7 @@ protected function findPreviousCloseInWorkingHours(Time $time, TimeRange $timeRa
{
$end = $timeRange->end();

if ($end->isBefore($time)) {
if ($time->isAfter($end)) {
return $end;
}
}
Expand Down
28 changes: 14 additions & 14 deletions src/OpeningHours.php
Original file line number Diff line number Diff line change
Expand Up @@ -370,14 +370,14 @@ public function isOpenAt(DateTimeInterface $dateTime): bool
if ($this->overflow) {
$dateTimeMinus1Day = $this->yesterday($dateTime);
$openingHoursForDayBefore = $this->forDate($dateTimeMinus1Day);
if ($openingHoursForDayBefore->isOpenAtNight(Time::fromDateTime($dateTimeMinus1Day))) {
if ($openingHoursForDayBefore->isOpenAtNight(PreciseTime::fromDateTime($dateTimeMinus1Day))) {
return true;
}
}

$openingHoursForDay = $this->forDate($dateTime);

return $openingHoursForDay->isOpenAt(Time::fromDateTime($dateTime));
return $openingHoursForDay->isOpenAt(PreciseTime::fromDateTime($dateTime));
}

public function isClosedAt(DateTimeInterface $dateTime): bool
Expand Down Expand Up @@ -459,7 +459,7 @@ public function nextOpen(DateTimeInterface $dateTime = null): DateTimeInterface
$dateTime = $this->applyTimezone($dateTime ?? new $this->dateTimeClass());
$dateTime = $this->copyDateTime($dateTime);
$openingHoursForDay = $this->forDate($dateTime);
$nextOpen = $openingHoursForDay->nextOpen(Time::fromDateTime($dateTime));
$nextOpen = $openingHoursForDay->nextOpen(PreciseTime::fromDateTime($dateTime));
$tries = $this->getDayLimit();

while ($nextOpen === false || $nextOpen->hours() >= 24) {
Expand All @@ -480,7 +480,7 @@ public function nextOpen(DateTimeInterface $dateTime = null): DateTimeInterface

$openingHoursForDay = $this->forDate($dateTime);

$nextOpen = $openingHoursForDay->nextOpen(Time::fromDateTime($dateTime));
$nextOpen = $openingHoursForDay->nextOpen(PreciseTime::fromDateTime($dateTime));
}

if ($dateTime->format('H:i') === '00:00' && $this->isOpenAt((clone $dateTime)->modify('-1 second'))) {
Expand All @@ -507,14 +507,14 @@ public function nextClose(DateTimeInterface $dateTime = null): DateTimeInterface
if ($this->overflow) {
$dateTimeMinus1Day = $this->copyDateTime($dateTime)->modify('-1 day');
$openingHoursForDayBefore = $this->forDate($dateTimeMinus1Day);
if ($openingHoursForDayBefore->isOpenAtNight(Time::fromDateTime($dateTimeMinus1Day))) {
$nextClose = $openingHoursForDayBefore->nextClose(Time::fromDateTime($dateTime));
if ($openingHoursForDayBefore->isOpenAtNight(PreciseTime::fromDateTime($dateTimeMinus1Day))) {
$nextClose = $openingHoursForDayBefore->nextClose(PreciseTime::fromDateTime($dateTime));
}
}

$openingHoursForDay = $this->forDate($dateTime);
if (! $nextClose) {
$nextClose = $openingHoursForDay->nextClose(Time::fromDateTime($dateTime));
$nextClose = $openingHoursForDay->nextClose(PreciseTime::fromDateTime($dateTime));

if ($nextClose && $nextClose->hours() < 24 && $nextClose->format('Gi') < $dateTime->format('Gi')) {
$dateTime = $dateTime->modify('+1 day');
Expand All @@ -541,7 +541,7 @@ public function nextClose(DateTimeInterface $dateTime = null): DateTimeInterface

$openingHoursForDay = $this->forDate($dateTime);

$nextClose = $openingHoursForDay->nextClose(Time::fromDateTime($dateTime));
$nextClose = $openingHoursForDay->nextClose(PreciseTime::fromDateTime($dateTime));
}

$nextDateTime = $nextClose->toDateTime();
Expand All @@ -557,7 +557,7 @@ public function previousOpen(DateTimeInterface $dateTime): DateTimeInterface
$outputTimezone = $this->getOutputTimezone($dateTime);
$dateTime = $this->copyDateTime($this->applyTimezone($dateTime));
$openingHoursForDay = $this->forDate($dateTime);
$previousOpen = $openingHoursForDay->previousOpen(Time::fromDateTime($dateTime));
$previousOpen = $openingHoursForDay->previousOpen(PreciseTime::fromDateTime($dateTime));
$tries = $this->getDayLimit();

while ($previousOpen === false || ($previousOpen->hours() === 0 && $previousOpen->minutes() === 0)) {
Expand All @@ -578,7 +578,7 @@ public function previousOpen(DateTimeInterface $dateTime): DateTimeInterface
return $this->getDateWithTimezone($midnight, $outputTimezone);
}

$previousOpen = $openingHoursForDay->previousOpen(Time::fromDateTime($dateTime));
$previousOpen = $openingHoursForDay->previousOpen(PreciseTime::fromDateTime($dateTime));
}

$nextDateTime = $previousOpen->toDateTime();
Expand All @@ -597,14 +597,14 @@ public function previousClose(DateTimeInterface $dateTime): DateTimeInterface
if ($this->overflow) {
$dateTimeMinus1Day = $this->copyDateTime($dateTime)->modify('-1 day');
$openingHoursForDayBefore = $this->forDate($dateTimeMinus1Day);
if ($openingHoursForDayBefore->isOpenAtNight(Time::fromDateTime($dateTimeMinus1Day))) {
$previousClose = $openingHoursForDayBefore->previousClose(Time::fromDateTime($dateTime));
if ($openingHoursForDayBefore->isOpenAtNight(PreciseTime::fromDateTime($dateTimeMinus1Day))) {
$previousClose = $openingHoursForDayBefore->previousClose(PreciseTime::fromDateTime($dateTime));
}
}

$openingHoursForDay = $this->forDate($dateTime);
if (! $previousClose) {
$previousClose = $openingHoursForDay->previousClose(Time::fromDateTime($dateTime));
$previousClose = $openingHoursForDay->previousClose(PreciseTime::fromDateTime($dateTime));
}

$tries = $this->getDayLimit();
Expand All @@ -626,7 +626,7 @@ public function previousClose(DateTimeInterface $dateTime): DateTimeInterface
return $this->getDateWithTimezone($midnight, $outputTimezone);
}

$previousClose = $openingHoursForDay->previousClose(Time::fromDateTime($dateTime));
$previousClose = $openingHoursForDay->previousClose(PreciseTime::fromDateTime($dateTime));
}

$previousDateTime = $previousClose->toDateTime();
Expand Down
82 changes: 82 additions & 0 deletions src/PreciseTime.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

namespace Spatie\OpeningHours;

use DateTimeImmutable;
use DateTimeInterface;
use DateTimeZone;

class PreciseTime extends Time
{
/** @var DateTimeInterface */
protected $dateTime;

protected function __construct(DateTimeInterface $dateTime)
{
$this->dateTime = $dateTime;
}

public static function fromString(string $string): parent
{
return self::fromDateTime(new DateTimeImmutable($string));
}

public function hours(): int
{
return (int) $this->dateTime->format('G');
}

public function minutes(): int
{
return (int) $this->dateTime->format('i');
}

public static function fromDateTime(DateTimeInterface $dateTime): parent
{
return new self($dateTime);
}

public function isSame(parent $time): bool
{
return $this->format('H:i:s.u') === $time->format('H:i:s.u');
}

public function isAfter(parent $time): bool
{
return $this->format('H:i:s.u') > $time->format('H:i:s.u');
}

public function isBefore(parent $time): bool
{
return $this->format('H:i:s.u') < $time->format('H:i:s.u');
}

public function isSameOrAfter(parent $time): bool
{
return $this->format('H:i:s.u') >= $time->format('H:i:s.u');
}

public function diff(parent $time): \DateInterval
{
return $this->toDateTime()->diff($time->toDateTime());
}

public function toDateTime(DateTimeInterface $date = null): DateTimeInterface
{
return $date
? $this->copyDateTime($date)->modify($this->format('H:i:s.u'))
: $this->copyDateTime($this->dateTime);
}

public function format(string $format = 'H:i', $timezone = null): string
{
$date = $timezone
? $this->copyDateTime($this->dateTime)->setTimezone($timezone instanceof DateTimeZone
? $timezone
: new DateTimeZone($timezone)
)
: $this->dateTime;

return $date->format($format);
}
}
8 changes: 6 additions & 2 deletions src/Time.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Spatie\OpeningHours;

use DateTime;
use DateTimeImmutable;
use DateTimeInterface;
use DateTimeZone;
use Spatie\OpeningHours\Exceptions\InvalidTimeString;
Expand Down Expand Up @@ -104,8 +105,11 @@ public function format(string $format = 'H:i', $timezone = null): string
)
: null;

if ($format === 'H:i' && $this->hours === 24 && $this->minutes === 0) {
return '24:00';
if ($this->hours === 24 && $this->minutes === 0 && substr($format, 0, 3) === 'H:i') {
return '24:00'.(strlen($format) > 3
? ($date ?? new DateTimeImmutable('1970-01-01 00:00:00'))->format(substr($format, 3))
: ''
);
}

return $this->toDateTime($date)->format($format);
Expand Down
6 changes: 6 additions & 0 deletions tests/OpeningHoursTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,12 @@ public function it_can_handle_consecutive_open_hours()
$friday = new DateTimeImmutable('2019-02-08 02:00:00');
$this->assertSame('2019-02-07 00:00:00', $openingHours->previousClose($friday)->format('Y-m-d H:i:s'));
$this->assertSame('2019-02-08 00:00:00', $openingHours->previousOpen($friday)->format('Y-m-d H:i:s'));

$friday = new DateTimeImmutable('2022-08-05 03:00:00.000001');
$this->assertSame('2022-08-05 03:00:00', $openingHours->previousClose($friday)->format('Y-m-d H:i:s'));

$friday = new DateTimeImmutable('2022-08-05 00:00:00.000000');
$this->assertSame('2022-08-04 00:00:00', $openingHours->previousClose($friday)->format('Y-m-d H:i:s'));
}

/** @test */
Expand Down
72 changes: 72 additions & 0 deletions tests/PreciseTimeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

namespace Spatie\OpeningHours\Test;

use DateTimeImmutable;
use DateTimeZone;
use PHPUnit\Framework\TestCase;
use Spatie\OpeningHours\PreciseTime;

class PreciseTimeTest extends TestCase
{
/** @test */
public function it_can_be_formatted()
{
$date = new DateTimeImmutable('2022-08-07 23:32:58.123456 America/Toronto');
$this->assertSame(
'2022-08-08 05:32:58.123456 Europe/Berlin',
PreciseTime::fromDateTime($date)->format('Y-m-d H:i:s.u e', 'Europe/Berlin')
);
$this->assertSame(
'2022-08-08 12:32:58.123456 Asia/Tokyo',
PreciseTime::fromDateTime($date)->format('Y-m-d H:i:s.u e', new DateTimeZone('Asia/Tokyo'))
);
}

/** @test */
public function it_can_return_original_datetime()
{
$date = new DateTimeImmutable('2022-08-07 23:32:58.123456 America/Toronto');
$this->assertSame($date, PreciseTime::fromDateTime($date)->toDateTime());
$this->assertSame('2021-11-25 23:32:58'.(PHP_VERSION < 7.1 ? '' : '.123456').' Asia/Tokyo', PreciseTime::fromDateTime($date)->toDateTime(
new DateTimeImmutable('2021-11-25 15:02:03.987654 Asia/Tokyo')
)->format('Y-m-d H:i:s'.(PHP_VERSION < 7.1 ? '' : '.u').' e'));
}

/** @test */
public function it_can_return_diff()
{
$date = new DateTimeImmutable('2021-08-07 23:32:58.123456 America/Toronto');
$this->assertSame(
'02 29 05 '.(PHP_VERSION < 7.1 ? '%F' : '864198'),
PreciseTime::fromDateTime($date)->diff(PreciseTime::fromDateTime(
new DateTimeImmutable('2022-11-25 15:02:03.987654 Asia/Tokyo')
))->format('%H %I %S %F')
);
}

/** @test */
public function it_can_be_compared()
{
$date = new DateTimeImmutable('2022-08-07 23:32:58.123456 America/Toronto');
$this->assertSame($date, PreciseTime::fromDateTime($date)->toDateTime());
$this->assertTrue(PreciseTime::fromDateTime(
new DateTimeImmutable('2022-08-07 23:32:58.123456 America/Toronto')
)->isSame(PreciseTime::fromDateTime(
new DateTimeImmutable('2021-11-25 23:32:58.123456 Asia/Tokyo')
)));
$this->assertFalse(PreciseTime::fromDateTime(
new DateTimeImmutable('2022-08-07 23:32:58.123456 America/Toronto')
)->isSame(PreciseTime::fromDateTime(
new DateTimeImmutable('2022-08-07 23:32:58.123457 America/Toronto')
)));
}

/** @test */
public function it_can_output_hours_and_minutes()
{
$date = PreciseTime::fromString('2022-08-07 23:32:58.123456 America/Toronto');
$this->assertSame(23, $date->hours());
$this->assertSame(32, $date->minutes());
}
}

0 comments on commit befffe9

Please sign in to comment.