summaryrefslogtreecommitdiffstats
path: root/vendor/simshaun/recurr/src/Recurr/Transformer
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/simshaun/recurr/src/Recurr/Transformer')
-rw-r--r--vendor/simshaun/recurr/src/Recurr/Transformer/ArrayTransformer.php736
-rw-r--r--vendor/simshaun/recurr/src/Recurr/Transformer/ArrayTransformerConfig.php65
-rw-r--r--vendor/simshaun/recurr/src/Recurr/Transformer/Constraint.php21
-rw-r--r--vendor/simshaun/recurr/src/Recurr/Transformer/Constraint/AfterConstraint.php64
-rw-r--r--vendor/simshaun/recurr/src/Recurr/Transformer/Constraint/BeforeConstraint.php64
-rw-r--r--vendor/simshaun/recurr/src/Recurr/Transformer/Constraint/BetweenConstraint.php81
-rw-r--r--vendor/simshaun/recurr/src/Recurr/Transformer/ConstraintInterface.php25
-rw-r--r--vendor/simshaun/recurr/src/Recurr/Transformer/TextTransformer.php502
-rw-r--r--vendor/simshaun/recurr/src/Recurr/Transformer/Translator.php42
-rw-r--r--vendor/simshaun/recurr/src/Recurr/Transformer/TranslatorInterface.php8
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);
+}