default services conflit ?

This commit is contained in:
armansansd
2022-04-27 11:30:43 +02:00
parent 28190a5749
commit 8bb1064a3b
8132 changed files with 900138 additions and 426 deletions

View File

@@ -0,0 +1,48 @@
<?php
/*
* Copyright 2014 Shaun Simmons
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Recurr;
/**
* Class DateExclusion is a container for a single \DateTimeInterface.
*
* The purpose of this class is to hold a flag that specifies whether
* or not the \DateTimeInterface was created from a DATE only, or with a
* DATETIME.
*
* It also tracks whether or not the exclusion is explicitly set to UTC.
*
* @package Recurr
* @author Shaun Simmons <shaun@envysphere.com>
*/
class DateExclusion
{
/** @var \DateTimeInterface */
public $date;
/** @var bool Day of year */
public $hasTime;
/** @var bool */
public $isUtcExplicit;
/**
* Constructor
*
* @param \DateTimeInterface $date
* @param bool $hasTime
* @param bool $isUtcExplicit
*/
public function __construct(\DateTimeInterface $date, $hasTime = true, $isUtcExplicit = false)
{
$this->date = $date;
$this->hasTime = $hasTime;
$this->isUtcExplicit = $isUtcExplicit;
}
}

View File

@@ -0,0 +1,48 @@
<?php
/*
* Copyright 2015 Shaun Simmons
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Recurr;
/**
* Class DateInclusion is a container for a single \DateTimeInterface.
*
* The purpose of this class is to hold a flag that specifies whether
* or not the \DateTimeInterface was created from a DATE only, or with a
* DATETIME.
*
* It also tracks whether or not the inclusion is explicitly set to UTC.
*
* @package Recurr
* @author Shaun Simmons <shaun@envysphere.com>
*/
class DateInclusion
{
/** @var \DateTimeInterface */
public $date;
/** @var bool Day of year */
public $hasTime;
/** @var bool */
public $isUtcExplicit;
/**
* Constructor
*
* @param \DateTimeInterface $date
* @param bool $hasTime
* @param bool $isUtcExplicit
*/
public function __construct(\DateTimeInterface $date, $hasTime = true, $isUtcExplicit = false)
{
$this->date = $date;
$this->hasTime = $hasTime;
$this->isUtcExplicit = $isUtcExplicit;
}
}

View File

@@ -0,0 +1,73 @@
<?php
/*
* Copyright 2013 Shaun Simmons
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* Based on rrule.js
* Copyright 2010, Jakub Roztocil and Lars Schoning
* https://github.com/jkbr/rrule/blob/master/LICENCE
*/
namespace Recurr;
/**
* Class DateInfo is responsible for holding information based on a particular
* date that is applicable to a Rule.
*
* @package Recurr
* @author Shaun Simmons <shaun@envysphere.com>
*/
class DateInfo
{
/** @var \DateTime */
public $dt;
/**
* @var int Number of days in the month.
*/
public $monthLength;
/**
* @var int Number of days in the year (365 normally, 366 on leap years)
*/
public $yearLength;
/**
* @var int Number of days in the next year (365 normally, 366 on leap years)
*/
public $nextYearLength;
/**
* @var array Day of year of last day of each month.
*/
public $mRanges;
/** @var int Day of week */
public $dayOfWeek;
/** @var int Day of week of the year's first day */
public $dayOfWeekYearDay1;
/**
* @var array Month number for each day of the year.
*/
public $mMask;
/**
* @var array Month-daynumber for each day of the year.
*/
public $mDayMask;
/**
* @var array Month-daynumber for each day of the year (in reverse).
*/
public $mDayMaskNeg;
/**
* @var array Day of week (0-6) for each day of the year, 0 being Monday
*/
public $wDayMask;
}

View File

