Skip to content

Commit

Permalink
Merge pull request #5 from mohammedmanssour/better_repetition_calcula…
Browse files Browse the repository at this point in the history
…tion

Better repetition calculation
  • Loading branch information
mohammedmanssour authored Aug 6, 2023
2 parents d91fbb6 + 1e0d45f commit f089fb5
Show file tree
Hide file tree
Showing 11 changed files with 54 additions and 21 deletions.
5 changes: 3 additions & 2 deletions src/Concerns/Repeatable.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

namespace MohammedManssour\LaravelRecurringModels\Concerns;

use Carbon\Carbon;
use Carbon\CarbonInterface as Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use MohammedManssour\LaravelRecurringModels\Enums\RepetitionType;
use MohammedManssour\LaravelRecurringModels\Models\Repetition;
use MohammedManssour\LaravelRecurringModels\Support\Repeat;

Expand All @@ -27,7 +28,7 @@ public function repetitions(): MorphMany
/**
* define the base date that we would use to calculate repetition start_at
*/
public function repetitionBaseDate(): Carbon
public function repetitionBaseDate(RepetitionType $type = null): Carbon
{
return $this->created_at;
}
Expand Down
5 changes: 3 additions & 2 deletions src/Contracts/Repeatable.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@

namespace MohammedManssour\LaravelRecurringModels\Contracts;

use Carbon\Carbon;
use Carbon\CarbonInterface as Carbon;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use MohammedManssour\LaravelRecurringModels\Enums\RepetitionType;
use MohammedManssour\LaravelRecurringModels\Support\Repeat;

interface Repeatable
{
public function repetitions(): MorphMany;

public function repetitionBaseDate(): Carbon;
public function repetitionBaseDate(RepetitionType $type = null): Carbon;

public function repeat(): Repeat;
}
2 changes: 1 addition & 1 deletion src/Exceptions/DriverNotSupportedException.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

