diff options
Diffstat (limited to 'vendor/simshaun/recurr/src/Recurr/Transformer')
10 files changed, 1608 insertions, 0 deletions
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); +} |