@@ -0,0 +1,571 @@
<?php
/*
* Copyright 2013 Shaun Simmons
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* Based on rrule.js
* Copyright 2010, Jakub Roztocil and Lars Schoning
* https://github.com/jkbr/rrule/blob/master/LICENCE
*
* Based on python-dateutil - Extensions to the standard Python datetime module.
* Copyright (c) 2003-2011 - Gustavo Niemeyer <gustavo@niemeyer.net>
* Copyright (c) 2012 - Tomi Pieviläinen <tomi.pievilainen@iki.fi>
*/
namespace Recurr;
/**
* Class DateUtil is responsible for providing utilities applicable to Rules.
*
* @package Recurr
* @author Shaun Simmons <shaun@envysphere.com>
*/
class DateUtil
{
public static $leapBug = null;
public static $monthEndDoY366 = array(
0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366
);
public static $monthEndDoY365 = array(
0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365
);
public static $wDayMask = array(
0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6,
0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6,
0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6,
0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6,
0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6,
0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6,
0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6,
0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6,
0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6,
0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6,
0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6,
0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6,
0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6,
0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6,
0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6,
0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6,
0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6,
0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6,
0, 1, 2, 3, 4, 5, 6,
);
/**
* Get an object containing info for a particular date
*
* @param \DateTimeInterface $dt
*
* @return DateInfo
*/
public static function getDateInfo(\DateTimeInterface $dt)
{
$i = new DateInfo();
$i->dt = $dt;
$i->dayOfWeek = self::getDayOfWeek($dt);
$i->monthLength = $dt->format('t');
$i->yearLength = self::getYearLength($dt);
$i->mMask = self::getMonthMask($dt);
$i->mDayMask = self::getMonthDaysMask($dt);
$i->mDayMaskNeg = self::getMonthDaysMask($dt, true);
if ($i->yearLength == 365) {
$i->mRanges = self::$monthEndDoY365;
} else {
$i->mRanges = self::$monthEndDoY366;
}
$tmpDt = clone $dt;
$tmpDt = $tmpDt->setDate($dt->format('Y') + 1, 1, 1);
$i->nextYearLength = self::getYearLength($tmpDt);
$tmpDt = clone $dt;
$tmpDt = $tmpDt->setDate($dt->format('Y'), 1, 1);
$i->dayOfWeekYearDay1 = self::getDayOfWeek($tmpDt);
$i->wDayMask = array_slice(
self::$wDayMask,
$i->dayOfWeekYearDay1
);
return $i;
}
/**
* Get an array of DOY (Day of Year) for each day in a particular week.
*
* @param \DateTimeInterface $dt
* @param \DateTimeInterface $start
* @param null|Rule $rule
* @param null|DateInfo $dtInfo
*
* @return DaySet
*/
public static function getDaySetOfWeek(
\DateTimeInterface $dt,
\DateTimeInterface $start,
Rule $rule = null,
DateInfo $dtInfo = null
)
{
$start = clone $dt;
$start = $start->setDate($start->format('Y'), 1, 1);
$diff = $dt->diff($start);
$start = $diff->days;
$set = array();
for ($i = $start, $k = 0; $k < 7; $k++) {
$set[] = $i;
++$i;
if (null !== $dtInfo && null !== $rule && $dtInfo->wDayMask[$i] == $rule->getWeekStartAsNum()) {
break;
}
}
$obj = new DaySet($set, $start, $i);
return $obj;
}
/**
* @param Rule $rule
* @param \DateTimeInterface $dt
* @param DateInfo $dtInfo
* @param \DateTimeInterface $start
*
* @return DaySet
*/
public static function getDaySet(Rule $rule, \DateTimeInterface $dt, DateInfo $dtInfo, $start)
{
switch ($rule->getFreq()) {
case Frequency::SECONDLY:
return self::getDaySetOfDay($dt, $start, $rule, $dtInfo);
break;
case Frequency::MINUTELY:
return self::getDaySetOfDay($dt, $start, $rule, $dtInfo);
break;
case Frequency::HOURLY:
return self::getDaySetOfDay($dt, $start, $rule, $dtInfo);
break;
case Frequency::DAILY:
return self::getDaySetOfDay($dt, $start, $rule, $dtInfo);
break;
case Frequency::WEEKLY:
return self::getDaySetOfWeek($dt, $start, $rule, $dtInfo);
case Frequency::MONTHLY:
return self::getDaySetOfMonth($dt, $start, $rule, $dtInfo);
case Frequency::YEARLY:
return self::getDaySetOfYear($dt, $start, $rule, $dtInfo);
}
throw new \RuntimeException('Invalid freq.');
}
/**
* Get an array of DOY (Day of Year) for each day in a particular year.
*
* @param \DateTimeInterface $dt The datetime
*
* @return DaySet
*/
public static function getDaySetOfYear(\DateTimeInterface $dt)
{
$yearLen = self::getYearLength($dt);
$set = range(0, $yearLen - 1);
return new DaySet($set, 0, $yearLen);
}
/**
* Get an array of DOY (Day of Year) for each day in a particular month.
*
* @param \DateTimeInterface $dt The datetime
*
* @return DaySet
*/
public static function getDaySetOfMonth(\DateTimeInterface $dt)
{
$dateInfo = self::getDateInfo($dt);
$monthNum = $dt->format('n');
$start = $dateInfo->mRanges[$monthNum - 1];
$end = $dateInfo->mRanges[$monthNum];
$days = range(0, $dt->format('t') - 1);
$set = range($start, $end - 1);
$set = array_combine($days, $set);
$obj = new DaySet($set, $start, $end - 1);
return $obj;
}
/**
* Get an array of DOY (Day of Year) for each day in a particular month.
*
* @param \DateTimeInterface $dt The datetime
*
* @return DaySet
*/
public static function getDaySetOfDay(\DateTimeInterface $dt)
{
$dayOfYear = $dt->format('z');
if (self::isLeapYearDate($dt) && self::hasLeapYearBug() && $dt->format('nj') > 229) {
$dayOfYear -= 1;
}
$start = $dayOfYear;
$end = $dayOfYear;
$set = range($start, $end);
$obj = new DaySet($set, $start, $end + 1);
return $obj;
}
/**
* @param Rule $rule
* @param \DateTimeInterface $dt
*
* @return array
*/
public static function getTimeSetOfHour(Rule $rule, \DateTimeInterface $dt)
{
$set = array();
$hour = $dt->format('G');
$byMinute = $rule->getByMinute();
$bySecond = $rule->getBySecond();
if (empty($byMinute)) {
$byMinute = array($dt->format('i'));
}
if (empty($bySecond)) {
$bySecond = array($dt->format('s'));
}
foreach ($byMinute as $minute) {
foreach ($bySecond as $second) {
$set[] = new Time($hour, $minute, $second);
}
}
return $set;
}
/**
* @param Rule $rule
* @param \DateTimeInterface $dt
*
* @return array
*/
public static function getTimeSetOfMinute(Rule $rule, \DateTimeInterface $dt)
{
$set = array();
$hour = $dt->format('G');
$minute = $dt->format('i');
$bySecond = $rule->getBySecond();
if (empty($bySecond)) {
$bySecond = array($dt->format('s'));
}
foreach ($bySecond as $second) {
$set[] = new Time($hour, $minute, $second);
}
return $set;
}
/**
* @param \DateTimeInterface $dt
*
* @return array
*/
public static function getTimeSetOfSecond(\DateTimeInterface $dt)
{
return array(new Time($dt->format('G'), $dt->format('i'), $dt->format('s')));
}
/**
* @param Rule $rule
* @param \DateTimeInterface $dt
*
* @return array
*/
public static function getTimeSet(Rule $rule, \DateTimeInterface $dt)
{
$set = array();
if (null === $rule || $rule->getFreq() >= Frequency::HOURLY) {
return $set;
}
$byHour = $rule->getByHour();
$byMinute = $rule->getByMinute();
$bySecond = $rule->getBySecond();
if (empty($byHour)) {
$byHour = array($dt->format('G'));
}
if (empty($byMinute)) {
$byMinute = array($dt->format('i'));
}
if (empty($bySecond)) {
$bySecond = array($dt->format('s'));
}
foreach ($byHour as $hour) {
foreach ($byMinute as $minute) {
foreach ($bySecond as $second) {
$set[] = new Time($hour, $minute, $second);
}
}
}
return $set;
}
/**
* Get a reference array with the day number for each day of each month.
*
* @param \DateTimeInterface $dt The datetime
* @param bool $negative
*
* @return array
*/
public static function getMonthDaysMask(\DateTimeInterface $dt, $negative = false)
{
if ($negative) {
$m29 = range(-29, -1);
$m30 = range(-30, -1);
$m31 = range(-31, -1);
} else {
$m29 = range(1, 29);
$m30 = range(1, 30);
$m31 = range(1, 31);
}
$mask = array_merge(
$m31, // Jan (31)
$m29, // Feb (28)
$m31, // Mar (31)
$m30, // Apr (30)
$m31, // May (31)
$m30, // Jun (30)
$m31, // Jul (31)
$m31, // Aug (31)
$m30, // Sep (30)
$m31, // Oct (31)
$m30, // Nov (30)
$m31, // Dec (31)
array_slice(
$m31,
0,
7
)
);
if (self::isLeapYearDate($dt)) {
return $mask;
} else {
if ($negative) {
$mask = array_merge(array_slice($mask, 0, 31), array_slice($mask, 32));
} else {
$mask = array_merge(array_slice($mask, 0, 59), array_slice($mask, 60));
}
return $mask;
}
}
public static function getMonthMask(\DateTimeInterface $dt)
{
if (self::isLeapYearDate($dt)) {
return array_merge(
array_fill(0, 31, 1), // Jan (31)
array_fill(0, 29, 2), // Feb (29)
array_fill(0, 31, 3), // Mar (31)
array_fill(0, 30, 4), // Apr (30)
array_fill(0, 31, 5), // May (31)
array_fill(0, 30, 6), // Jun (30)
array_fill(0, 31, 7), // Jul (31)
array_fill(0, 31, 8), // Aug (31)
array_fill(0, 30, 9), // Sep (30)
array_fill(0, 31, 10), // Oct (31)
array_fill(0, 30, 11), // Nov (30)
array_fill(0, 31, 12), // Dec (31)
array_fill(0, 7, 1)
);
} else {
return array_merge(
array_fill(0, 31, 1), // Jan (31)
array_fill(0, 28, 2), // Feb (28)
array_fill(0, 31, 3), // Mar (31)
array_fill(0, 30, 4), // Apr (30)
array_fill(0, 31, 5), // May (31)
array_fill(0, 30, 6), // Jun (30)
array_fill(0, 31, 7), // Jul (31)
array_fill(0, 31, 8), // Aug (31)
array_fill(0, 30, 9), // Sep (30)
array_fill(0, 31, 10), // Oct (31)
array_fill(0, 30, 11), // Nov (30)
array_fill(0, 31, 12), // Dec (31)
array_fill(0, 7, 1)
);
}
}
public static function getDateTimeByDayOfYear($dayOfYear, $year, \DateTimeZone $timezone)
{
$dtTmp = new \DateTime('now', $timezone);
$dtTmp = $dtTmp->setDate($year, 1, 1);
$dtTmp = $dtTmp->modify("+$dayOfYear day");
return $dtTmp;
}
public static function hasLeapYearBug()
{
$leapBugTest = \DateTime::createFromFormat('Y-m-d', '2016-03-21');
return $leapBugTest->format('z') != '80';
}
/**
* closure/goog/math/math.js:modulo
* Copyright 2006 The Closure Library Authors.
*
* The % operator in PHP returns the remainder of a / b, but differs from
* some other languages in that the result will have the same sign as the
* dividend. For example, -1 % 8 == -1, whereas in some other languages
* (such as Python) the result would be 7. This function emulates the more
* correct modulo behavior, which is useful for certain applications such as
* calculating an offset index in a circular list.
*
* @param int $a The dividend.
* @param int $b The divisor.
*
* @return int $a % $b where the result is between 0 and $b
* (either 0 <= x < $b
* or $b < x <= 0, depending on the sign of $b).
*/
public static function pymod($a, $b)
{
$x = $a % $b;
// If $x and $b differ in sign, add $b to wrap the result to the correct sign.
return ($x * $b < 0) ? $x + $b : $x;
}
/**
* Alias method to determine if a date falls within a leap year.
*
* @param \DateTimeInterface $dt
*
* @return bool
*/
public static function isLeapYearDate(\DateTimeInterface $dt)
{
return $dt->format('L') ? true : false;
}
/**
* Alias method to determine if a year is a leap year.
*
* @param int $year
*
* @return bool
*/
public static function isLeapYear($year)
{
$isDivisBy4 = $year % 4 == 0 ? true : false;
$isDivisBy100 = $year % 100 == 0? true : false;
$isDivisBy400 = $year % 400 == 0 ? true : false;
// http://en.wikipedia.org/wiki/February_29
if ($isDivisBy100 && !$isDivisBy400) {
return false;
}
return $isDivisBy4;
}
/**
* Method to determine the day of the week from MO-SU.
*
* MO = Monday
* TU = Tuesday
* WE = Wednesday
* TH = Thursday
* FR = Friday
* SA = Saturday
* SU = Sunday
*
* @param \DateTimeInterface $dt
*
* @return string
*/
public static function getDayOfWeekAsText(\DateTimeInterface $dt)
{
$dayOfWeek = $dt->format('w') - 1;
if ($dayOfWeek < 0) {
$dayOfWeek = 6;
}
$map = array('MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU');
return $map[$dayOfWeek];
}
/**
* Alias method to determine the day of the week from 0-6.
*
* 0 = Monday
* 1 = Tuesday
* 2 = Wednesday
* 3 = Thursday
* 4 = Friday
* 5 = Saturday
* 6 = Sunday
*
* @param \DateTimeInterface $dt
*
* @return int
*/
public static function getDayOfWeek(\DateTimeInterface $dt)
{
$dayOfWeek = $dt->format('w') - 1;
if ($dayOfWeek < 0) {
$dayOfWeek = 6;
}
return $dayOfWeek;
}
/**
* Get the number of days in a year.
*
* @param \DateTimeInterface $dt
*
* @return int
*/
public static function getYearLength(\DateTimeInterface $dt)
{
return self::isLeapYearDate($dt) ? 366 : 365;
}
}

