Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

if (!file_exists(__DIR__.'/src')) {
exit(0);
}

return (new PhpCsFixer\Config())
->setRules([
'@PhpCsFixer' => true,
'strict_param' => true,
'fopen_flags' => false,
'no_useless_else' => true,
'no_useless_return' => true,
'native_constant_invocation' => [
'include' => ['PNG_ALL_FILTERS'],
'strict' => true,
],
'array_indentation' => true,
'static_lambda' => true,
'return_assignment' => false,
'method_argument_space' => [
'on_multiline' => 'ensure_fully_multiline',
],
'trailing_comma_in_multiline' => [
'elements' => ['arrays', 'arguments', 'parameters']
],
'php_unit_test_class_requires_covers' => false,
'php_unit_internal_class' => [],
])
->setRiskyAllowed(true)
->setFinder(
PhpCsFixer\Finder::create()
->in(__DIR__.'/src')
->in(__DIR__.'/tests')
)
;
43 changes: 25 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,33 @@ RFC : https://tools.ietf.org/html/rfc5545#page-37
### Create recurrences using object method

```php
$recurrence = (new Recurrence())
->setFrequency(new Frequency('MONTHLY'))
->setPeriodStartAt(new \Datetime('2017-01-01'))
->setPeriodEndAt(new \Datetime('2017-12-31'))
->setInterval(1);
$periods = (new DatetimeProvider())->provide($recurrence);
$frequency = new Frequency(Frequency::FREQUENCY_MONTHLY);
$interval = 1;
$periodStartAt = new \DateTime('2017-01-01');
$periodEndAt = new \DateTime('2017-12-31');
$count = null;
$constraints = [];


$recurrence = new Recurrence(
$frequency,
$interval,
$periodStartAt,
$periodEndAt,
$count,
$constraints
);

$periods = (new \Recurrence\DatetimeProvider())->provide($recurrence);
```

Available methods :
- `setFrequency` (`Frequency`) : set `FREQ` option
- `setPeriodStartAt` (`\Datetime()`) : set `DTSTART` option
- `setPeriodEndAt` (`\Datetime()`) : set `UNTIL` option
- `setInterval` (`integer`) : set `INTERVAL` option
- `setCount` (`integer`) : set `COUNT` option
Parameters:
- `$frequency` (`Frequency`): Determine the recurrence period (daily, weekly, ...).
- `$interval` (`integer`): Interval base on the frequency (a monthly frequency with an interval of `2` mean every 2 months).
- `$periodStartAt` (`DateTime`): Recurrence period start at.
- `$periodEndAt` (`DateTime`, nullable): Recurrence period end at.
- `$count` (`integer`, nullable): Limit the number of recurrence.
- `$constraints` (`RecurrenceConstraintInterface[]`): Some additional constraints that help you to manage some useful cases.

### Create recurrences from RRULE standard expression

Expand Down Expand Up @@ -60,10 +73,6 @@ Supported rules :
You can add some constraint to `Recurrence` in order to manage more precisely generated datetimes.
For example, if you do not want to generate datetime on wednesday (day `3` according to date format in PHP), add this constraint :

```php
$recurrence->addConstraint(new ExcludeDaysOfWeekConstraint([3]));
```

* `EndOfMonthConstraint` : if recurrence has `MONTHLY` frequency and start date is last day of current month, force last day of month for all datetimes
* `ExcludeDaysOfWeekConstraint` : if datetime is concerned, `DatetimeProvider` will return next valid date
* `ExcludeWeekendConstraint` : if datetime is concerned, `DatetimeProvider` will return next monday
Expand Down Expand Up @@ -106,5 +115,3 @@ Or even better, compare the 2 benchmarks and see if you don't degrade performanc
```
./vendor/bin/phpbench run --report=aggregate --ref=initial
```


Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@

interface DatetimeConstraintInterface
{
public function apply(Recurrence $recurrence, \Datetime $datetime): \Datetime | null;
public function apply(Recurrence $recurrence, \DateTime $datetime): \DateTime|null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public function __construct(array $excludedDays = [])
}

