diff options
Diffstat (limited to 'vendor/simshaun/recurr/src')
25 files changed, 4165 insertions, 0 deletions
diff --git a/vendor/simshaun/recurr/src/Recurr/DateExclusion.php b/vendor/simshaun/recurr/src/Recurr/DateExclusion.php new file mode 100644 index 0000000..6ecb6ec --- /dev/null +++ b/vendor/simshaun/recurr/src/Recurr/DateExclusion.php @@ -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; + } +} diff --git a/vendor/simshaun/recurr/src/Recurr/DateInclusion.php b/vendor/simshaun/recurr/src/Recurr/DateInclusion.php new file mode 100644 index 0000000..0ee6e84 --- /dev/null +++ b/vendor/simshaun/recurr/src/Recurr/DateInclusion.php @@ -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; + } +} diff --git a/vendor/simshaun/recurr/src/Recurr/DateInfo.php b/vendor/simshaun/recurr/src/Recurr/DateInfo.php new file mode 100644 index 0000000..453734d --- /dev/null +++ b/vendor/simshaun/recurr/src/Recurr/DateInfo.php @@ -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; +} diff --git a/vendor/simshaun/recurr/src/Recurr/DateUtil.php b/vendor/simshaun/recurr/src/Recurr/DateUtil.php new file mode 100644 index 0000000..48a4e87 --- /dev/null +++ b/vendor/simshaun/recurr/src/Recurr/DateUtil.php @@ -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; + } +} diff --git a/vendor/simshaun/recurr/src/Recurr/DaySet.php b/vendor/simshaun/recurr/src/Recurr/DaySet.php new file mode 100644 index 0000000..6162e41 --- /dev/null +++ b/vendor/simshaun/recurr/src/Recurr/DaySet.php @@ -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; + } +} diff --git a/vendor/simshaun/recurr/src/Recurr/Exception.php b/vendor/simshaun/recurr/src/Recurr/Exception.php new file mode 100644 index 0000000..d0d8c6b --- /dev/null +++ b/vendor/simshaun/recurr/src/Recurr/Exception.php @@ -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 +{ +} diff --git a/vendor/simshaun/recurr/src/Recurr/Exception/InvalidArgument.php b/vendor/simshaun/recurr/src/Recurr/Exception/InvalidArgument.php new file mode 100644 index 0000000..dce4467 --- /dev/null +++ b/vendor/simshaun/recurr/src/Recurr/Exception/InvalidArgument.php @@ -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 +{ +} diff --git a/vendor/simshaun/recurr/src/Recurr/Exception/InvalidRRule.php b/vendor/simshaun/recurr/src/Recurr/Exception/InvalidRRule.php new file mode 100644 index 0000000..cbd2692 --- /dev/null +++ b/vendor/simshaun/recurr/src/Recurr/Exception/InvalidRRule.php @@ -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 +{ +} diff --git a/vendor/simshaun/recurr/src/Recurr/Exception/InvalidWeekday.php b/vendor/simshaun/recurr/src/Recurr/Exception/InvalidWeekday.php new file mode 100644 index 0000000..041a773 --- /dev/null +++ b/vendor/simshaun/recurr/src/Recurr/Exception/InvalidWeekday.php @@ -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 +{ +} diff --git a/vendor/simshaun/recurr/src/Recurr/Frequency.php b/vendor/simshaun/recurr/src/Recurr/Frequency.php new file mode 100644 index 0000000..e5d277b --- /dev/null +++ b/vendor/simshaun/recurr/src/Recurr/Frequency.php @@ -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; +}
\ No newline at end of file diff --git a/vendor/simshaun/recurr/src/Recurr/Recurrence.php b/vendor/simshaun/recurr/src/Recurr/Recurrence.php new file mode 100644 index 0000000..48ab117 --- /dev/null +++ b/vendor/simshaun/recurr/src/Recurr/Recurrence.php @@ -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; + } +} diff --git a/vendor/simshaun/recurr/src/Recurr/RecurrenceCollection.php b/vendor/simshaun/recurr/src/Recurr/RecurrenceCollection.php new file mode 100644 index 0000000..e1e676b --- /dev/null +++ b/vendor/simshaun/recurr/src/Recurr/RecurrenceCollection.php @@ -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; + } + ); + } +} diff --git a/vendor/simshaun/recurr/src/Recurr/Rule.php b/vendor/simshaun/recurr/src/Recurr/Rule.php new file mode 100644 index 0000000..dfcf40f --- /dev/null +++ b/vendor/simshaun/recurr/src/Recurr/Rule.php @@ -0,0 +1,1315 @@ +<?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; + +use Recurr\Exception\InvalidArgument; +use Recurr\Exception\InvalidRRule; +use Recurr\Exception\InvalidWeekday; + +/** + * This class is responsible for providing a programmatic way of building, + * parsing, and handling RRULEs. + * + * http://www.ietf.org/rfc/rfc2445.txt + * + * Information, not contained in the built/parsed RRULE, necessary to determine + * the various recurrence instance start time and dates are derived from the + * DTSTART property (default: \DateTime()). + * + * For example, "FREQ=YEARLY;BYMONTH=1" doesn't specify a specific day within + * the month or a time. This information would be the same as what is specified + * for DTSTART. + * + * + * BYxxx rule parts modify the recurrence in some manner. BYxxx rule parts for + * a period of time which is the same or greater than the frequency generally + * reduce or limit the number of occurrences of the recurrence generated. + * + * For example, "FREQ=DAILY;BYMONTH=1" reduces the number of recurrence + * instances from all days (if BYMONTH tag is not present) to all days in + * January. + * + * BYxxx rule parts for a period of time less than the frequency generally + * increase or expand the number of occurrences of the recurrence. + * + * For example, "FREQ=YEARLY;BYMONTH=1,2" increases the number of days within + * the yearly recurrence set from 1 (if BYMONTH tag is not present) to 2. + * + * If multiple BYxxx rule parts are specified, then after evaluating the + * specified FREQ and INTERVAL rule parts, the BYxxx rule parts are applied to + * the current set of evaluated occurrences in the following order: + * + * BYMONTH, BYWEEKNO, BYYEARDAY, BYMONTHDAY, BYDAY, BYHOUR, + * BYMINUTE, BYSECOND and BYSETPOS; then COUNT and UNTIL are evaluated. + * + * Here is an example of evaluating multiple BYxxx rule parts. + * + * FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=SU;BYHOUR=8,9;BYMINUTE=30 + * + * First, the "INTERVAL=2" would be applied to "FREQ=YEARLY" to arrive at + * "every other year". + * Then, "BYMONTH=1" would be applied to arrive at "every January, every + * other year". + * Then, "BYDAY=SU" would be applied to arrive at "every Sunday in January, + * every other year". + * Then, "BYHOUR=8,9" would be applied to arrive at "every Sunday in January + * at 8 AM and 9 AM, every other year". + * Then, "BYMINUTE=30" would be applied to arrive at "every Sunday in January + * at 8:30 AM and 9:30 AM, every other year". + * Then, lacking information from RRULE, the second is derived from DTSTART, to + * end up in "every Sunday in January at 8:30:00 AM and 9:30:00 AM, every + * other year". Similarly, if the BYMINUTE, BYHOUR, BYDAY, BYMONTHDAY or + * BYMONTH rule part were missing, the appropriate minute, hour, day or month + * would have been retrieved from the "DTSTART" property. + * + * Example: The following is a rule which specifies 10 meetings which occur + * every other day: + * + * FREQ=DAILY;COUNT=10;INTERVAL=2 + * + * @package Recurr + * @author Shaun Simmons <shaun@envysphere.com> + */ +class Rule +{ + const TZ_FIXED = 'fixed'; + const TZ_FLOAT = 'floating'; + + public static $freqs = array( + 'YEARLY' => 0, + 'MONTHLY' => 1, + 'WEEKLY' => 2, + 'DAILY' => 3, + 'HOURLY' => 4, + 'MINUTELY' => 5, + 'SECONDLY' => 6, + ); + + /** @var string */ + protected $timezone; + + /** @var \DateTimeInterface|null */ + protected $startDate; + + /** @var \DateTimeInterface|null */ + protected $endDate; + + /** @var bool */ + protected $isStartDateFromDtstart = false; + + /** @var string */ + protected $freq; + + /** @var int */ + protected $interval = 1; + + /** @var bool */ + protected $isExplicitInterval = false; + + /** @var \DateTimeInterface|null */ + protected $until; + + /** @var int|null */ + protected $count; + + /** @var array */ + protected $bySecond; + + /** @var array */ + protected $byMinute; + + /** @var array */ + protected $byHour; + + /** @var array */ + protected $byDay; + + /** @var array */ + protected $byMonthDay; + + /** @var array */ + protected $byYearDay; + + /** @var array */ + protected $byWeekNumber; + + /** @var array */ + protected $byMonth; + + /** @var string */ + protected $weekStart = 'MO'; + protected $weekStartDefined = false; + + /** @var array */ + protected $days = array( + 'MO' => 0, + 'TU' => 1, + 'WE' => 2, + 'TH' => 3, + 'FR' => 4, + 'SA' => 5, + 'SU' => 6 + ); + + /** @var int[] */ + protected $bySetPosition; + + /** @var array */ + protected $rDates = array(); + + /** @var array */ + protected $exDates = array(); + + /** + * Construct a new Rule. + * + * @param string $rrule RRULE string + * @param string|\DateTimeInterface|null $startDate + * @param string|\DateTimeInterface|null $endDate + * @param string $timezone + * + * @throws InvalidRRule + */ + public function __construct($rrule = null, $startDate = null, $endDate = null, $timezone = null) + { + if (empty($timezone)) { + if ($startDate instanceof \DateTimeInterface) { + $timezone = $startDate->getTimezone()->getName(); + } else { + $timezone = date_default_timezone_get(); + } + } + $this->setTimezone($timezone); + + if ($startDate !== null && !$startDate instanceof \DateTimeInterface) { + $startDate = new \DateTime($startDate, new \DateTimeZone($timezone)); + } + + $this->setStartDate($startDate); + + if ($endDate !== null && !$endDate instanceof \DateTimeInterface) { + $endDate = new \DateTime($endDate, new \DateTimeZone($timezone)); + } + + $this->setEndDate($endDate); + + if (is_array($rrule)) { + $this->loadFromArray($rrule); + } else if (!empty($rrule)) { + $this->loadFromString($rrule); + } + } + + /** + * Create a Rule object based on a RRULE string. + * + * @param string $rrule RRULE string + * @param string|\DateTimeInterface $startDate + * @param \DateTimeInterface|null $endDate + * @param string $timezone + * + * @return Rule + * @throws InvalidRRule + */ + public static function createFromString($rrule, $startDate = null, $endDate = null, $timezone = null) + { + $rule = new static($rrule, $startDate, $endDate, $timezone); + + return $rule; + } + + /** + * Create a Rule object based on a RRULE array. + * + * @param array $rrule RRULE array + * @param string|\DateTimeInterface $startDate + * @param \DateTimeInterface|null $endDate + * @param string $timezone + * + * @return Rule + * @throws InvalidRRule + */ + public static function createFromArray($rrule, $startDate = null, $endDate = null, $timezone = null) + { + $rule = new static($rrule, $startDate, $endDate, $timezone); + + return $rule; + } + + /** + * Populate the object based on a RRULE string. + * + * @param string $rrule RRULE string + * + * @return Rule + * @throws InvalidRRule + */ + public function loadFromString($rrule) + { + $rrule = strtoupper($rrule); + $rrule = trim($rrule, ';'); + $rrule = trim($rrule, "\n"); + $rows = explode("\n", $rrule); + + $parts = array(); + + foreach ($rows as $rruleForRow) { + $parts = array_merge($parts, $this->parseString($rruleForRow)); + } + + return $this->loadFromArray($parts); + } + + /** + * Parse string for parts + * + * @param string $rrule + * + * @return array + * + * @throws InvalidRRule + */ + public function parseString($rrule) + { + if (strpos($rrule, 'DTSTART:') === 0) { + $pieces = explode(':', $rrule); + + if (count($pieces) !== 2) { + throw new InvalidRRule('DSTART is not valid'); + } + + return array('DTSTART' => $pieces[1]); + } + + if (strpos($rrule, 'RRULE:') === 0) { + $rrule = str_replace('RRULE:', '', $rrule); + } + + $pieces = explode(';', $rrule); + $parts = array(); + + if (!count($pieces)) { + throw new InvalidRRule('RRULE is empty'); + } + + // Split each piece of the RRULE in to KEY=>VAL + foreach ($pieces as $piece) { + if (false === strpos($piece, '=')) { + continue; + } + + list($key, $val) = explode('=', $piece); + $parts[$key] = $val; + } + + return $parts; + } + + /** + * Populate the object based on a RRULE array. + * + * @param array + * + * @return Rule + * @throws InvalidRRule + */ + public function loadFromArray($parts) + { + // FREQ is required + if (!isset($parts['FREQ'])) { + throw new InvalidRRule('FREQ is required'); + } else { + if (!in_array($parts['FREQ'], array_keys(self::$freqs))) { + throw new InvalidRRule('FREQ is invalid'); + } + + $this->setFreq(self::$freqs[$parts['FREQ']]); + } + + // DTSTART + if (isset($parts['DTSTART'])) { + $this->isStartDateFromDtstart = true; + $date = new \DateTime($parts['DTSTART']); + $date = $date->setTimezone(new \DateTimeZone($this->getTimezone())); + $this->setStartDate($date); + } + + // DTEND + if (isset($parts['DTEND'])) { + $date = new \DateTime($parts['DTEND']); + $date = $date->setTimezone(new \DateTimeZone($this->getTimezone())); + $this->setEndDate($date); + } + + // UNTIL or COUNT + if (isset($parts['UNTIL']) && isset($parts['COUNT'])) { + throw new InvalidRRule('UNTIL and COUNT must not exist together in the same RRULE'); + } elseif (isset($parts['UNTIL'])) { + $date = new \DateTime($parts['UNTIL']); + $date = $date->setTimezone(new \DateTimeZone($this->getTimezone())); + $this->setUntil($date); + } elseif (isset($parts['COUNT'])) { + $this->setCount($parts['COUNT']); + } + + // INTERVAL + if (isset($parts['INTERVAL'])) { + $this->setInterval($parts['INTERVAL']); + } + + // BYSECOND + if (isset($parts['BYSECOND'])) { + $this->setBySecond(explode(',', $parts['BYSECOND'])); + } + + // BYMINUTE + if (isset($parts['BYMINUTE'])) { + $this->setByMinute(explode(',', $parts['BYMINUTE'])); + } + + // BYHOUR + if (isset($parts['BYHOUR'])) { + $this->setByHour(explode(',', $parts['BYHOUR'])); + } + + // BYDAY + if (isset($parts['BYDAY'])) { + $this->setByDay(explode(',', $parts['BYDAY'])); + } + + // BYMONTHDAY + if (isset($parts['BYMONTHDAY'])) { + $this->setByMonthDay(explode(',', $parts['BYMONTHDAY'])); + } + + // BYYEARDAY + if (isset($parts['BYYEARDAY'])) { + $this->setByYearDay(explode(',', $parts['BYYEARDAY'])); + } + + // BYWEEKNO + if (isset($parts['BYWEEKNO'])) { + $this->setByWeekNumber(explode(',', $parts['BYWEEKNO'])); + } + + // BYMONTH + if (isset($parts['BYMONTH'])) { + $this->setByMonth(explode(',', $parts['BYMONTH'])); + } + + // BYSETPOS + if (isset($parts['BYSETPOS'])) { + $this->setBySetPosition(explode(',', $parts['BYSETPOS'])); + } + + // WKST + if (isset($parts['WKST'])) { + $this->setWeekStart($parts['WKST']); + } + + // RDATE + if (isset($parts['RDATE'])) { + $this->setRDates(explode(',', $parts['RDATE'])); + } + + // EXDATE + if (isset($parts['EXDATE'])) { + $this->setExDates(explode(',', $parts['EXDATE'])); + } + + return $this; + } + + /** + * Get the RRULE as a string + * + * @param string $timezoneType + * + * @return string + */ + public function getString($timezoneType=self::TZ_FLOAT) + { + $format = 'Ymd\THis'; + + $parts = array(); + + // FREQ + $parts[] = 'FREQ='.$this->getFreqAsText(); + + // UNTIL or COUNT + $until = $this->getUntil(); + $count = $this->getCount(); + if (!empty($until)) { + if ($timezoneType === self::TZ_FIXED) { + $u = clone $until; + $u = $u->setTimezone(new \DateTimeZone('UTC')); + $parts[] = 'UNTIL='.$u->format($format.'\Z'); + } else { + $parts[] = 'UNTIL='.$until->format($format); + } + } elseif (!empty($count)) { + $parts[] = 'COUNT='.$count; + } + + // DTSTART + if ($this->isStartDateFromDtstart) { + if ($timezoneType === self::TZ_FIXED) { + $d = $this->getStartDate(); + $tzid = $d->getTimezone()->getName(); + $date = $d->format($format); + $parts[] = "DTSTART;TZID=$tzid:$date"; + } else { + $parts[] = 'DTSTART='.$this->getStartDate()->format($format); + } + } + + // DTEND + if ($this->endDate instanceof \DateTime) { + if ($timezoneType === self::TZ_FIXED) { + $d = $this->getEndDate(); + $tzid = $d->getTimezone()->getName(); + $date = $d->format($format); + + $parts[] = "DTEND;TZID=$tzid:$date"; + } else { + $parts[] = 'DTEND='.$this->getEndDate()->format($format); + } + } + + // INTERVAL + $interval = $this->getInterval(); + if ($this->isExplicitInterval && !empty($interval)) { + $parts[] = 'INTERVAL='.$interval; + } + + // BYSECOND + $bySecond = $this->getBySecond(); + if (!empty($bySecond)) { + $parts[] = 'BYSECOND='.implode(',', $bySecond); + } + + // BYMINUTE + $byMinute = $this->getByMinute(); + if (!empty($byMinute)) { + $parts[] = 'BYMINUTE='.implode(',', $byMinute); + } + + // BYHOUR + $byHour = $this->getByHour(); + if (!empty($byHour)) { + $parts[] = 'BYHOUR='.implode(',', $byHour); + } + + // BYDAY + $byDay = $this->getByDay(); + if (!empty($byDay)) { + $parts[] = 'BYDAY='.implode(',', $byDay); + } + + // BYMONTHDAY + $byMonthDay = $this->getByMonthDay(); + if (!empty($byMonthDay)) { + $parts[] = 'BYMONTHDAY='.implode(',', $byMonthDay); + } + + // BYYEARDAY + $byYearDay = $this->getByYearDay(); + if (!empty($byYearDay)) { + $parts[] = 'BYYEARDAY='.implode(',', $byYearDay); + } + + // BYWEEKNO + $byWeekNumber = $this->getByWeekNumber(); + if (!empty($byWeekNumber)) { + $parts[] = 'BYWEEKNO='.implode(',', $byWeekNumber); + } + + // BYMONTH + $byMonth = $this->getByMonth(); + if (!empty($byMonth)) { + $parts[] = 'BYMONTH='.implode(',', $byMonth); + } + + // BYSETPOS + $bySetPosition = $this->getBySetPosition(); + if (!empty($bySetPosition)) { + $parts[] = 'BYSETPOS='.implode(',', $bySetPosition); + } + + // WKST + $weekStart = $this->getWeekStart(); + if ($this->weekStartDefined && !empty($weekStart)) { + $parts[] = 'WKST='.$weekStart; + } + + // RDATE + $rDates = $this->getRDates(); + if (!empty($rDates)) { + foreach ($rDates as $key => $inclusion) { + $format = 'Ymd'; + if ($inclusion->hasTime) { + $format .= '\THis'; + if ($inclusion->isUtcExplicit) { + $format .= '\Z'; + } + } + $rDates[$key] = $inclusion->date->format($format); + } + $parts[] = 'RDATE='.implode(',', $rDates); + } + + // EXDATE + $exDates = $this->getExDates(); + if (!empty($exDates)) { + foreach ($exDates as $key => $exclusion) { + $format = 'Ymd'; + if ($exclusion->hasTime) { + $format .= '\THis'; + if ($exclusion->isUtcExplicit) { + $format .= '\Z'; + } + } + $exDates[$key] = $exclusion->date->format($format); + } + $parts[] = 'EXDATE='.implode(',', $exDates); + } + + return implode(';', $parts); + } + + /** + * @param string $timezone + * + * @see http://www.php.net/manual/en/timezones.php + * @return $this + */ + public function setTimezone($timezone) + { + $this->timezone = $timezone; + + return $this; + } + + /** + * Get timezone to use for \DateTimeInterface objects that are UTC. + * + * @return null|string + */ + public function getTimezone() + { + return $this->timezone; + } + + /** + * This date specifies the first instance in the recurrence set. + * + * @param \DateTimeInterface|null $startDate Date of the first instance in the recurrence + * @param bool|null $includeInString If true, include as DTSTART when calling getString() + * + * @return $this + */ + public function setStartDate($startDate, $includeInString = null) + { + $this->startDate = $startDate; + + if ($includeInString !== null) { + $this->isStartDateFromDtstart = (bool) $includeInString; + } + + return $this; + } + + /** + * @return \DateTimeInterface + */ + public function getStartDate() + { + return $this->startDate; + } + + /** + * This date specifies the last possible instance in the recurrence set. + * + * @param \DateTimeInterface|null $endDate Date of the last possible instance in the recurrence + * + * @return $this + */ + public function setEndDate($endDate) + { + $this->endDate = $endDate; + + return $this; + } + + /** + * @return \DateTimeInterface|null + */ + public function getEndDate() + { + return $this->endDate; + } + + /** + * Identifies the type of recurrence rule. + * + * May be one of: + * - Frequency::SECONDLY to specify repeating events based on an + * interval of a second or more. + * - Frequency::MINUTELY to specify repeating events based on an + * interval of a minute or more. + * - Frequency::HOURLY to specify repeating events based on an + * interval of an hour or more. + * - Frequency::DAILY to specify repeating events based on an + * interval of a day or more. + * - Frequency::WEEKLY to specify repeating events based on an + * interval of a week or more. + * - Frequency::MONTHLY to specify repeating events based on an + * interval of a month or more. + * - Frequency::YEAR to specify repeating events based on an + * interval of a year or more. + * + * @param string|int $freq Frequency of recurrence. + * + * @return $this + * @throws Exception\InvalidArgument + */ + public function setFreq($freq) + { + if (is_string($freq)) { + if (!array_key_exists($freq, self::$freqs)) { + throw new InvalidArgument('Frequency must comply with RFC 2445.'); + } else { + $freq = self::$freqs[$freq]; + } + } + + if (is_int($freq) && ($freq < 0 || $freq > 6)) { + throw new InvalidArgument('Frequency integer must be between 0 and 6 Use the class constants.'); + } + + $this->freq = $freq; + + return $this; + } + + /** + * Get the type of recurrence rule (as integer). + * + * @return int + */ + public function getFreq() + { + return $this->freq; + } + + /** + * Get the type of recurrence rule (as text). + * + * @return string + */ + public function getFreqAsText() + { + return array_search($this->getFreq(), self::$freqs); + } + + /** + * The interval represents how often the recurrence rule repeats. + * + * The default value is "1", meaning every second for a SECONDLY rule, + * or every minute for a MINUTELY rule, every hour for an HOURLY rule, + * every day for a DAILY rule, every week for a WEEKLY rule, every month + * for a MONTHLY rule and every year for a YEARLY rule. + * + * @param int $interval Positive integer that represents how often the + * recurrence rule repeats. + * + * @return $this + * @throws Exception\InvalidArgument + */ + public function setInterval($interval) + { + $interval = (int) $interval; + + if ($interval < 1) { + throw new InvalidArgument('Interval must be a positive integer'); + } + + $this->interval = $interval; + $this->isExplicitInterval = true; + + return $this; + } + + /** + * Get the interval that represents how often the recurrence rule repeats. + * + * @return int + */ + public function getInterval() + { + return $this->interval; + } + + /** + * Define a \DateTimeInterface value which bounds the recurrence rule in an + * inclusive manner. If the value specified is synchronized with the + * specified recurrence, this DateTime becomes the last instance of the + * recurrence. If not present, and a COUNT is also not present, the RRULE + * is considered to repeat forever. + * + * Either UNTIL or COUNT may be specified, but UNTIL and COUNT MUST NOT + * both be specified. + * + * @param \DateTimeInterface $until The upper bound of the recurrence. + * + * @return $this + */ + public function setUntil(\DateTimeInterface $until) + { + $this->until = $until; + $this->count = null; + + return $this; + } + + /** + * Get the \DateTimeInterface that the recurrence lasts until. + * + * @return \DateTimeInterface|null + */ + public function getUntil() + { + $date = $this->until; + + if ($date instanceof \DateTime + && $date->getTimezone()->getName() == 'UTC' + && $this->getTimezone() != 'UTC' + ) { + $timestamp = $date->getTimestamp(); + $date = $date->setTimezone(new \DateTimeZone($this->getTimezone())); + $date = $date->setTimestamp($timestamp); + } + + return $date; + } + + /** + * The count defines the number of occurrences at which to range-bound the + * recurrence. The DTSTART counts as the first occurrence. + * + * Either COUNT or UNTIL may be specified, but COUNT and UNTIL MUST NOT + * both be specified. + * + * @param int $count Number of occurrences + * + * @return $this + */ + public function setCount($count) + { + $this->count = (int) $count; + $this->until = null; + + return $this; + } + + /** + * Get the number of occurrences at which the recurrence is range-bound. + * + * @return int|null + */ + public function getCount() + { + return $this->count; + } + + /** + * This rule specifies an array of seconds within a minute. + * + * Valid values are 0 to 59. + * + * @param array $bySecond Array of seconds within a minute + * + * @return $this + */ + public function setBySecond(array $bySecond) + { + $this->bySecond = $bySecond; + + return $this; + } + + /** + * Get an array of seconds within a minute. + * + * @return array + */ + public function getBySecond() + { + return $this->bySecond; + } + + /** + * This rule specifies an array of minutes within an hour. + * + * Valid values are 0 to 59. + * + * @param array $byMinute Array of minutes within an hour + * + * @return $this + */ + public function setByMinute(array $byMinute) + { + $this->byMinute = $byMinute; + + return $this; + } + + /** + * Get an array of minutes within an hour. + * + * @return array + */ + public function getByMinute() + { + return $this->byMinute; + } + + /** + * This rule specifies an array of hours of the day. + * + * Valid values are 0 to 23. + * + * @param array $byHour Array of hours of the day + * + * @return $this + */ + public function setByHour(array $byHour) + { + $this->byHour = $byHour; + + return $this; + } + + /** + * Get an array of hours of the day. + * + * @return array + */ + public function getByHour() + { + return $this->byHour; + } + + /** + * This rule specifies an array of days of the week; + * + * MO indicates Monday; TU indicates Tuesday; WE indicates Wednesday; + * TH indicates Thursday; FR indicates Friday; SA indicates Saturday; + * SU indicates Sunday. + * + * Each BYDAY value can also be preceded by a positive (+n) or negative + * (-n) integer. If present, this indicates the nth occurrence of the + * specific day within the MONTHLY or YEARLY RRULE. For example, within + * a MONTHLY rule, +1MO (or simply 1MO) represents the first Monday + * within the month, whereas -1MO represents the last Monday of the + * month. If an integer modifier is not present, it means all days of + * this type within the specified frequency. For example, within a + * MONTHLY rule, MO represents all Mondays within the month. + * + * ------------------------------------------- + * DO NOT MIX DAYS AND DAYS WITH MODIFIERS. + * This is not supported. + * ------------------------------------------- + * + * @param array $byDay Array of days of the week + * + * @return $this + * @throws InvalidRRule + */ + public function setByDay(array $byDay) + { + if ($this->getFreq() > static::$freqs['MONTHLY'] && preg_match('/\d/', implode(',', $byDay))) { + throw new InvalidRRule('BYDAY only supports MONTHLY and YEARLY frequencies'); + } + if (count($byDay) === 0 || $byDay === array('')) { + throw new InvalidRRule('BYDAY must be set to at least one day'); + } + + $this->byDay = $byDay; + + return $this; + } + + /** + * Get an array of days of the week (SU, MO, TU, ..) + * + * @return array + */ + public function getByDay() + { + return $this->byDay; + } + + /** + * Get an array of Weekdays + * + * @return array of Weekdays + * @throws InvalidWeekday + */ + public function getByDayTransformedToWeekdays() + { + $byDay = $this->getByDay(); + + if (null === $byDay || !count($byDay)) { + return array(); + } + + foreach ($byDay as $idx => $day) { + if (strlen($day) === 2) { + $byDay[$idx] = new Weekday($day, null); + } else { + preg_match('/^([+-]?[0-9]+)([A-Z]{2})$/', $day, $dayParts); + $byDay[$idx] = new Weekday($dayParts[2], $dayParts[1]); + } + } + + return $byDay; + } + + /** + * This rule specifies an array of days of the month. + * Valid values are 1 to 31 or -31 to -1. + * + * For example, -10 represents the tenth to the last day of the month. + * + * @param array $byMonthDay Array of days of the month from -31 to 31 + * + * @return $this + */ + public function setByMonthDay(array $byMonthDay) + { + $this->byMonthDay = $byMonthDay; + + return $this; + } + + /** + * Get an array of days of the month. + * + * @return array + */ + public function getByMonthDay() + { + return $this->byMonthDay; + } + + /** + * This rule specifies an array of days of the year. + * Valid values are 1 to 366 or -366 to -1. + * + * For example, -1 represents the last day of the year (December 31st) and + * -306 represents the 306th to the last day of the year (March 1st). + * + * @param array $byYearDay Array of days of the year from -1 to 306 + * + * @return $this + */ + public function setByYearDay(array $byYearDay) + { + $this->byYearDay = $byYearDay; + + return $this; + } + + /** + * Get an array of days of the year. + * + * @return array + */ + public function getByYearDay() + { + return $this->byYearDay; + } + + /** + * This rule specifies an array of ordinals specifying weeks of the year. + * Valid values are 1 to 53 or -53 to -1. + * + * This corresponds to weeks according to week numbering as defined in + * [ISO 8601]. A week is defined as a seven day period, starting on the day + * of the week defined to be the week start (see setWeekStart). Week number + * one of the calendar year is the first week which contains at least four + * days in that calendar year. This rule is only valid for YEARLY rules. + * + * For example, 3 represents the third week of the year. + * + * Note: Assuming a Monday week start, week 53 can only occur when + * Thursday is January 1 or if it is a leap year and Wednesday is January 1. + * + * @param array $byWeekNumber Array of ordinals specifying weeks of the year. + * + * @return $this + */ + public function setByWeekNumber(array $byWeekNumber) + { + $this->byWeekNumber = $byWeekNumber; + + return $this; + } + + /** + * Get an array of ordinals specifying weeks of the year. + * + * @return array + */ + public function getByWeekNumber() + { + return $this->byWeekNumber; + } + + /** + * This rule specifies an array of months of the year. + * + * Valid values are 1 to 12. + * + * @param array $byMonth Array of months of the year from 1 to 12 + * + * @return $this + */ + public function setByMonth(array $byMonth) + { + $this->byMonth = $byMonth; + + return $this; + } + + /** + * Get an array of months of the year. + * + * @return array + */ + public function getByMonth() + { + return $this->byMonth; + } + + public function hasByMonth() + { + $val = $this->getByMonth(); + + return ! empty($val); + } + + /** + * This rule specifies the day on which the workweek starts. + * + * Valid values are MO, TU, WE, TH, FR, SA and SU. + * + * This is significant when a WEEKLY RRULE has an interval greater than 1, + * and a BYDAY rule part is specified. + * + * This is also significant when in a YEARLY RRULE when a BYWEEKNO rule + * is specified. The default value is MO. + * + * @param string $weekStart The day on which the workweek starts. + * + * @return $this + * @throws Exception\InvalidArgument + */ + public function setWeekStart($weekStart) + { + $weekStart = strtoupper($weekStart); + + if (!in_array($weekStart, array('MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU'))) { + throw new InvalidArgument('Week Start must be one of MO, TU, WE, TH, FR, SA, SU'); + } + + $this->weekStart = $weekStart; + $this->weekStartDefined = true; + + return $this; + } + + /** + * Get the day on which the workweek starts. + * + * @return string + */ + public function getWeekStart() + { + return $this->weekStart; + } + + /** + * Get the day on which the workweek starts, as an integer from 0-6, + * 0 being Monday and 6 being Sunday. + * + * @return int + */ + public function getWeekStartAsNum() + { + $weekStart = $this->getWeekStart(); + + return $this->days[$weekStart]; + } + + /** + * This rule specifies an array of values which corresponds to the nth + * occurrence within the set of events specified by the rule. Valid values + * are 1 to 366 or -366 to -1. It MUST only be used in conjunction with + * another BYxxx rule part. + * + * For example "the last work day of the month" could be represented as: + * RRULE:FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1 + * + * Each BYSETPOS value can include a positive or negative integer. + * If present, this indicates the nth occurrence of the specific occurrence + * within the set of events specified by the rule. + * + * @param array $bySetPosition Array of values which corresponds to the nth + * occurrence within the set of events specified by the rule. + * + * @return $this + */ + public function setBySetPosition($bySetPosition) + { + $this->bySetPosition = $bySetPosition; + + return $this; + } + + /** + * Get the array of values which corresponds to the nth occurrence within + * the set of events specified by the rule. + * + * @return array + */ + public function getBySetPosition() + { + return $this->bySetPosition; + } + + /** + * This rule specifies an array of dates that will be + * included in a recurrence set. + * + * @param string[]|DateInclusion[] $rDates Array of dates that will be + * included in the recurrence set. + * + * @return $this + */ + public function setRDates(array $rDates) + { + $timezone = new \DateTimeZone($this->getTimezone()); + + foreach ($rDates as $key => $val) { + if ($val instanceof DateInclusion) { + $val->date = $this->convertZtoUtc($val->date); + } else { + $date = new \DateTime($val, $timezone); + $rDates[$key] = new DateInclusion( + $this->convertZtoUtc($date), + strpos($val, 'T') !== false, + strpos($val, 'Z') !== false + ); + } + } + + $this->rDates = $rDates; + + return $this; + } + + /** + * Get the array of dates that will be included in a recurrence set. + * + * @return DateInclusion[] + */ + public function getRDates() + { + return $this->rDates; + } + + /** + * This rule specifies an array of exception dates that will not be + * included in a recurrence set. + * + * @param string[]|DateExclusion[] $exDates Array of dates that will not be + * included in the recurrence set. + * + * @return $this + */ + public function setExDates(array $exDates) + { + $timezone = new \DateTimeZone($this->getTimezone()); + + foreach ($exDates as $key => $val) { + if ($val instanceof DateExclusion) { + $val->date = $this->convertZtoUtc($val->date); + } else { + $date = new \DateTime($val, $timezone); + $exDates[$key] = new DateExclusion( + $this->convertZtoUtc($date), + strpos($val, 'T') !== false, + strpos($val, 'Z') !== false + ); + } + } + + $this->exDates = $exDates; + + return $this; + } + + /** + * DateTime::setTimezone fails if the timezone does not have an ID. + * "Z" is the same as "UTC", but "Z" does not have an ID. + * + * This is necessary for exclusion dates to be handled properly. + * + * @param \DateTimeInterface $date + * + * @return \DateTimeInterface + */ + private function convertZtoUtc(\DateTimeInterface $date) + { + if ($date->getTimezone()->getName() !== 'Z') { + return $date; + } + + return $date->setTimezone(new \DateTimeZone('UTC')); + } + + /** + * Get the array of dates that will not be included in a recurrence set. + * + * @return DateExclusion[] + */ + public function getExDates() + { + return $this->exDates; + } + + /** + * @return bool + */ + public function repeatsIndefinitely() + { + return !$this->getCount() && !$this->getUntil() && !$this->getEndDate(); + } +} diff --git a/vendor/simshaun/recurr/src/Recurr/Time.php b/vendor/simshaun/recurr/src/Recurr/Time.php new file mode 100644 index 0000000..c6360af --- /dev/null +++ b/vendor/simshaun/recurr/src/Recurr/Time.php @@ -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; + } +} diff --git a/vendor/simshaun/recurr/src/Recurr/Transformer/ArrayTransformer.php b/vendor/simshaun/recurr/src/Recurr/Transformer/ArrayTransformer.php new file mode 100644 index 0000000..414a3e6 --- /dev/null +++ b/vendor/simshaun/recurr/src/Recurr/Transformer/ArrayTransformer.php @@ -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); + } +} diff --git a/vendor/simshaun/recurr/src/Recurr/Transformer/ArrayTransformerConfig.php b/vendor/simshaun/recurr/src/Recurr/Transformer/ArrayTransformerConfig.php new file mode 100644 index 0000000..68350b0 --- /dev/null +++ b/vendor/simshaun/recurr/src/Recurr/Transformer/ArrayTransformerConfig.php @@ -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; + } +} diff --git a/vendor/simshaun/recurr/src/Recurr/Transformer/Constraint.php b/vendor/simshaun/recurr/src/Recurr/Transformer/Constraint.php new file mode 100644 index 0000000..2a9cf2a --- /dev/null +++ b/vendor/simshaun/recurr/src/Recurr/Transformer/Constraint.php @@ -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; + } +}
\ No newline at end of file diff --git a/vendor/simshaun/recurr/src/Recurr/Transformer/Constraint/AfterConstraint.php b/vendor/simshaun/recurr/src/Recurr/Transformer/Constraint/AfterConstraint.php new file mode 100644 index 0000000..3baf9a0 --- /dev/null +++ b/vendor/simshaun/recurr/src/Recurr/Transformer/Constraint/AfterConstraint.php @@ -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; + } +} diff --git a/vendor/simshaun/recurr/src/Recurr/Transformer/Constraint/BeforeConstraint.php b/vendor/simshaun/recurr/src/Recurr/Transformer/Constraint/BeforeConstraint.php new file mode 100644 index 0000000..aa94525 --- /dev/null +++ b/vendor/simshaun/recurr/src/Recurr/Transformer/Constraint/BeforeConstraint.php @@ -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; + } +} diff --git a/vendor/simshaun/recurr/src/Recurr/Transformer/Constraint/BetweenConstraint.php b/vendor/simshaun/recurr/src/Recurr/Transformer/Constraint/BetweenConstraint.php new file mode 100644 index 0000000..f16d491 --- /dev/null +++ b/vendor/simshaun/recurr/src/Recurr/Transformer/Constraint/BetweenConstraint.php @@ -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; + } +} diff --git a/vendor/simshaun/recurr/src/Recurr/Transformer/ConstraintInterface.php b/vendor/simshaun/recurr/src/Recurr/Transformer/ConstraintInterface.php new file mode 100644 index 0000000..6879554 --- /dev/null +++ b/vendor/simshaun/recurr/src/Recurr/Transformer/ConstraintInterface.php @@ -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); +} diff --git a/vendor/simshaun/recurr/src/Recurr/Transformer/TextTransformer.php b/vendor/simshaun/recurr/src/Recurr/Transformer/TextTransformer.php new file mode 100644 index 0000000..0fdef0b --- /dev/null +++ b/vendor/simshaun/recurr/src/Recurr/Transformer/TextTransformer.php @@ -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; + } +} diff --git a/vendor/simshaun/recurr/src/Recurr/Transformer/Translator.php b/vendor/simshaun/recurr/src/Recurr/Transformer/Translator.php new file mode 100644 index 0000000..cf3cb2a --- /dev/null +++ b/vendor/simshaun/recurr/src/Recurr/Transformer/Translator.php @@ -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; + } +} diff --git a/vendor/simshaun/recurr/src/Recurr/Transformer/TranslatorInterface.php b/vendor/simshaun/recurr/src/Recurr/Transformer/TranslatorInterface.php new file mode 100644 index 0000000..96e77f4 --- /dev/null +++ b/vendor/simshaun/recurr/src/Recurr/Transformer/TranslatorInterface.php @@ -0,0 +1,8 @@ +<?php + +namespace Recurr\Transformer; + +interface TranslatorInterface +{ + public function trans($string); +} diff --git a/vendor/simshaun/recurr/src/Recurr/Weekday.php b/vendor/simshaun/recurr/src/Recurr/Weekday.php new file mode 100644 index 0000000..b972b9d --- /dev/null +++ b/vendor/simshaun/recurr/src/Recurr/Weekday.php @@ -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]; + } +} |