View File

@@ -0,0 +1,46 @@
<?php
/*
* Copyright 2013 Shaun Simmons
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* Based on rrule.js
* Copyright 2010, Jakub Roztocil and Lars Schoning
* https://github.com/jkbr/rrule/blob/master/LICENCE
*/
namespace Recurr;
/**
* Class DaySet is a container for a set and its meta.
*
* @package Recurr
* @author Shaun Simmons <shaun@envysphere.com>
*/
class DaySet
{
/** @var array */
public $set;
/** @var int Day of year */
public $start;
/** @var int Day of year */
public $end;
/**
* Constructor
*
* @param array $set Set of days
* @param int $start Day of year of start day
* @param int $end Day of year of end day
*/
public function __construct($set, $start, $end)
{
$this->set = $set;
$this->start = $start;
$this->end = $end;
}
}

View File

@@ -0,0 +1,17 @@
<?php
/*
* Copyright 2013 Shaun Simmons
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Recurr;
/**
* The base Recurr exception class
*/
class Exception extends \Exception
{
}

View File

@@ -0,0 +1,20 @@
<?php
/*
* Copyright 2013 Shaun Simmons
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Recurr\Exception;
use Recurr\Exception;
/**
* @package Recurr\Exception
* @author Shaun Simmons <shaun@envysphere.com>
*/
class InvalidArgument extends Exception
{
}

View File

@@ -0,0 +1,20 @@
<?php
/*
* Copyright 2013 Shaun Simmons
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Recurr\Exception;
use Recurr\Exception;
/**
* @package Recurr\Exception
* @author Shaun Simmons <shaun@envysphere.com>
*/
class InvalidRRule extends Exception
{
}

View File

@@ -0,0 +1,20 @@
<?php
/*
* Copyright 2013 Shaun Simmons
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Recurr\Exception;
use Recurr\Exception;
/**
* @package Recurr\Exception
* @author Shaun Simmons <shaun@envysphere.com>
*/
class InvalidWeekday extends Exception
{
}

View File

@@ -0,0 +1,25 @@
<?php
/*
* Copyright 2014 Shaun Simmons
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* Based on:
* rrule.js - Library for working with recurrence rules for calendar dates.
* Copyright 2010, Jakub Roztocil and Lars Schoning
* https://github.com/jkbr/rrule/blob/master/LICENCE
*/
namespace Recurr;
class Frequency {
const YEARLY = 0;
const MONTHLY = 1;
const WEEKLY = 2;
const DAILY = 3;
const HOURLY = 4;
const MINUTELY = 5;
const SECONDLY = 6;
}

View File

@@ -0,0 +1,90 @@
<?php
/*
* Copyright 2014 Shaun Simmons
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Recurr;
/**
* Class Recurrence is responsible for storing the start and end \DateTime of
* a specific recurrence in a RRULE.
*
* @package Recurr
* @author Shaun Simmons <shaun@envysphere.com>
*/
class Recurrence
{
/** @var \DateTimeInterface */
protected $start;
/** @var \DateTimeInterface */
protected $end;
/** @var int */
protected $index;
public function __construct(\DateTimeInterface $start = null, \DateTimeInterface $end = null, $index = 0)
{
if ($start instanceof \DateTimeInterface) {
$this->setStart($start);
}
if ($end instanceof \DateTimeInterface) {
$this->setEnd($end);
}
$this->index = $index;
}
/**
* @return \DateTimeInterface
*/
public function getStart()
{
return $this->start;
}
/**
* @param \DateTime $start
*/
public function setStart($start)
{
$this->start = $start;
}
/**
* @return \DateTime
*/
public function getEnd()
{
return $this->end;
}
/**
* @param \DateTime $end
*/
public function setEnd($end)
{
$this->end = $end;
}
/**
* @return int
*/
public function getIndex()
{
return $this->index;
}
/**
* @param int $index
*/
public function setIndex($index)
{
$this->index = $index;
}
}

View File