foreach ($excludedDays as $excludedDay) {
$excludedDay = filter_var($excludedDay, FILTER_VALIDATE_INT);
$excludedDay = filter_var($excludedDay, \FILTER_VALIDATE_INT);

if (!$excludedDay || $excludedDay < 1 || $excludedDay > 7) {
throw new \InvalidArgumentException('Exclude day must be an integer between 1 and 7');
Expand All @@ -30,9 +30,9 @@ public function __construct(array $excludedDays = [])
}
}

public function apply(Recurrence $recurrence, \Datetime $datetime): \Datetime
public function apply(Recurrence $recurrence, \DateTime $datetime): \DateTime
{
while (in_array((int) $datetime->format('N'), $this->excludedDays)) {
while (in_array((int) $datetime->format('N'), $this->excludedDays, true)) {
$datetime->modify('+1 day');
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

class ExcludeWeekendConstraint implements DatetimeConstraintInterface, RecurrenceConstraintInterface
{
public function apply(Recurrence $recurrence, \Datetime $datetime): \Datetime
public function apply(Recurrence $recurrence, \DateTime $datetime): \DateTime
{
if ($this->isWeekend($datetime)) {
// Add 1 or 2 days to skip weekend (we didn't use `next monday` pattern of \Datetime::format cause it remove time)
Expand All @@ -18,8 +18,8 @@ public function apply(Recurrence $recurrence, \Datetime $datetime): \Datetime
return $datetime;
}

private function isWeekend(\Datetime$datetime): bool
private function isWeekend(\DateTime $datetime): bool
{
return ((int) $datetime->format('N') >= 6);
return (int) $datetime->format('N') >= 6;
}
}
5 changes: 1 addition & 4 deletions src/Recurrence/DatetimeProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
use Recurrence\Constraint\DatetimeConstraint\DatetimeConstraintInterface;
use Recurrence\Model\Recurrence;
use Recurrence\Provider\DatetimeProviderFactory;
use Recurrence\Validator\RecurrenceValidator;

class DatetimeProvider
{
Expand All @@ -14,8 +13,6 @@ class DatetimeProvider
*/
public function provide(Recurrence $recurrence): array
{
RecurrenceValidator::validate($recurrence);

$provider = DatetimeProviderFactory::create($recurrence);

$datetimes = $provider->provide($recurrence);
Expand All @@ -38,7 +35,7 @@ public function provide(Recurrence $recurrence): array
// Avoid duplicate datetime due to constraint updates
if (empty($filteredDatetimes) || $previousDatetime != $filteredDatetime) {
$filteredDatetimes[] = $filteredDatetime;
$previousDatetime = $filteredDatetime;
$previousDatetime = $filteredDatetime;
}
}

Expand Down
77 changes: 40 additions & 37 deletions src/Recurrence/Model/Frequency.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,35 @@

use Recurrence\Model\Exception\InvalidFrequencyOptionException;

/**
* TODO: replace by enum.
*/
class Frequency
{
public const FREQUENCY_YEARLY = 'YEARLY';
public const FREQUENCY_MONTHLY = 'MONTHLY';
public const FREQUENCY_WEEKLY = 'WEEKLY';
public const FREQUENCY_DAILY = 'DAILY';
public const FREQUENCY_HOURLY = 'HOURLY';
public const FREQUENCY_YEARLY = 'YEARLY';
public const FREQUENCY_MONTHLY = 'MONTHLY';
public const FREQUENCY_WEEKLY = 'WEEKLY';
public const FREQUENCY_DAILY = 'DAILY';
public const FREQUENCY_HOURLY = 'HOURLY';
public const FREQUENCY_MINUTELY = 'MINUTELY';
public const FREQUENCY_SECONDLY = 'SECONDLY';

public const DATEINTERVAL_YEARLY = 'P1Y';
public const DATEINTERVAL_MONTHLY = 'P1M';
public const DATEINTERVAL_WEEKLY = 'P1W';
public const DATEINTERVAL_DAILY = 'P1D';
public const DATEINTERVAL_HOURLY = 'PT1H';
public const DATEINTERVAL_MINUTELY = 'PT1M';
public const DATEINTERVAL_SECONDLY = 'PT1S';

public const DATETIME_YEARLY = '+1 years';
public const DATETIME_MONTHLY = '+1 months';
public const DATETIME_WEEKLY = '+1 weeks';
public const DATETIME_DAILY = '+1 days';
public const DATETIME_HOURLY = '+1 hours';
public const DATETIME_MINUTELY = '+1 minutes';
public const DATETIME_SECONDLY = '+1 seconds';

private array $frequencies = [
self::FREQUENCY_YEARLY,
self::FREQUENCY_MONTHLY,
Expand All @@ -24,38 +43,22 @@ class Frequency
self::FREQUENCY_SECONDLY,
];

public const DATEINTERVAL_YEARLY = 'P1Y';
public const DATEINTERVAL_MONTHLY = 'P1M';
public const DATEINTERVAL_WEEKLY = 'P1W';
public const DATEINTERVAL_DAILY = 'P1D';
public const DATEINTERVAL_HOURLY = 'PT1H';
public const DATEINTERVAL_MINUTELY = 'PT1M';
public const DATEINTERVAL_SECONDLY = 'PT1S';

private array $dateIntervalFrequencies = [
self::FREQUENCY_YEARLY => self::DATEINTERVAL_YEARLY,
self::FREQUENCY_MONTHLY => self::DATEINTERVAL_MONTHLY,
self::FREQUENCY_WEEKLY => self::DATEINTERVAL_WEEKLY,
self::FREQUENCY_DAILY => self::DATEINTERVAL_DAILY,
self::FREQUENCY_HOURLY => self::DATEINTERVAL_HOURLY,
self::FREQUENCY_YEARLY => self::DATEINTERVAL_YEARLY,
self::FREQUENCY_MONTHLY => self::DATEINTERVAL_MONTHLY,
self::FREQUENCY_WEEKLY => self::DATEINTERVAL_WEEKLY,
self::FREQUENCY_DAILY => self::DATEINTERVAL_DAILY,
self::FREQUENCY_HOURLY => self::DATEINTERVAL_HOURLY,
self::FREQUENCY_MINUTELY => self::DATEINTERVAL_MINUTELY,
self::FREQUENCY_SECONDLY => self::DATEINTERVAL_SECONDLY,
];

public const DATETIME_YEARLY = '+1 years';
public const DATETIME_MONTHLY = '+1 months';
public const DATETIME_WEEKLY = '+1 weeks';
public const DATETIME_DAILY = '+1 days';
public const DATETIME_HOURLY = '+1 hours';
public const DATETIME_MINUTELY = '+1 minutes';
public const DATETIME_SECONDLY = '+1 seconds';

private array $dateTimeFrequencies = [
self::FREQUENCY_YEARLY => self::DATETIME_YEARLY,
self::FREQUENCY_MONTHLY => self::DATETIME_MONTHLY,
self::FREQUENCY_WEEKLY => self::DATETIME_WEEKLY,
self::FREQUENCY_DAILY => self::DATETIME_DAILY,
self::FREQUENCY_HOURLY => self::DATETIME_HOURLY,
self::FREQUENCY_YEARLY => self::DATETIME_YEARLY,
self::FREQUENCY_MONTHLY => self::DATETIME_MONTHLY,
self::FREQUENCY_WEEKLY => self::DATETIME_WEEKLY,
self::FREQUENCY_DAILY => self::DATETIME_DAILY,
self::FREQUENCY_HOURLY => self::DATETIME_HOURLY,
self::FREQUENCY_MINUTELY => self::DATETIME_MINUTELY,
self::FREQUENCY_SECONDLY => self::DATETIME_SECONDLY,
];
Expand All @@ -64,13 +67,18 @@ class Frequency

public function __construct(string $frequencyName)
{
if (!in_array($frequencyName, $this->frequencies)) {
if (!in_array($frequencyName, $this->frequencies, true)) {
throw new InvalidFrequencyOptionException(sprintf('Invalid frequency name. Supported values are : %s', implode(', ', $this->frequencies)));
}

$this->frequencyName = $frequencyName;
}

public function __toString(): string
{
return $this->frequencyName;
}

public function convertToDateIntervalFormat(): string
{
return $this->dateIntervalFrequencies[$this->frequencyName];
Expand All @@ -80,9 +88,4 @@ public function convertToDateTimeFormat(): string
{
return $this->dateTimeFrequencies[$this->frequencyName];
}

public function __toString(): string
{
return $this->frequencyName;
}
}
Loading