class DriverNotSupportedException extends \Exception
{
public function __construct(string $driver, int $code = 0, ?Throwable $previous = null)
public function __construct(string $driver, int $code = 0, Throwable $previous = null)
{
parent::__construct("Database driver \"{$driver}\" is not supported.", $code, $previous);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

class RepetitionEndsAfterNotAvailableException extends \Exception
{
public function __construct(int $code = 0, ?Throwable $previous = null)
public function __construct(int $code = 0, Throwable $previous = null)
{
parent::__construct('endsAfter method is not available for complex repetitions. Please use endsAt method instead to explicitly set end date.', $code, $previous);
}
Expand Down
10 changes: 6 additions & 4 deletions src/Models/Repetition.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace MohammedManssour\LaravelRecurringModels\Models;

use Carbon\Carbon;
use Carbon\CarbonInterface as Carbon;
use Carbon\CarbonPeriod;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
Expand Down Expand Up @@ -88,14 +88,16 @@ public function scopeWhereOccurresBetween(Builder $query, Carbon $start, Carbon

private function simpleQuery(Builder $query, Carbon $date): Builder
{
$secondsInDay = 86400;

$query
->where('type', RepetitionType::Simple);

$driver = $query->getConnection()->getConfig('driver');
match ($driver) {
'mysql' => $query->whereRaw('(ABS(DATEDIFF(start_at, ?)) * 24 * 60 * 60) % `interval` = 0', [$date->toDateTimeString()]),
'sqlite' => $query->whereRaw('(CAST(ABS(julianday(start_at) - julianday(?)) AS INT) * 24 * 60 * 60) % `interval` = 0', [$date->toDateTimeString()]),
'pgsql' => $query->whereRaw('MOD(ABS(DATE_PART(\'day\', start_at::date) - DATE_PART(\'day\', ?::date))::INTEGER * 24 * 60 * 60, interval) = 0', [$date->toDateTimeString()]),
'mysql' => $query->whereRaw('(UNIX_TIMESTAMP(?) - UNIX_TIMESTAMP(`start_at`)) % `interval` BETWEEN 0 AND ?', [$date->toDateTimeString(), $secondsInDay]),
'sqlite' => $query->whereRaw('(? - unixepoch(`start_at`)) % `interval` BETWEEN 0 AND ?', [$date->timestamp, $secondsInDay]),
'pgsql' => $query->whereRaw("MOD((? - DATE_PART('EPOCH', start_at))::INTEGER, interval) BETWEEN 0 AND ?", [$date->timestamp, $secondsInDay]),
default => throw new DriverNotSupportedException($driver),
};

Expand Down
2 changes: 1 addition & 1 deletion src/Support/PendingRepeats/PendingComplexRepeat.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public function __construct(
Repeatable $model
) {
parent::__construct($model);
$this->start_at = $this->model->repetitionBaseDate()->clone()->addDay();
$this->start_at = $this->model->repetitionBaseDate(RepetitionType::Complex)->clone()->addDay();
}

public function rule(string $year = '*', string $month = '*', string $day = '*', string $week = '*', string $weekday = '*'): static
Expand Down
3 changes: 2 additions & 1 deletion src/Support/PendingRepeats/PendingEveryNDaysRepeat.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
namespace MohammedManssour\LaravelRecurringModels\Support\PendingRepeats;

use MohammedManssour\LaravelRecurringModels\Contracts\Repeatable;
use MohammedManssour\LaravelRecurringModels\Enums\RepetitionType;

class PendingEveryNDaysRepeat extends PendingRepeat
{
public function __construct(Repeatable $model, int $days)
{
parent::__construct($model);
$this->interval = $days * 86400;
$this->start_at = $this->model->repetitionBaseDate()->clone()->addSeconds($this->interval);
$this->start_at = $this->model->repetitionBaseDate(RepetitionType::Simple)->clone()->addSeconds($this->interval);
}

public function endsAfter(int $times): static
Expand Down
3 changes: 2 additions & 1 deletion src/Support/PendingRepeats/PendingEveryWeekRepeat.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Illuminate\Support\Collection;
use MohammedManssour\LaravelRecurringModels\Contracts\Repeatable;
use MohammedManssour\LaravelRecurringModels\Enums\RepetitionType;
use MohammedManssour\LaravelRecurringModels\Exceptions\RepetitionEndsAfterNotAvailableException;

class PendingEveryWeekRepeat extends PendingRepeat
Expand Down Expand Up @@ -57,7 +58,7 @@ private function makeRules(): void
if ($this->days->isEmpty()) {
$this->rules->push(
$this->getRule(
strtolower($this->model->repetitionBaseDate()->format('l'))
strtolower($this->model->repetitionBaseDate(RepetitionType::Complex)->format('l'))
)
);

Expand Down
4 changes: 2 additions & 2 deletions tests/RepeatableTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public function it_gets_the_repeatable_model_that_will_occure_in_a_specific_date
$this->repetition($this->task(), '2023-04-23 00:00:00');

$model = Task::whereOccurresOn(Carbon::make('2023-04-20 00:00:00'))->first();
$this->assertSame($this->task()->id, $model->id);
$this->assertTrue($this->task()->is($model));

$model = Repetition::whereOccurresOn(Carbon::make('2023-04-25 00:00:00'))->first();
$this->assertNull($model);
Expand All @@ -31,6 +31,6 @@ public function it_gets_the_repeatable_model_that_will_occure_between_specific_d
Carbon::make('2023-04-20 00:00:00'),
Carbon::make('2023-04-25 00:00:00'),
)->first();
$this->assertEquals($this->task()->id, $model->id);
$this->assertTrue($this->task()->is($model));
}
}
36 changes: 31 additions & 5 deletions tests/RepetitionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,35 @@ class RepetitionTest extends TestCase
use HasTask;

/** @test */
public function it_ches_if_simple_repetition_occurres_on_specific_day()
public function it_checks_if_simple_repetition_occurres_on_specific_day()
{
// repeat start at 2023-04-15 00:00:00
$repetition = $this->repetition($this->task());

$model = Repetition::whereOccurresOn(Carbon::make('2023-04-10 00:00:00'))->first();
$this->assertNull($model);

$model = Repetition::whereOccurresOn(Carbon::make('2023-04-20 00:00:00'))->first();
$this->assertEquals($repetition->id, $model->id);
$model = Repetition::whereOccurresOn(Carbon::make('2023-04-20 23:00:00'))->first();
$this->assertTrue($repetition->is($model));

$model = Repetition::whereOccurresOn(Carbon::make('2023-04-16 23:00:00'))->first();
$this->assertFalse($repetition->is($model));

$model = Repetition::whereOccurresOn(Carbon::make('2023-04-17 23:00:00'))->first();
$this->assertFalse($repetition->is($model));

$model = Repetition::whereOccurresOn(Carbon::make('2023-04-18 23:00:00'))->first();
$this->assertFalse($repetition->is($model));

$model = Repetition::whereOccurresOn(Carbon::make('2023-04-19 23:00:00'))->first();
$this->assertFalse($repetition->is($model));

// ensure the day no matter what the hour is. usefull when handling timezones
$model = Repetition::whereOccurresOn(Carbon::make('2023-04-20 23:00:00'))->first();
$this->assertTrue($repetition->is($model));

$model = Repetition::whereOccurresOn(Carbon::make('2023-04-25 00:00:00'))->first();
$this->assertEquals($repetition->id, $model->id);
$this->assertTrue($repetition->is($model));
}

/** @test */
Expand All @@ -37,13 +54,22 @@ public function it_checks_if_simple_repetition_occurres_on_specific_dates_after_
/** @test */
public function it_checks_if_simple_repetition_occurres_between_two_specific_dates()
{
// repeat start at 2023-04-15 00:00:00
$repetition = $this->repetition($this->task());

$model = Repetition::whereOccurresBetween(
Carbon::make('2023-04-20 00:00:00'),
Carbon::make('2023-04-25 00:00:00'),
)->first();
$this->assertEquals($repetition->id, $model->id);
$this->assertTrue($repetition->is($model));

$this->assertFalse(
Repetition::whereOccurresBetween(
Carbon::make('2023-04-10 00:00:00'),
Carbon::make('2023-04-14 00:00:00'),
)
->exists()
);
}

/** @test */
Expand Down
3 changes: 2 additions & 1 deletion tests/Stubs/Models/Task.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Illuminate\Database\Eloquent\Model;
use MohammedManssour\LaravelRecurringModels\Concerns\Repeatable;
use MohammedManssour\LaravelRecurringModels\Contracts\Repeatable as RepeatableContract;
use MohammedManssour\LaravelRecurringModels\Enums\RepetitionType;

class Task extends Model implements RepeatableContract
{
Expand All @@ -18,7 +19,7 @@ class Task extends Model implements RepeatableContract
/**
* define the base date that we would use to calculate repetition start_at
*/
public function repetitionBaseDate(): Carbon
public function repetitionBaseDate(RepetitionType $type = null): Carbon
{
return now();
}
Expand Down

0 comments on commit f089fb5

Please sign in to comment.