@@ -0,0 +1,153 @@
<?php
/*
* Copyright 2014 Shaun Simmons
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Recurr;
use \Doctrine\Common\Collections\ArrayCollection as BaseCollection;
/**
* @package Recurr
* @author Shaun Simmons <shaun@envysphere.com>
*/
class RecurrenceCollection extends BaseCollection
{
/**
* @param \DateTimeInterface $after
* @param \DateTimeInterface $before
* @param bool $inc Include $after or $before if they happen to be a recurrence.
*
* @return RecurrenceCollection
*/
public function startsBetween(\DateTimeInterface $after, \DateTimeInterface $before, $inc = false)
{
return $this->filter(
function ($recurrence) use ($after, $before, $inc) {
/** @var $recurrence Recurrence */
$start = $recurrence->getStart();
if ($inc) {
return $start >= $after && $start <= $before;
}
return $start > $after && $start < $before;
}
);
}
/**
* @param \DateTimeInterface $before
* @param bool $inc Include $before if it is a recurrence.
*
* @return RecurrenceCollection
*/
public function startsBefore(\DateTimeInterface $before, $inc = false)
{
return $this->filter(
function ($recurrence) use ($before, $inc) {
/** @var $recurrence Recurrence */
$start = $recurrence->getStart();
if ($inc) {
return $start <= $before;
}
return $start < $before;
}
);
}
/**
* @param \DateTimeInterface $after
* @param bool $inc Include $after if it a recurrence.
*
* @return RecurrenceCollection
*/
public function startsAfter(\DateTimeInterface $after, $inc = false)
{
return $this->filter(
function ($recurrence) use ($after, $inc) {
/** @var $recurrence Recurrence */
$start = $recurrence->getStart();
if ($inc) {
return $start >= $after;
}
return $start > $after;
}
);
}
/**
* @param \DateTimeInterface $after
* @param \DateTimeInterface $before
* @param bool $inc Include $after or $before if they happen to be a recurrence.
*
* @return RecurrenceCollection
*/
public function endsBetween(\DateTimeInterface $after, \DateTimeInterface $before, $inc = false)
{
return $this->filter(
function ($recurrence) use ($after, $before, $inc) {
/** @var $recurrence Recurrence */
$end = $recurrence->getEnd();
if ($inc) {
return $end >= $after && $end <= $before;
}
return $end > $after && $end < $before;
}
);
}
/**
* @param \DateTimeInterface $before
* @param bool $inc Include $before if it is a recurrence.
*
* @return RecurrenceCollection
*/
public function endsBefore(\DateTimeInterface $before, $inc = false)
{
return $this->filter(
function ($recurrence) use ($before, $inc) {
/** @var $recurrence Recurrence */
$end = $recurrence->getEnd();
if ($inc) {
return $end <= $before;
}
return $end < $before;
}
);
}
/**
* @param \DateTimeInterface $after
* @param bool $inc Include $after if it a recurrence.
*
* @return RecurrenceCollection
*/
public function endsAfter(\DateTimeInterface $after, $inc = false)
{
return $this->filter(
function ($recurrence) use ($after, $inc) {
/** @var $recurrence Recurrence */
$end = $recurrence->getEnd();
if ($inc) {
return $end >= $after;
}
return $end > $after;
}
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,39 @@
<?php
/*
* Copyright 2013 Shaun Simmons
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* Based on rrule.js
* Copyright 2010, Jakub Roztocil and Lars Schoning
* https://github.com/jkbr/rrule/blob/master/LICENCE
*/
namespace Recurr;
/**
* Class Time is a storage container for a time of day.
*
* @package Recurr
* @author Shaun Simmons <shaun@envysphere.com>
*/
class Time
{
/** @var int */
public $hour;
/** @var int */
public $minute;
/** @var int */
public $second;
public function __construct($hour, $minute, $second)
{
$this->hour = $hour;
$this->minute = $minute;
$this->second = $second;
}
}

View File

@@ -0,0 +1,736 @@
<?php
/*
* Copyright 2013 Shaun Simmons
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* Based on:
* rrule.js - Library for working with recurrence rules for calendar dates.
* Copyright 2010, Jakub Roztocil and Lars Schoning
* https://github.com/jkbr/rrule/blob/master/LICENCE
*/
namespace Recurr\Transformer;
use Recurr\DateExclusion;
use Recurr\DateInclusion;
use Recurr\Exception\InvalidWeekday;
use Recurr\Frequency;
use Recurr\Recurrence;
use Recurr\RecurrenceCollection;
use Recurr\Rule;
use Recurr\Time;
use Recurr\Weekday;
use Recurr\DateUtil;
/**
* This class is responsible for transforming a Rule in to an array
* of \DateTimeInterface objects.
*
* If a recurrence rule is infinitely recurring, a virtual limit is imposed.
*
* @package Recurr
* @author Shaun Simmons <shaun@envysphere.com>
*/
class ArrayTransformer
{
/** @var ArrayTransformerConfig */
protected $config;
/**
* Some versions of PHP are affected by a bug where
* \DateTimeInterface::createFromFormat('z Y', ...) does not account for leap years.
*
* @var bool
*/
protected $leapBug = false;
/**
* Construct a new ArrayTransformer
*
* @param ArrayTransformerConfig $config
*/
public function __construct(ArrayTransformerConfig $config = null)
{
if (!$config instanceof ArrayTransformerConfig) {
$config = new ArrayTransformerConfig();
}
$this->config = $config;
$this->leapBug = DateUtil::hasLeapYearBug();
}
/**
* @param ArrayTransformerConfig $config
*/
public function setConfig($config)
{
$this->config = $config;
}
/**
* Transform a Rule in to an array of \DateTimeInterface objects
*
* @param Rule $rule the Rule object
* @param ConstraintInterface|null $constraint Potential recurrences must pass the constraint, else
* they will not be included in the returned collection.
* @param bool $countConstraintFailures Whether recurrences that fail the constraint's test
* should count towards a rule's COUNT limit.
*
* @return RecurrenceCollection|Recurrence[]
* @throws InvalidWeekday
*/
public function transform(Rule $rule, ConstraintInterface $constraint = null, $countConstraintFailures = true)
{
$start = $rule->getStartDate();
$end = $rule->getEndDate();
$until = $rule->getUntil();
if (null === $start) {
$start = new \DateTime(
'now', $until instanceof \DateTimeInterface ? $until->getTimezone() : null
);
}
if (null === $end) {
$end = $start;
}
$durationInterval = $start->diff($end);
$startDay = $start->format('j');
$startMonthLength = $start->format('t');
$fixLastDayOfMonth = false;
$dt = clone $start;
$maxCount = $rule->getCount();
$vLimit = $this->config->getVirtualLimit();
$freq = $rule->getFreq();
$weekStart = $rule->getWeekStartAsNum();
$bySecond = $rule->getBySecond();
$byMinute = $rule->getByMinute();
$byHour = $rule->getByHour();
$byMonth = $rule->getByMonth();
$byWeekNum = $rule->getByWeekNumber();
$byYearDay = $rule->getByYearDay();
$byMonthDay = $rule->getByMonthDay();
$byMonthDayNeg = array();
$byWeekDay = $rule->getByDayTransformedToWeekdays();
$byWeekDayRel = array();
$bySetPos = $rule->getBySetPosition();
$implicitByMonthDay = false;
if (!(!empty($byWeekNum) || !empty($byYearDay) || !empty($byMonthDay) || !empty($byWeekDay))) {
switch ($freq) {
case Frequency::YEARLY:
if (empty($byMonth)) {
$byMonth = array($start->format('n'));
}
if ($startDay > 28) {
$fixLastDayOfMonth = true;
}
$implicitByMonthDay = true;
$byMonthDay = array($startDay);
break;
case Frequency::MONTHLY:
if ($startDay > 28) {
$fixLastDayOfMonth = true;
}
$implicitByMonthDay = true;
$byMonthDay = array($startDay);
break;
case Frequency::WEEKLY:
$byWeekDay = array(
new Weekday(
DateUtil::getDayOfWeek($start), null
)
);
break;
}
}
if (!$this->config->isLastDayOfMonthFixEnabled()) {
$fixLastDayOfMonth = false;
}
if (is_array($byMonthDay) && count($byMonthDay)) {
foreach ($byMonthDay as $idx => $day) {
if ($day < 0) {
unset($byMonthDay[$idx]);
$byMonthDayNeg[] = $day;
}
}
}
if (!empty($byWeekDay)) {
foreach ($byWeekDay as $idx => $day) {
/** @var $day Weekday */
if (!empty($day->num)) {
$byWeekDayRel[] = $day;
unset($byWeekDay[$idx]);
} else {
$byWeekDay[$idx] = $day->weekday;
}
}
}
if (empty($byYearDay)) {
$byYearDay = null;
}
if (empty($byMonthDay)) {
$byMonthDay = null;
}
if (empty($byMonthDayNeg)) {
$byMonthDayNeg = null;
}
if (empty($byWeekDay)) {
$byWeekDay = null;
}
if (!count($byWeekDayRel)) {
$byWeekDayRel = null;
}
$year = $dt->format('Y');
$month = $dt->format('n');
$hour = $dt->format('G');
$minute = $dt->format('i');
$second = $dt->format('s');
$dates = array();
$total = 1;
$count = $maxCount;
$continue = true;
$iterations = 0;
while ($continue) {
$dtInfo = DateUtil::getDateInfo($dt);
$tmp = DateUtil::getDaySet($rule, $dt, $dtInfo, $start);
$daySet = $tmp->set;
$daySetStart = $tmp->start;
$daySetEnd = $tmp->end;
$wNoMask = array();
$wDayMaskRel = array();
$timeSet = DateUtil::getTimeSet($rule, $dt);
if ($freq >= Frequency::HOURLY) {
if (
($freq >= Frequency::HOURLY && !empty($byHour) && !in_array($hour, $byHour))
|| ($freq >= Frequency::MINUTELY && !empty($byMinute) && !in_array($minute, $byMinute))
|| ($freq >= Frequency::SECONDLY && !empty($bySecond) && !in_array($second, $bySecond))
) {
$timeSet = array();
} else {
switch ($freq) {
case Frequency::HOURLY:
$timeSet = DateUtil::getTimeSetOfHour($rule, $dt);
break;
case Frequency::MINUTELY:
$timeSet = DateUtil::getTimeSetOfMinute($rule, $dt);
break;
case Frequency::SECONDLY:
$timeSet = DateUtil::getTimeSetOfSecond($dt);
break;
}
}
}
// Handle byWeekNum
if (!empty($byWeekNum)) {
$no1WeekStart = $firstWeekStart = DateUtil::pymod(7 - $dtInfo->dayOfWeekYearDay1 + $weekStart, 7);
if ($no1WeekStart >= 4) {
$no1WeekStart = 0;
$wYearLength = $dtInfo->yearLength + DateUtil::pymod(
$dtInfo->dayOfWeekYearDay1 - $weekStart,
7
);
} else {
$wYearLength = $dtInfo->yearLength - $no1WeekStart;
}
$div = floor($wYearLength / 7);
$mod = DateUtil::pymod($wYearLength, 7);
$numWeeks = floor($div + ($mod / 4));
foreach ($byWeekNum as $weekNum) {
if ($weekNum < 0) {
$weekNum += $numWeeks + 1;
}
if (!(0 < $weekNum && $weekNum <= $numWeeks)) {
continue;
}
if ($weekNum > 1) {
$offset = $no1WeekStart + ($weekNum - 1) * 7;
if ($no1WeekStart != $firstWeekStart) {
$offset -= 7 - $firstWeekStart;
}
} else {
$offset = $no1WeekStart;
}
for ($i = 0; $i < 7; $i++) {
$wNoMask[] = $offset;
$offset++;
if ($dtInfo->wDayMask[$offset] == $weekStart) {
break;
}
}
}
// Check week number 1 of next year as well
if (in_array(1, $byWeekNum)) {
$offset = $no1WeekStart + $numWeeks * 7;
if ($no1WeekStart != $firstWeekStart) {
$offset -= 7 - $firstWeekStart;
}
// If week starts in next year, we don't care about it.
if ($offset < $dtInfo->yearLength) {
for ($k = 0; $k < 7; $k++) {
$wNoMask[] = $offset;
$offset += 1;
if ($dtInfo->wDayMask[$offset] == $weekStart) {
break;
}
}
}
}
if ($no1WeekStart) {
// Check last week number of last year as well.
// If $no1WeekStart is 0, either the year started on week start,
// or week number 1 got days from last year, so there are no
// days from last year's last week number in this year.
if (!in_array(-1, $byWeekNum)) {
$dtTmp = new \DateTime();
$dtTmp = $dtTmp->setDate($year - 1, 1, 1);
$lastYearWeekDay = DateUtil::getDayOfWeek($dtTmp);
$lastYearNo1WeekStart = DateUtil::pymod(7 - $lastYearWeekDay + $weekStart, 7);
$lastYearLength = DateUtil::getYearLength($dtTmp);
if ($lastYearNo1WeekStart >= 4) {
$lastYearNo1WeekStart = 0;
$lastYearNumWeeks = floor(
52 + DateUtil::pymod(
$lastYearLength + DateUtil::pymod(
$lastYearWeekDay - $weekStart,
7
),
7
) / 4
);
} else {
$lastYearNumWeeks = floor(
52 + DateUtil::pymod(
$dtInfo->yearLength - $no1WeekStart,
7
) / 4
);
}
} else {
$lastYearNumWeeks = -1;
}
if (in_array($lastYearNumWeeks, $byWeekNum)) {
for ($i = 0; $i < $no1WeekStart; $i++) {
$wNoMask[] = $i;
}
}
}
}
// Handle relative weekdays (e.g. 3rd Friday of month)
if (!empty($byWeekDayRel)) {
$ranges = array();
if (Frequency::YEARLY == $freq) {
if (!empty($byMonth)) {
foreach ($byMonth as $mo) {
$ranges[] = array_slice($dtInfo->mRanges, $mo - 1, 2);
}
} else {
$ranges[] = array(0, $dtInfo->yearLength);
}
} elseif (Frequency::MONTHLY == $freq) {
$ranges[] = array_slice($dtInfo->mRanges, $month - 1, 2);
}
if (!empty($ranges)) {
foreach ($ranges as $range) {
$rangeStart = $range[0];
$rangeEnd = $range[1];
--$rangeEnd;
reset($byWeekDayRel);
foreach ($byWeekDayRel as $weekday) {
/** @var Weekday $weekday */
if ($weekday->num < 0) {
$i = $rangeEnd + ($weekday->num + 1) * 7;
$i -= DateUtil::pymod(
$dtInfo->wDayMask[$i] - $weekday->weekday,
7
);
} else {
$i = $rangeStart + ($weekday->num - 1) * 7;
$i += DateUtil::pymod(
7 - $dtInfo->wDayMask[$i] + $weekday->weekday,
7
);
}
if ($rangeStart <= $i && $i <= $rangeEnd) {
$wDayMaskRel[] = $i;
}
}
}
}
}
$numMatched = 0;
foreach ($daySet as $i => $dayOfYear) {
$dayOfMonth = $dtInfo->mDayMask[$dayOfYear];
$ifByMonth = $byMonth !== null && !in_array($dtInfo->mMask[$dayOfYear], $byMonth);
$ifByWeekNum = $byWeekNum !== null && !in_array($i, $wNoMask);
$ifByYearDay = $byYearDay !== null && (
(
$i < $dtInfo->yearLength &&
!in_array($i + 1, $byYearDay) &&
!in_array(-$dtInfo->yearLength + $i, $byYearDay)
) ||
(
$i >= $dtInfo->yearLength &&
!in_array($i + 1 - $dtInfo->yearLength, $byYearDay) &&
!in_array(-$dtInfo->nextYearLength + $i - $dtInfo->yearLength, $byYearDay)
)
);
$ifByMonthDay = $byMonthDay !== null && !in_array($dtInfo->mDayMask[$dayOfYear], $byMonthDay);
// Handle "last day of next month" problem.
if ($fixLastDayOfMonth
&& $ifByMonthDay
&& $implicitByMonthDay
&& $startMonthLength > $dtInfo->monthLength
&& $dayOfMonth == $dtInfo->monthLength
&& $dayOfMonth < $startMonthLength
&& !$numMatched
) {
$ifByMonthDay = false;
}
$ifByMonthDayNeg = $byMonthDayNeg !== null
&& !in_array($dtInfo->mDayMaskNeg[$dayOfYear], $byMonthDayNeg);
$ifByDay = $byWeekDay !== null && count($byWeekDay)
&& !in_array($dtInfo->wDayMask[$dayOfYear], $byWeekDay);
$ifWDayMaskRel = $byWeekDayRel !== null && !in_array($dayOfYear, $wDayMaskRel);
if ($byMonthDay !== null && $byMonthDayNeg !== null) {
if ($ifByMonthDay && $ifByMonthDayNeg) {
unset($daySet[$i]);
}
} elseif ($ifByMonth || $ifByWeekNum || $ifByYearDay || $ifByMonthDay || $ifByMonthDayNeg || $ifByDay || $ifWDayMaskRel) {
unset($daySet[$i]);
} else {
++$numMatched;
}
}
if (!empty($bySetPos) && !empty($daySet)) {
$datesAdj = array();
$tmpDaySet = array_combine($daySet, $daySet);
foreach ($bySetPos as $setPos) {
if ($setPos < 0) {
$dayPos = floor($setPos / count($timeSet));
$timePos = DateUtil::pymod($setPos, count($timeSet));
} else {
$dayPos = floor(($setPos - 1) / count($timeSet));
$timePos = DateUtil::pymod(($setPos - 1), count($timeSet));
}
$tmp = array();
for ($k = $daySetStart; $k <= $daySetEnd; $k++) {
if (!array_key_exists($k, $tmpDaySet)) {
continue;
}
$tmp[] = $tmpDaySet[$k];
}
if ($dayPos < 0) {
$nextInSet = array_slice($tmp, $dayPos, 1);
if (count($nextInSet) === 0) {
continue;
}
$nextInSet = $nextInSet[0];
} else {
$nextInSet = isset($tmp[$dayPos]) ? $tmp[$dayPos] : null;
}
if (null !== $nextInSet) {
/** @var Time $time */
$time = $timeSet[$timePos];
$dtTmp = DateUtil::getDateTimeByDayOfYear($nextInSet, $dt->format('Y'), $start->getTimezone());
$dtTmp = $dtTmp->setTime(
$time->hour,
$time->minute,
$time->second
);
$datesAdj[] = $dtTmp;
}
}
foreach ($datesAdj as $dtTmp) {
if (null !== $until && $dtTmp > $until) {
$continue = false;
break;
}
if ($dtTmp < $start) {
continue;
}
if ($constraint instanceof ConstraintInterface && !$constraint->test($dtTmp)) {
if (!$countConstraintFailures) {
if ($constraint->stopsTransformer()) {
$continue = false;
break;
} else {
continue;
}
}
} else {
$dates[$total] = $dtTmp;
}
if (null !== $count) {
--$count;
if ($count <= 0) {
$continue = false;
break;
}
}
++$total;
if ($total > $vLimit) {
$continue = false;
break;
}
}
} else {
foreach ($daySet as $dayOfYear) {
$dtTmp = DateUtil::getDateTimeByDayOfYear($dayOfYear, $dt->format('Y'), $start->getTimezone());
foreach ($timeSet as $time) {
/** @var Time $time */
$dtTmp = $dtTmp->setTime(
$time->hour,
$time->minute,
$time->second
);
if (null !== $until && $dtTmp > $until) {
$continue = false;
break;
}
if ($dtTmp < $start) {
continue;
}
if ($constraint instanceof ConstraintInterface && !$constraint->test($dtTmp)) {
if (!$countConstraintFailures) {
if ($constraint->stopsTransformer()) {
$continue = false;
break;
} else {
continue;
}
}
} else {
$dates[$total] = clone $dtTmp;
}
if (null !== $count) {
--$count;
if ($count <= 0) {
$continue = false;
break;
}
}
++$total;
if ($total > $vLimit) {
$continue = false;
break;
}
}
if (!$continue) {
break;
}
}
if ($total > $vLimit) {
$continue = false;
break;
}
}
switch ($freq) {
case Frequency::YEARLY:
$year += $rule->getInterval();
$month = $dt->format('n');
$dt = $dt->setDate($year, $month, 1);
// Stop an infinite loop w/ a sane limit
++$iterations;
if ($iterations > 300 && !count($dates)) {
break 2;
}
break;
case Frequency::MONTHLY:
$month += $rule->getInterval();
if ($month > 12) {
$delta = floor($month / 12);
$mod = DateUtil::pymod($month, 12);
$month = $mod;
$year += $delta;
if ($month == 0) {
$month = 12;
--$year;
}
}
$dt = $dt->setDate($year, $month, 1);
break;
case Frequency::WEEKLY:
if ($weekStart > $dtInfo->dayOfWeek) {
$delta = ($dtInfo->dayOfWeek + 1 + (6 - $weekStart)) * -1 + $rule->getInterval() * 7;
} else {
$delta = ($dtInfo->dayOfWeek - $weekStart) * -1 + $rule->getInterval() * 7;
}
$dt = $dt->modify("+$delta day");
$year = $dt->format('Y');
$month = $dt->format('n');
break;
case Frequency::DAILY:
$dt = $dt->modify('+'.$rule->getInterval().' day');
$year = $dt->format('Y');
$month = $dt->format('n');
break;
case Frequency::HOURLY:
$dt = $dt->modify('+'.$rule->getInterval().' hours');
$year = $dt->format('Y');
$month = $dt->format('n');
$hour = $dt->format('G');
break;
case Frequency::MINUTELY:
$dt = $dt->modify('+'.$rule->getInterval().' minutes');
$year = $dt->format('Y');
$month = $dt->format('n');
$hour = $dt->format('G');
$minute = $dt->format('i');
break;
case Frequency::SECONDLY:
$dt = $dt->modify('+'.$rule->getInterval().' seconds');
$year = $dt->format('Y');
$month = $dt->format('n');
$hour = $dt->format('G');
$minute = $dt->format('i');
$second = $dt->format('s');
break;
}
}
/** @var Recurrence[] $recurrences */
$recurrences = array();
foreach ($dates as $key => $start) {
/** @var \DateTimeInterface $end */
$end = clone $start;
$recurrences[] = new Recurrence($start, $end->add($durationInterval), $key);
}
$recurrences = $this->handleInclusions($rule->getRDates(), $recurrences);
$recurrences = $this->handleExclusions($rule->getExDates(), $recurrences);
return new RecurrenceCollection($recurrences);
}
/**
* @param DateExclusion[] $exclusions
* @param Recurrence[] $recurrences
*
* @return Recurrence[]
*/
protected function handleExclusions(array $exclusions, array $recurrences)
{
foreach ($exclusions as $exclusion) {
$exclusionDate = $exclusion->date->format('Ymd');
$exclusionTime = $exclusion->date->format('Ymd\THis');
$exclusionTimezone = $exclusion->date->getTimezone();
foreach ($recurrences as $key => $recurrence) {
$recurrenceDate = $recurrence->getStart();
if ($recurrenceDate->getTimezone()->getName() !== $exclusionTimezone->getName()) {
$recurrenceDate = clone $recurrenceDate;
$recurrenceDate = $recurrenceDate->setTimezone($exclusionTimezone);
}
if (!$exclusion->hasTime && $recurrenceDate->format('Ymd') == $exclusionDate) {
unset($recurrences[$key]);
continue;
}
if ($exclusion->hasTime && $recurrenceDate->format('Ymd\THis') == $exclusionTime) {
unset($recurrences[$key]);
}
}
}
return array_values($recurrences);
}
/**
* @param DateInclusion[] $inclusions
* @param Recurrence[] $recurrences
*
* @return Recurrence[]
*/
protected function handleInclusions(array $inclusions, array $recurrences)
{
foreach ($inclusions as $inclusion) {
$recurrence = new Recurrence(clone $inclusion->date, clone $inclusion->date);
$recurrences[] = $recurrence;
}
return array_values($recurrences);
}
}

View File

@@ -0,0 +1,65 @@
<?php
/*
* Copyright 2014 Shaun Simmons
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Recurr\Transformer;
class ArrayTransformerConfig
{
/** @var int */
protected $virtualLimit = 732;
protected $lastDayOfMonthFix = false;
/**
* Set the virtual limit imposed upon infinitely recurring events.
*
* @param int $virtualLimit The limit
*
* @return $this
*/
public function setVirtualLimit($virtualLimit)
{
$this->virtualLimit = (int) $virtualLimit;
return $this;
}
/**
* Get the virtual limit imposed upon infinitely recurring events.
*
* @return int
*/
public function getVirtualLimit()
{
return $this->virtualLimit;
}
/**
* By default, January 30 + 1 month results in March 30 because February doesn't have 30 days.
*
* Enabling this fix tells Recurr that +1 month means "last day of next month".
*/
public function enableLastDayOfMonthFix()
{
$this->lastDayOfMonthFix = true;
}
public function disableLastDayOfMonthFix()
{
$this->lastDayOfMonthFix = false;
}
/**
* @return boolean
*/
public function isLastDayOfMonthFixEnabled()
{
return $this->lastDayOfMonthFix;
}
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* Copyright 2014 Shaun Simmons
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Recurr\Transformer;
abstract class Constraint implements ConstraintInterface
{
protected $stopsTransformer = true;
public function stopsTransformer()
{
return $this->stopsTransformer;
}
}

View File

@@ -0,0 +1,64 @@
<?php
/*
* Copyright 2014 Shaun Simmons
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Recurr\Transformer\Constraint;
use Recurr\Transformer\Constraint;
class AfterConstraint extends Constraint
{
protected $stopsTransformer = false;
/** @var \DateTimeInterface */
protected $after;
/** @var bool */
protected $inc;
/**
* @param \DateTimeInterface $after
* @param bool $inc Include date if it equals $after.
*/
public function __construct(\DateTimeInterface $after, $inc = false)
{
$this->after = $after;
$this->inc = $inc;
}
/**
* Passes if $date is after $after
*
* {@inheritdoc}
*/
public function test(\DateTimeInterface $date)
{
if ($this->inc) {
return $date >= $this->after;
}
return $date > $this->after;
}
/**
* @return \DateTimeInterface
*/
public function getAfter()
{
return $this->after;
}
/**
* @return bool
*/
public function isInc()
{
return $this->inc;
}
}

View File

@@ -0,0 +1,64 @@
<?php
/*
* Copyright 2014 Shaun Simmons
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Recurr\Transformer\Constraint;
use Recurr\Transformer\Constraint;
class BeforeConstraint extends Constraint
{
protected $stopsTransformer = true;
/** @var \DateTimeInterface */
protected $before;
/** @var bool */
protected $inc;
/**
* @param \DateTimeInterface $before
* @param bool $inc Include date if it equals $before.
*/
public function __construct(\DateTimeInterface $before, $inc = false)
{
$this->before = $before;
$this->inc = $inc;
}
/**
* Passes if $date is before $before
*
* {@inheritdoc}
*/
public function test(\DateTimeInterface $date)
{
if ($this->inc) {
return $date <= $this->before;
}
return $date < $this->before;
}
/**
* @return \DateTimeInterface
*/
public function getBefore()
{
return $this->before;
}
/**
* @return bool
*/
public function isInc()
{
return $this->inc;
}
}

View File

@@ -0,0 +1,81 @@
<?php
/*
* Copyright 2014 Shaun Simmons
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Recurr\Transformer\Constraint;
use Recurr\Transformer\Constraint;
class BetweenConstraint extends Constraint
{
protected $stopsTransformer = false;
/** @var \DateTimeInterface */
protected $before;
/** @var \DateTimeInterface */
protected $after;
/** @var bool */
protected $inc;
/**
* @param \DateTimeInterface $after
* @param \DateTimeInterface $before
* @param bool $inc Include date if it equals $after or $before.
*/
public function __construct(\DateTimeInterface $after, \DateTimeInterface $before, $inc = false)
{
$this->after = $after;
$this->before = $before;
$this->inc = $inc;
}
/**
* Passes if $date is between $after and $before
*
* {@inheritdoc}
*/
public function test(\DateTimeInterface $date)
{
if ($date > $this->before) {
$this->stopsTransformer = true;
}
if ($this->inc) {
return $date >= $this->after && $date <= $this->before;
}
return $date > $this->after && $date < $this->before;
}
/**
* @return \DateTimeInterface
*/
public function getBefore()
{
return $this->before;
}
/**
* @return \DateTimeInterface
*/
public function getAfter()
{
return $this->after;
}
/**
* @return bool
*/
public function isInc()
{
return $this->inc;
}
}

View File

@@ -0,0 +1,25 @@
<?php
/*
* Copyright 2014 Shaun Simmons
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Recurr\Transformer;
interface ConstraintInterface
{
/**
* @return bool
*/
public function stopsTransformer();
/**
* @param \DateTimeInterface $date
*
* @return bool
*/
public function test(\DateTimeInterface $date);
}

View File

@@ -0,0 +1,502 @@
<?php
namespace Recurr\Transformer;
use Recurr\Rule;
class TextTransformer
{
protected $fragments = array();
protected $translator;
public function __construct(TranslatorInterface $translator = null)
{
$this->translator = $translator ?: new Translator('en');
}
public function transform(Rule $rule)
{
$this->fragments = array();
switch ($rule->getFreq()) {
case 0:
$this->addYearly($rule);
break;
case 1:
$this->addMonthly($rule);
break;
case 2:
$this->addWeekly($rule);
break;
case 3:
$this->addDaily($rule);
break;
case 4:
$this->addHourly($rule);
break;
case 5:
case 6:
return $this->translator->trans('Unable to fully convert this rrule to text.');
}
$until = $rule->getUntil();
$count = $rule->getCount();
if ($until instanceof \DateTimeInterface) {
$dateFormatted = $this->translator->trans('day_date', array('date' => $until->format('U')));
$this->addFragment($this->translator->trans('until %date%', array('date' => $dateFormatted)));
} else if (!empty($count)) {
if ($this->isPlural($count)) {
$this->addFragment($this->translator->trans('for %count% times', array('count' => $count)));
} else {
$this->addFragment($this->translator->trans('for one time'));
}
}
if (!$this->isFullyConvertible($rule)) {
$this->addFragment($this->translator->trans('(~ approximate)'));
}
return implode(' ', $this->fragments);
}
protected function isFullyConvertible(Rule $rule)
{
if ($rule->getFreq() >= 5) {
return false;
}
$until = $rule->getUntil();
$count = $rule->getCount();
if (!empty($until) && !empty($count)) {
return false;
}
$bySecond = $rule->getBySecond();
$byMinute = $rule->getByMinute();
$byHour = $rule->getByHour();
if (!empty($bySecond) || !empty($byMinute) || !empty($byHour)) {
return false;
}
$byWeekNum = $rule->getByWeekNumber();
$byYearDay = $rule->getByYearDay();
if ($rule->getFreq() != 0 && (!empty($byWeekNum) || !empty($byYearDay))) {
return false;
}
return true;
}
protected function addYearly(Rule $rule)
{
$interval = $rule->getInterval();
$byMonth = $rule->getByMonth();
$byMonthDay = $rule->getByMonthDay();
$byDay = $rule->getByDay();
$byYearDay = $rule->getByYearDay();
$byWeekNum = $rule->getByWeekNumber();
if (!empty($byMonth) && count($byMonth) > 1 && $interval == 1) {
$this->addFragment($this->translator->trans('every_month_list'));
} else {
$this->addFragment($this->translator->trans($this->isPlural($interval) ? 'every %count% years' : 'every year', array('count' => $interval)));
}
$hasNoOrOneByMonth = is_null($byMonth) || count($byMonth) <= 1;
if ($hasNoOrOneByMonth && empty($byMonthDay) && empty($byDay) && empty($byYearDay) && empty($byWeekNum)) {
$this->addFragment($this->translator->trans('on'));
$monthNum = (is_array($byMonth) && count($byMonth)) ? $byMonth[0] : $rule->getStartDate()->format('n');
$this->addFragment(
$this->translator->trans('day_month', array('month' => $monthNum, 'day' => $rule->getStartDate()->format('d')))
);
} elseif (!empty($byMonth)) {
if ($interval != 1) {
$this->addFragment($this->translator->trans('in_month'));
}
$this->addByMonth($rule);
}
if (!empty($byMonthDay)) {
$this->addByMonthDay($rule);
$this->addFragment($this->translator->trans('of_the_month'));
} else if (!empty($byDay)) {
$this->addByDay($rule);
}
if (!empty($byYearDay)) {
$this->addFragment($this->translator->trans('on the'));
$this->addFragment($this->getByYearDayAsText($byYearDay));
$this->addFragment($this->translator->trans('day'));
}
if (!empty($byWeekNum)) {
$this->addFragment($this->translator->trans('in_week'));
$this->addFragment($this->translator->trans($this->isPlural(count($byWeekNum)) ? 'weeks' : 'week'));
$this->addFragment($this->getByWeekNumberAsText($byWeekNum));
}
if (empty($byMonthDay) && empty($byYearDay) && empty($byDay) && !empty($byWeekNum)) {
$this->addDayOfWeek($rule);
}
}
protected function addMonthly(Rule $rule)
{
$interval = $rule->getInterval();
$byMonth = $rule->getByMonth();
if (!empty($byMonth) && $interval == 1) {
$this->addFragment($this->translator->trans('every_month_list'));
} else {
$this->addFragment($this->translator->trans($this->isPlural($interval) ? 'every %count% months' : 'every month', array('count' => $interval)));
}
if (!empty($byMonth)) {
if ($interval != 1) {
$this->addFragment($this->translator->trans('in_month'));
}
$this->addByMonth($rule);
}
$byMonthDay = $rule->getByMonthDay();
$byDay = $rule->getByDay();
if (!empty($byMonthDay)) {
$this->addByMonthDay($rule);
} else if (!empty($byDay)) {
$this->addByDay($rule);
}
}
protected function addWeekly(Rule $rule)
{
$interval = $rule->getInterval();
$byMonth = $rule->getByMonth();
$byMonthDay = $rule->getByMonthDay();
$byDay = $rule->getByDay();
$this->addFragment($this->translator->trans($this->isPlural($interval) ? 'every %count% weeks' : 'every week', array('count' => $interval)));
if (empty($byMonthDay) && empty($byDay)) {
$this->addDayOfWeek($rule);
}
if (!empty($byMonth)) {
$this->addFragment($this->translator->trans('in_month'));
$this->addByMonth($rule);
}
if (!empty($byMonthDay)) {
$this->addByMonthDay($rule);
$this->addFragment($this->translator->trans('of_the_month'));
} else if (!empty($byDay)) {
$this->addByDay($rule);
}
}
protected function addDaily(Rule $rule)
{
$interval = $rule->getInterval();
$byMonth = $rule->getByMonth();
$this->addFragment($this->translator->trans($this->isPlural($interval) ? 'every %count% days' : 'every day', array('count' => $interval)));
if (!empty($byMonth)) {
$this->addFragment($this->translator->trans('in_month'));
$this->addByMonth($rule);
}
$byMonthDay = $rule->getByMonthDay();
$byDay = $rule->getByDay();
if (!empty($byMonthDay)) {
$this->addByMonthDay($rule);
$this->addFragment($this->translator->trans('of_the_month'));
} else if (!empty($byDay)) {
$this->addByDay($rule);
}
}
protected function addHourly(Rule $rule)
{
$interval = $rule->getInterval();
$byMonth = $rule->getByMonth();
$this->addFragment($this->translator->trans($this->isPlural($interval) ? 'every %count% hours' : 'every hour', array('count' => $interval)));
if (!empty($byMonth)) {
$this->addFragment($this->translator->trans('in_month'));
$this->addByMonth($rule);
}
$byMonthDay = $rule->getByMonthDay();
$byDay = $rule->getByDay();
if (!empty($byMonthDay)) {
$this->addByMonthDay($rule);
$this->addFragment($this->translator->trans('of_the_month'));
} else if (!empty($byDay)) {
$this->addByDay($rule);
}
}
protected function addByMonth(Rule $rule)
{
$byMonth = $rule->getByMonth();
if (empty($byMonth)) {
return;
}
$this->addFragment($this->getByMonthAsText($byMonth));
}
protected function addByMonthDay(Rule $rule)
{
$byMonthDay = $rule->getByMonthDay();
$byDay = $rule->getByDay();
if (!empty($byDay)) {
$this->addFragment($this->translator->trans('on'));
$this->addFragment($this->getByDayAsText($byDay, 'or'));
$this->addFragment($this->translator->trans('the_for_monthday'));
$this->addFragment($this->getByMonthDayAsText($byMonthDay, 'or'));
} else {
$this->addFragment($this->translator->trans('on the'));
$this->addFragment($this->getByMonthDayAsText($byMonthDay, 'and'));
}
}
protected function addByDay(Rule $rule)
{
$byDay = $rule->getByDay();
$this->addFragment($this->translator->trans('on'));
$this->addFragment($this->getByDayAsText($byDay));
}
protected function addDayOfWeek(Rule $rule)
{
$this->addFragment($this->translator->trans('on'));
$dayNames = $this->translator->trans('day_names');
$this->addFragment($dayNames[$rule->getStartDate()->format('w')]);
}
public function getByMonthAsText($byMonth)
{
if (empty($byMonth)) {
return '';
}
if (count($byMonth) > 1) {
sort($byMonth);
}
$monthNames = $this->translator->trans('month_names');
$byMonth = array_map(
function ($monthInt) use ($monthNames) {
return $monthNames[$monthInt - 1];
},
$byMonth
);
return $this->getListStringFromArray($byMonth);
}
public function getByDayAsText($byDay, $listSeparator = 'and')
{
if (empty($byDay)) {
return '';
}
$map = array(
'SU' => null,
'MO' => null,
'TU' => null,
'WE' => null,
'TH' => null,
'FR' => null,
'SA' => null
);
$dayNames = $this->translator->trans('day_names');
$timestamp = mktime(1, 1, 1, 1, 12, 2014); // A Sunday
foreach (array_keys($map) as $short) {
$long = $dayNames[date('w', $timestamp)];
$map[$short] = $long;
$timestamp += 86400;
}
$numOrdinals = 0;
foreach ($byDay as $key => $short) {
$day = strtoupper($short);
$string = '';
if (preg_match('/([+-]?)([0-9]*)([A-Z]+)/', $short, $parts)) {
$symbol = $parts[1];
$nth = $parts[2];
$day = $parts[3];
if (!empty($nth)) {
++$numOrdinals;
$string .= $this->getOrdinalNumber($symbol == '-' ? -$nth : $nth);
}
}
if (!isset($map[$day])) {
throw new \RuntimeException("byDay $short could not be transformed");
}
if (!empty($string)) {
$string .= ' ';
}
$byDay[$key] = ltrim($string.$map[$day]);
}
$output = $numOrdinals ? $this->translator->trans('the_for_weekday') . ' ' : '';
if ($output == ' ') {
$output = '';
}
$output .= $this->getListStringFromArray($byDay, $listSeparator);
return $output;
}
public function getByMonthDayAsText($byMonthDay, $listSeparator = 'and')
{
if (empty($byMonthDay)) {
return '';
}
// sort negative indices in reverse order so we get e.g. 1st, 2nd, 4th, 3rd last, last day
usort($byMonthDay, function ($a, $b) {
if (($a < 0 && $b < 0) || ($a >= 0 && $b >= 0)) {
return $a - $b;
}
return $b - $a;
});
// generate ordinal numbers and insert a "on the" for clarity in the middle if we have both
// positive and negative ordinals. This is to avoid confusing situations like:
//
// monthly on the 1st and 2nd to the last day
//
// which gets clarified to:
//
// monthly on the 1st day and on the 2nd to the last day
$hadPositives = false;
$hadNegatives = false;
foreach ($byMonthDay as $index => $day) {
$prefix = '';
if ($day >= 0) {
$hadPositives = true;
}
if ($day < 0) {
if ($hadPositives && !$hadNegatives && $listSeparator === 'and') {
$prefix = $this->translator->trans('on the') . ' ';
}
$hadNegatives = true;
}
$byMonthDay[$index] = $prefix . $this->getOrdinalNumber($day, end($byMonthDay) < 0, true);
}
return $this->getListStringFromArray($byMonthDay, $listSeparator);
}
public function getByYearDayAsText($byYearDay)
{
if (empty($byYearDay)) {
return '';
}
// sort negative indices in reverse order so we get e.g. 1st, 2nd, 4th, 3rd last, last day
usort($byYearDay, function ($a, $b) {
if (($a < 0 && $b < 0) || ($a >= 0 && $b >= 0)) {
return $a - $b;
}
return $b - $a;
});
$byYearDay = array_map(
array($this, 'getOrdinalNumber'),
$byYearDay,
array_fill(0, count($byYearDay), end($byYearDay) < 0)
);
return $this->getListStringFromArray($byYearDay);
}
public function getByWeekNumberAsText($byWeekNum)
{
if (empty($byWeekNum)) {
return '';
}
if (count($byWeekNum) > 1) {
sort($byWeekNum);
}
return $this->getListStringFromArray($byWeekNum);
}
protected function addFragment($fragment)
{
if ($fragment && $fragment !== ' ') {
$this->fragments[] = $fragment;
}
}
public function resetFragments()
{
$this->fragments = array();
}
protected function isPlural($number)
{
return $number % 100 != 1;
}
protected function getOrdinalNumber($number, $hasNegatives = false, $dayInMonth = false)
{
if (!preg_match('{^-?\d+$}D', $number)) {
throw new \RuntimeException('$number must be a whole number');
}
return $this->translator->trans('ordinal_number', array('number' => $number, 'has_negatives' => $hasNegatives, 'day_in_month' => $dayInMonth));
}
protected function getListStringFromArray($values, $separator = 'and')
{
$separator = $this->translator->trans($separator);
if (!is_array($values)) {
throw new \RuntimeException('$values must be an array.');
}
$numValues = count($values);
if (!$numValues) {
return '';
}
if ($numValues == 1) {
reset($values);
return current($values);
}
if ($numValues == 2) {
return implode(" $separator ", $values);
}
$lastValue = array_pop($values);
$output = implode(', ', $values);
$output .= " $separator ".$lastValue;
return $output;
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace Recurr\Transformer;
class Translator implements TranslatorInterface
{
protected $data = array();
public function __construct($locale = 'en', $fallbackLocale = 'en')
{
$this->loadLocale($fallbackLocale);
if ($locale !== $fallbackLocale) {
$this->loadLocale($locale);
}
}
public function loadLocale($locale, $path = null)
{
if (!$path) {
$path = __DIR__ . '/../../../translations/' . $locale . '.php';
}
if (!file_exists($path)) {
throw new \InvalidArgumentException('Locale '.$locale.' could not be found in '.$path);
}
$this->data = array_merge($this->data, include $path);
}
public function trans($string, array $params = array())
{
$res = $this->data[$string];
if (is_object($res) && is_callable($res)) {
$res = $res($string, $params);
}
foreach ($params as $key => $val) {
$res = str_replace('%' . $key . '%', $val, $res);
}
return $res;
}
}

View File

@@ -0,0 +1,8 @@
<?php
namespace Recurr\Transformer;
interface TranslatorInterface
{
public function trans($string);
}

View File

@@ -0,0 +1,72 @@
<?php
/*
* Copyright 2013 Shaun Simmons
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* Based on rrule.js
* Copyright 2010, Jakub Roztocil and Lars Schoning
* https://github.com/jkbr/rrule/blob/master/LICENCE
*/
namespace Recurr;
use Recurr\Exception\InvalidWeekday;
/**
* Class Weekday is a storage container for a day of the week.
*
* @package Recurr
* @author Shaun Simmons <shaun@envysphere.com>
*/
class Weekday
{
/**
* Weekday number.
*
* 0 = Sunday
* 1 = Monday
* 2 = Tuesday
* 3 = Wednesday
* 4 = Thursday
* 5 = Friday
* 6 = Saturday
*
* @var string
*/
public $weekday;
/** @var int nth occurrence of the weekday */
public $num;
protected $days = array('MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU');
/**
* @param int|string $weekday 0-6 or MO..SU
* @param null|int $num
*
* @throws InvalidWeekday
*/
public function __construct($weekday, $num)
{
if (is_numeric($weekday) && $weekday > 6 || $weekday < 0) {
throw new InvalidWeekday('Day is not a valid weekday (0-6)');
} elseif (!is_numeric($weekday) && !in_array($weekday, $this->days)) {
throw new InvalidWeekday('Day is not a valid weekday (SU, MO, ...)');
}
if (!is_numeric($weekday)) {
$weekday = array_search($weekday, $this->days);
}
$this->weekday = $weekday;
$this->num = $num;
}
public function __toString()
{
return $this->num . $this->days[$this->weekday];
}
}