From dde2b5ca30b7d9fcb7130c1e920197c43281bbdd Mon Sep 17 00:00:00 2001 From: Kay Lukas Date: Wed, 28 Nov 2018 15:28:32 +0100 Subject: [PATCH 01/92] Fix bug in by year day --- lib/Recur/RRuleIterator.php | 7 +-- tests/VObject/Recur/RRuleIteratorTest.php | 59 +++++++++++++++-------- 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/lib/Recur/RRuleIterator.php b/lib/Recur/RRuleIterator.php index 75342a2a8..2c6943c42 100644 --- a/lib/Recur/RRuleIterator.php +++ b/lib/Recur/RRuleIterator.php @@ -570,11 +570,12 @@ protected function nextYearly() // loop through all YearDay and Days to check all the combinations foreach ($this->byYearDay as $byYearDay) { $date = clone $this->currentDate; - $date = $date->setDate($currentYear, 1, 1); if ($byYearDay > 0) { - $date = $date->add(new \DateInterval('P'.$byYearDay.'D')); + $date = $date->setDate($currentYear, 1, 1); + $date = $date->add(new \DateInterval('P'.($byYearDay - 1).'D')); } else { - $date = $date->sub(new \DateInterval('P'.abs($byYearDay).'D')); + $date = $date->setDate($currentYear, 12, 31); + $date = $date->sub(new \DateInterval('P'.abs($byYearDay + 1).'D')); } if ($date > $this->currentDate && in_array($date->format('N'), $dayOffsets)) { diff --git a/tests/VObject/Recur/RRuleIteratorTest.php b/tests/VObject/Recur/RRuleIteratorTest.php index b6a8fdc53..e60344aec 100644 --- a/tests/VObject/Recur/RRuleIteratorTest.php +++ b/tests/VObject/Recur/RRuleIteratorTest.php @@ -488,19 +488,36 @@ public function testYearlyByMonthByDay() ); } + public function testYearlyNewYearsEve() + { + $this->parse( + 'FREQ=YEARLY;COUNT=7;INTERVAL=2;BYYEARDAY=1', + '2011-01-01 03:07:00', + [ + '2011-01-01 03:07:00', + '2013-01-01 03:07:00', + '2015-01-01 03:07:00', + '2017-01-01 03:07:00', + '2019-01-01 03:07:00', + '2021-01-01 03:07:00', + '2023-01-01 03:07:00', + ] + ); + } + public function testYearlyByYearDay() { $this->parse( 'FREQ=YEARLY;COUNT=7;INTERVAL=2;BYYEARDAY=190', - '2011-07-10 03:07:00', + '2011-07-09 03:07:00', [ - '2011-07-10 03:07:00', - '2013-07-10 03:07:00', - '2015-07-10 03:07:00', - '2017-07-10 03:07:00', - '2019-07-10 03:07:00', - '2021-07-10 03:07:00', - '2023-07-10 03:07:00', + '2011-07-09 03:07:00', + '2013-07-09 03:07:00', + '2015-07-09 03:07:00', + '2017-07-09 03:07:00', + '2019-07-09 03:07:00', + '2021-07-09 03:07:00', + '2023-07-09 03:07:00', ] ); } @@ -521,7 +538,7 @@ public function testYearlyByYearDayImmutable() $parser->next(); $item = $parser->current(); - $this->assertEquals($item->format('Y-m-d H:i:s'), '2013-07-10 03:07:00'); + $this->assertEquals($item->format('Y-m-d H:i:s'), '2013-07-09 03:07:00'); } public function testYearlyByYearDayMultiple() @@ -531,13 +548,13 @@ public function testYearlyByYearDayMultiple() '2011-07-10 14:53:11', [ '2011-07-10 14:53:11', - '2011-10-29 14:53:11', - '2014-07-10 14:53:11', - '2014-10-29 14:53:11', - '2017-07-10 14:53:11', - '2017-10-29 14:53:11', - '2020-07-09 14:53:11', - '2020-10-28 14:53:11', + '2011-10-28 14:53:11', + '2014-07-09 14:53:11', + '2014-10-28 14:53:11', + '2017-07-09 14:53:11', + '2017-10-28 14:53:11', + '2020-07-08 14:53:11', + '2020-10-27 14:53:11', ] ); } @@ -549,11 +566,11 @@ public function testYearlyByYearDayByDay() '2001-04-07 14:53:11', [ '2001-04-07 14:53:11', - '2006-04-08 14:53:11', - '2012-04-07 14:53:11', - '2017-04-08 14:53:11', - '2023-04-08 14:53:11', - '2034-04-08 14:53:11', + '2007-04-07 14:53:11', + '2018-04-07 14:53:11', + '2024-04-06 14:53:11', + '2029-04-07 14:53:11', + '2035-04-07 14:53:11', ] ); } From e980e2825a64d6182447a348697135ea064c72c2 Mon Sep 17 00:00:00 2001 From: Valentin Date: Tue, 10 Sep 2019 15:18:40 +0200 Subject: [PATCH 02/92] Change composer name --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index ad026879e..d757a4bc8 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "sabre/vobject", + "name": "protonlabs/vobject", "description" : "The VObject library for PHP allows you to easily parse and manipulate iCalendar and vCard objects", "keywords" : [ "iCalendar", From 2b27d82dc28cc7bd5d880caec104c9b72b9d0145 Mon Sep 17 00:00:00 2001 From: Valentin BONNEAUD Date: Tue, 10 Sep 2019 15:33:58 +0200 Subject: [PATCH 03/92] Add daily occurences to nextMonth and NextYear (#3) --- lib/Recur/RRuleIterator.php | 127 ++++++++++++++++++---- tests/VObject/Recur/RRuleIteratorTest.php | 40 +++++++ 2 files changed, 145 insertions(+), 22 deletions(-) diff --git a/lib/Recur/RRuleIterator.php b/lib/Recur/RRuleIterator.php index 2c6943c42..9ca0fdbfb 100644 --- a/lib/Recur/RRuleIterator.php +++ b/lib/Recur/RRuleIterator.php @@ -420,6 +420,9 @@ protected function nextWeekly() protected function nextMonthly() { $currentDayOfMonth = $this->currentDate->format('j'); + $currentHourOfMonth = $this->currentDate->format('G'); + $currentMinuteOfMonth = $this->currentDate->format('i'); + $currentSecondOfMonth = $this->currentDate->format('s'); if (!$this->byMonthDay && !$this->byDay) { // If the current day is higher than the 28th, rollover can // occur to the next month. We Must skip these invalid @@ -445,7 +448,23 @@ protected function nextMonthly() foreach ($occurrences as $occurrence) { // The first occurrence thats higher than the current // day of the month wins. - if ($occurrence > $currentDayOfMonth) { + if ($occurrence[0] > $currentDayOfMonth) { + break 2; + } elseif ($occurrence[0] < $currentDayOfMonth) { + continue; + } + if ($occurrence[1] > $currentHourOfMonth) { + break 2; + } elseif ($occurrence[1] < $currentHourOfMonth) { + continue; + } + + if ($occurrence[2] > $currentMinuteOfMonth) { + break 2; + } elseif ($occurrence[2] < $currentMinuteOfMonth) { + continue; + } + if ($occurrence[3] > $currentSecondOfMonth) { break 2; } } @@ -464,6 +483,9 @@ protected function nextMonthly() // This goes to 0 because we need to start counting at the // beginning. $currentDayOfMonth = 0; + $currentHourOfMonth = 0; + $currentMinuteOfMonth = 0; + $currentSecondOfMonth = 0; // To prevent running this forever (better: until we hit the max date of DateTimeImmutable) we simply // stop at 9999-12-31. Looks like the year 10000 problem is not solved in php .... @@ -477,8 +499,8 @@ protected function nextMonthly() $this->currentDate = $this->currentDate->setDate( (int) $this->currentDate->format('Y'), (int) $this->currentDate->format('n'), - (int) $occurrence - ); + $occurrence[0] + )->setTime($occurrence[1], $occurrence[2], $occurrence[3]); } /** @@ -489,6 +511,9 @@ protected function nextYearly() $currentMonth = $this->currentDate->format('n'); $currentYear = $this->currentDate->format('Y'); $currentDayOfMonth = $this->currentDate->format('j'); + $currentHourOfMonth = $this->currentDate->format('G'); + $currentMinuteOfMonth = $this->currentDate->format('i'); + $currentSecondOfMonth = $this->currentDate->format('s'); // No sub-rules, so we just advance by year if (empty($this->byMonth)) { @@ -600,25 +625,38 @@ protected function nextYearly() return; } - $currentMonth = $this->currentDate->format('n'); - $currentYear = $this->currentDate->format('Y'); - $currentDayOfMonth = $this->currentDate->format('j'); - $advancedToNewMonth = false; // If we got a byDay or getMonthDay filter, we must first expand // further. if ($this->byDay || $this->byMonthDay) { while (true) { - $occurrences = $this->getMonthlyOccurrences(); - - foreach ($occurrences as $occurrence) { - // The first occurrence that's higher than the current - // day of the month wins. - // If we advanced to the next month or year, the first - // occurrence is always correct. - if ($occurrence > $currentDayOfMonth || $advancedToNewMonth) { - break 2; + // If the start date is incorrect we must directly jump to the next value + if (in_array($currentMonth, $this->byMonth)) { + $occurrences = $this->getMonthlyOccurrences(); + foreach ($occurrences as $occurrence) { + // The first occurrence that's higher than the current + // day of the month wins. + // If we advanced to the next month or year, the first + // occurrence is always correct. + if ($occurrence[0] > $currentDayOfMonth || $advancedToNewMonth) { + break 2; + } elseif ($occurrence[0] < $currentDayOfMonth) { + continue; + } + if ($occurrence[1] > $currentHourOfMonth) { + break 2; + } elseif ($occurrence[1] < $currentHourOfMonth) { + continue; + } + if ($occurrence[2] > $currentMinuteOfMonth) { + break 2; + } elseif ($occurrence[2] < $currentMinuteOfMonth) { + continue; + } + if ($occurrence[3] > $currentSecondOfMonth) { + break 2; + } } } @@ -645,8 +683,8 @@ protected function nextYearly() $this->currentDate = $this->currentDate->setDate( (int) $currentYear, (int) $currentMonth, - (int) $occurrence - ); + (int) $occurrence[0] + )->setTime($occurrence[1], $occurrence[2], $occurrence[3]); return; } else { @@ -810,7 +848,8 @@ protected function parseRRule($rrule) * Returns all the occurrences for a monthly frequency with a 'byDay' or * 'byMonthDay' expansion for the current month. * - * The returned list is an array of integers with the day of month (1-31). + * The returned list is an array of arrays with as first element the day of month (1-31); + * the hour; the minute and second of the occurence * * @return array */ @@ -896,8 +935,23 @@ protected function getMonthlyOccurrences() } else { $result = $byDayResults; } - $result = array_unique($result); - sort($result, SORT_NUMERIC); + + $result = $this->addDailyOccurences($result); + $result = array_unique($result, SORT_REGULAR); + $sortLex = function ($a, $b) { + if ($a[0] != $b[0]) { + return $a[0] - $b[0]; + } + if ($a[1] != $b[1]) { + return $a[1] - $b[1]; + } + if ($a[2] != $b[2]) { + return $a[2] - $b[2]; + } + + return $a[3] - $b[3]; + }; + usort($result, $sortLex); // The last thing that needs checking is the BYSETPOS. If it's set, it // means only certain items in the set survive the filter. @@ -915,11 +969,40 @@ protected function getMonthlyOccurrences() } } - sort($filteredResult, SORT_NUMERIC); + usort($result, $sortLex); return $filteredResult; } + /** + * Expends daily occurrences to an array of days that an event occurs on. + * + * @param array $result an array of integers with the day of month (1-31); + * + * @return array an array of arrays with the day of the month, hours, minute and seconds of the occurence + */ + protected function addDailyOccurences(array $result) + { + $output = []; + $hour = (int) $this->currentDate->format('G'); + $minute = (int) $this->currentDate->format('i'); + $second = (int) $this->currentDate->format('s'); + foreach ($result as $day) { + $seconds = $this->bySecond ? $this->bySecond : [$second]; + $minutes = $this->byMinute ? $this->byMinute : [$minute]; + $hours = $this->byHour ? $this->byHour : [$hour]; + foreach ($hours as $h) { + foreach ($minutes as $m) { + foreach ($seconds as $s) { + $output[] = [(int) $day, (int) $h, (int) $m, (int) $s]; + } + } + } + } + + return $output; + } + /** * Simple mapping from iCalendar day names to day numbers. * diff --git a/tests/VObject/Recur/RRuleIteratorTest.php b/tests/VObject/Recur/RRuleIteratorTest.php index e60344aec..63f6aae5b 100644 --- a/tests/VObject/Recur/RRuleIteratorTest.php +++ b/tests/VObject/Recur/RRuleIteratorTest.php @@ -593,6 +593,46 @@ public function testYearlyByYearDayNegative() ); } + public function testFirstLastSundayEveryOtherYearAt1530and1730InJanuary() + { + $this->parse('FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=1SU,-1SU;BYHOUR=15,17;BYMINUTE=30,35;BYSECOND=15,56', + '1999-12-01 12:34:56', + [ + '1999-12-01 12:34:56', + '2001-01-07 15:30:15', '2001-01-07 15:30:56', '2001-01-07 15:35:15', '2001-01-07 15:35:56', + '2001-01-07 17:30:15', '2001-01-07 17:30:56', '2001-01-07 17:35:15', '2001-01-07 17:35:56', + + '2001-01-28 15:30:15', '2001-01-28 15:30:56', '2001-01-28 15:35:15', '2001-01-28 15:35:56', + '2001-01-28 17:30:15', '2001-01-28 17:30:56', '2001-01-28 17:35:15', '2001-01-28 17:35:56', + + '2003-01-05 15:30:15', '2003-01-05 15:30:56', '2003-01-05 15:35:15', '2003-01-05 15:35:56', + '2003-01-05 17:30:15', '2003-01-05 17:30:56', '2003-01-05 17:35:15', '2003-01-05 17:35:56', + + '2003-01-26 15:30:15', '2003-01-26 15:30:56', '2003-01-26 15:35:15', '2003-01-26 15:35:56', + '2003-01-26 17:30:15', '2003-01-26 17:30:56', '2003-01-26 17:35:15', '2003-01-26 17:35:56', + ]); + } + + public function testFirstFourthSundayEveryOtherMonthAt830and930() + { + $this->parse('FREQ=MONTHLY;INTERVAL=2;BYDAY=1SU,4SU;BYHOUR=15,17;BYMINUTE=30,32;BYSECOND=11,12', + '2001-01-01 12:34:56', + [ + '2001-01-01 12:34:56', + '2001-01-07 15:30:11', '2001-01-07 15:30:12', '2001-01-07 15:32:11', '2001-01-07 15:32:12', + '2001-01-07 17:30:11', '2001-01-07 17:30:12', '2001-01-07 17:32:11', '2001-01-07 17:32:12', + + '2001-01-28 15:30:11', '2001-01-28 15:30:12', '2001-01-28 15:32:11', '2001-01-28 15:32:12', + '2001-01-28 17:30:11', '2001-01-28 17:30:12', '2001-01-28 17:32:11', '2001-01-28 17:32:12', + + '2001-03-04 15:30:11', '2001-03-04 15:30:12', '2001-03-04 15:32:11', '2001-03-04 15:32:12', + '2001-03-04 17:30:11', '2001-03-04 17:30:12', '2001-03-04 17:32:11', '2001-03-04 17:32:12', + + '2001-03-25 15:30:11', '2001-03-25 15:30:12', '2001-03-25 15:32:11', '2001-03-25 15:32:12', + '2001-03-25 17:30:11', '2001-03-25 17:30:12', '2001-03-25 17:32:11', '2001-03-25 17:32:12', + ]); + } + /** * @expectedException \Sabre\VObject\InvalidDataException */ From ab366e1f1a1b19397658e72b00c8b04132f9d275 Mon Sep 17 00:00:00 2001 From: Valentin BONNEAUD Date: Tue, 10 Sep 2019 16:00:43 +0200 Subject: [PATCH 04/92] Enhance fastforward speed if no `count` value has been given (#4) * Improve fast forward performance * Add testcases --- lib/Recur/RRuleIterator.php | 117 +++++-- tests/VObject/Recur/FastForwardTest.php | 431 ++++++++++++++++++++++++ 2 files changed, 511 insertions(+), 37 deletions(-) create mode 100644 tests/VObject/Recur/FastForwardTest.php diff --git a/lib/Recur/RRuleIterator.php b/lib/Recur/RRuleIterator.php index 9ca0fdbfb..64c581a31 100644 --- a/lib/Recur/RRuleIterator.php +++ b/lib/Recur/RRuleIterator.php @@ -2,7 +2,6 @@ namespace Sabre\VObject\Recur; -use DateTimeImmutable; use DateTimeInterface; use Iterator; use Sabre\VObject\DateTimeParser; @@ -88,30 +87,28 @@ public function rewind() /** * Goes on to the next iteration. + * + * @param int $amount */ - public function next() + public function next($amount = 1) { // Otherwise, we find the next event in the normal RRULE // sequence. switch ($this->frequency) { case 'hourly': - $this->nextHourly(); + $this->nextHourly($amount); break; - case 'daily': - $this->nextDaily(); + $this->nextDaily($amount); break; - case 'weekly': - $this->nextWeekly(); + $this->nextWeekly($amount); break; - case 'monthly': - $this->nextMonthly(); + $this->nextMonthly($amount); break; - case 'yearly': - $this->nextYearly(); + $this->nextYearly($amount); break; } ++$this->counter; @@ -137,9 +134,49 @@ public function isInfinite() */ public function fastForward(DateTimeInterface $dt) { + if (!isset($this->count)) { + do { + $diff = $this->currentDate->diff($dt); + switch ($this->frequency) { + case 'hourly': + $i = $diff->days * 24; + break; + case 'daily': + $i = $diff->days; + break; + case 'weekly': + $i = $diff->days / 7; + break; + case 'monthly': + $i = $diff->days / 30; + break; + case 'yearly': + $i = $diff->days / 365; + break; + } + $i /= $this->interval; + $i /= 4; + $i = floor($i); + $i = max(1, $i); + do { + $previousDate = clone $this->currentDate; + $this->next($i); + } while ($this->valid() && $this->currentDate < $dt); + + $this->currentDate = $previousDate; + // do one step to avoid deadlock + $this->next(); + } while ($i > 5 && $this->valid() && $this->currentDate < $dt); + } + while ($this->valid() && $this->currentDate < $dt) { $this->next(); } + + if (!isset($this->count)) { + // We don't know the counter at this point anymore + $this->counter = NAN; + } } /** @@ -309,18 +346,18 @@ public function fastForward(DateTimeInterface $dt) /** * Does the processing for advancing the iterator for hourly frequency. */ - protected function nextHourly() + protected function nextHourly($amount = 1) { - $this->currentDate = $this->currentDate->modify('+'.$this->interval.' hours'); + $this->currentDate = $this->currentDate->modify('+'.$amount * $this->interval.' hours'); } /** * Does the processing for advancing the iterator for daily frequency. */ - protected function nextDaily() + protected function nextDaily($amount = 1) { if (!$this->byHour && !$this->byDay) { - $this->currentDate = $this->currentDate->modify('+'.$this->interval.' days'); + $this->currentDate = $this->currentDate->modify('+'.$amount * $this->interval.' days'); return; } @@ -341,12 +378,14 @@ protected function nextDaily() if ($this->byHour) { if ('23' == $this->currentDate->format('G')) { // to obey the interval rule - $this->currentDate = $this->currentDate->modify('+'.($this->interval - 1).' days'); + $this->currentDate = $this->currentDate->modify('+'.(($amount * $this->interval) - 1).' days'); + $amount = 1; } $this->currentDate = $this->currentDate->modify('+1 hours'); } else { - $this->currentDate = $this->currentDate->modify('+'.$this->interval.' days'); + $this->currentDate = $this->currentDate->modify('+'.($amount * $this->interval).' days'); + $amount = 1; } // Current month of the year @@ -367,10 +406,10 @@ protected function nextDaily() /** * Does the processing for advancing the iterator for weekly frequency. */ - protected function nextWeekly() + protected function nextWeekly($amount = 1) { if (!$this->byHour && !$this->byDay) { - $this->currentDate = $this->currentDate->modify('+'.$this->interval.' weeks'); + $this->currentDate = $this->currentDate->modify('+'.($amount * $this->interval).' weeks'); return; } @@ -401,8 +440,8 @@ protected function nextWeekly() // We need to roll over to the next week if ($currentDay === $firstDay && (!$this->byHour || '0' == $currentHour)) { - $this->currentDate = $this->currentDate->modify('+'.($this->interval - 1).' weeks'); - + $this->currentDate = $this->currentDate->modify('+'.(($amount * $this->interval) - 1).' weeks'); + $amount = 1; // We need to go to the first day of this week, but only if we // are not already on this first day of this week. if ($this->currentDate->format('w') != $firstDay) { @@ -417,7 +456,7 @@ protected function nextWeekly() /** * Does the processing for advancing the iterator for monthly frequency. */ - protected function nextMonthly() + protected function nextMonthly($amount = 1) { $currentDayOfMonth = $this->currentDate->format('j'); $currentHourOfMonth = $this->currentDate->format('G'); @@ -428,9 +467,9 @@ protected function nextMonthly() // occur to the next month. We Must skip these invalid // entries. if ($currentDayOfMonth < 29) { - $this->currentDate = $this->currentDate->modify('+'.$this->interval.' months'); + $this->currentDate = $this->currentDate->modify('+'.($amount * $this->interval).' months'); } else { - $increase = 0; + $increase = $amount - 1; do { ++$increase; $tempDate = clone $this->currentDate; @@ -446,7 +485,7 @@ protected function nextMonthly() $occurrences = $this->getMonthlyOccurrences(); foreach ($occurrences as $occurrence) { - // The first occurrence thats higher than the current + // The first occurrence that's higher than the current // day of the month wins. if ($occurrence[0] > $currentDayOfMonth) { break 2; @@ -472,13 +511,14 @@ protected function nextMonthly() // If we made it all the way here, it means there were no // valid occurrences, and we need to advance to the next // month. - // - // This line does not currently work in hhvm. Temporary workaround - // follows: - // $this->currentDate->modify('first day of this month'); - $this->currentDate = new DateTimeImmutable($this->currentDate->format('Y-m-1 H:i:s'), $this->currentDate->getTimezone()); + $this->currentDate = $this->currentDate->setDate( + (int) $this->currentDate->format('Y'), + (int) $this->currentDate->format('n'), + 1 + ); // end of workaround - $this->currentDate = $this->currentDate->modify('+ '.$this->interval.' months'); + $this->currentDate = $this->currentDate->modify('+ '.($amount * $this->interval).' months'); + $amount = 1; // This goes to 0 because we need to start counting at the // beginning. @@ -506,10 +546,10 @@ protected function nextMonthly() /** * Does the processing for advancing the iterator for yearly frequency. */ - protected function nextYearly() + protected function nextYearly($amount = 1) { - $currentMonth = $this->currentDate->format('n'); $currentYear = $this->currentDate->format('Y'); + $currentMonth = $this->currentDate->format('n'); $currentDayOfMonth = $this->currentDate->format('j'); $currentHourOfMonth = $this->currentDate->format('G'); $currentMinuteOfMonth = $this->currentDate->format('i'); @@ -573,7 +613,8 @@ protected function nextYearly() } // if there is no date found, check the next year - $currentYear += $this->interval; + $currentYear += $amount * $this->interval; + $amount = 1; } } @@ -615,12 +656,13 @@ protected function nextYearly() } // if there is no date found, check the next year - $currentYear += $this->interval; + $currentYear += ($amount * $this->interval); + $amount = 1; } } // The easiest form - $this->currentDate = $this->currentDate->modify('+'.$this->interval.' years'); + $this->currentDate = $this->currentDate->modify('+'.($amount * $this->interval).' years'); return; } @@ -667,7 +709,8 @@ protected function nextYearly() do { ++$currentMonth; if ($currentMonth > 12) { - $currentYear += $this->interval; + $currentYear += ($amount * $this->interval); + $amount = 1; $currentMonth = 1; } } while (!in_array($currentMonth, $this->byMonth)); diff --git a/tests/VObject/Recur/FastForwardTest.php b/tests/VObject/Recur/FastForwardTest.php new file mode 100644 index 000000000..df9120561 --- /dev/null +++ b/tests/VObject/Recur/FastForwardTest.php @@ -0,0 +1,431 @@ +fastForward($ffDate); + $ru = getrusage(); + $endTime = $ru['ru_utime.tv_sec'] * 1000000 + $ru['ru_utime.tv_usec']; + $this->assertLessThan(self::FF_TIMEOUT, $endTime - $startTime); + } + + public function testFastForwardYearlyBasic() + { + $startDate = new DateTime('1970-10-23 00:00:00', new DateTimeZone('zulu')); + $ffDate = new DateTime('midnight', new DateTimeZone('zulu')); + $ffDate->setDate(99999, 1, 1); + $rrule = new RRuleIterator('FREQ=YEARLY', $startDate); + + $this->fastForward($rrule, $ffDate); + + $year = 60 * 60 * 24 * 365; + $expected = (new DateTime()) + ->setTimezone(new DateTimeZone('zulu')) + ->setDate(99999, 10, 23) + ->setTime(0, 0, 0) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + $rrule->next(); + // It's a leap + $expected += $year + 24 * 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + $rrule->next(); + $expected += $year; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + $rrule->next(); + $expected += $year; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + $rrule->next(); + $expected += $year; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + $rrule->next(); + // leap + $expected += $year + 24 * 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + $rrule->next(); + $expected += $year; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + $rrule->next(); + $expected += $year; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + $rrule->next(); + $expected += $year; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + $rrule->next(); + // leap + $expected += $year + 24 * 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + $rrule->next(); + } + + public function testFastForwardYearlyByYearDay() + { + $startDate = new \DateTime('1970-10-23 00:00:00', new \DateTimeZone('zulu')); + $ffDate = new \DateTime('midnight', new DateTimeZone('zulu')); + $ffDate->setDate(99998, 12, 31); + $rrule = new RRuleIterator('FREQ=YEARLY;BYYEARDAY=1,20,300', $startDate); + + $this->fastForward($rrule, $ffDate); + + $day = 60 * 60 * 24; + $expected = (new DateTime()) + ->setTimezone(new DateTimeZone('zulu')) + ->setDate(99999, 1, 1)// 20th day + ->setTime(0, 0, 0) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + $rrule->next(); + // 300th day + $expected += 19 * $day; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + $rrule->next(); + // 1st day + $expected += 280 * $day; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + // 20th day + $expected += 66 * $day; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + // 300th day + $rrule->next(); + $expected += 19 * $day; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + $rrule->next(); + $expected += 280 * $day; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + $rrule->next(); + // 1st day (leap year, we have 366 days in this year) + $expected += 67 * $day; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + $rrule->next(); + $expected += 19 * $day; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + } + + public function testFastForwardYearlyByWeekNo() + { + $startDate = new \DateTime('1970-10-23 00:00:00', new \DateTimeZone('zulu')); + $ffDate = new \DateTime('midnight', new DateTimeZone('zulu')); + $ffDate->setDate(99999, 1, 1); + $rrule = new RRuleIterator('FREQ=YEARLY;BYWEEKNO=1,20', $startDate); + + $this->fastForward($rrule, $ffDate); + + $day = 60 * 60 * 24; + $week = 7 * $day; + $expected = (new DateTime()) + ->setTimezone(new DateTimeZone('zulu')) + ->setDate(99999, 1, 4)// 1st day + ->setTime(0, 0, 0) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + $rrule->next(); + $expected += $week * 19; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + } + + public function testFastForwardYearlyAdvanced() + { + $startDate = new \DateTime('1970-10-23 12:34:56', new \DateTimeZone('zulu')); + $ffDate = new \DateTime('midnight', new DateTimeZone('zulu')); + $ffDate->setDate(9999, 1, 20)->setTime(0, 0, 13); + $rrule = new RRuleIterator('FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=SU;BYHOUR=8,9;BYMINUTE=30', $startDate); + + $this->fastForward($rrule, $ffDate); + + $expected = (new DateTime('midnight', new DateTimeZone('zulu'))) + ->setDate(10000, 1, 2) + ->setTime(8, 30, 56) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $rrule->next(); + $expected += 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $rrule->next(); + $expected += 7 * 24 * 60 * 60 - 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $rrule->next(); + $expected += 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $rrule->next(); + $expected += 7 * 24 * 60 * 60 - 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $rrule->next(); + $expected += 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $rrule->next(); + $expected += 7 * 24 * 60 * 60 - 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $rrule->next(); + $expected += 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $rrule->next(); + $expected += 7 * 24 * 60 * 60 - 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $rrule->next(); + $expected += 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // jump to 6th january 10002 + $rrule->next(); + $expected = (new DateTime('midnight', new DateTimeZone('zulu'))) + ->setDate(10002, 1, 6) + ->setTime(8, 30, 56) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $rrule->next(); + $expected += 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + } + + public function testFastForwardMonthlyBasic() + { + $startDate = new \DateTime('1970-10-23 22:42:31', new \DateTimeZone('zulu')); + $ffDate = new \DateTime('midnight', new DateTimeZone('zulu')); + $ffDate->setDate(18000, 1, 1); + $rrule = new RRuleIterator('FREQ=MONTHLY', $startDate); + + $this->fastForward($rrule, $ffDate); + + $expected = (new DateTime('midnight', new DateTimeZone('zulu'))) + ->setDate(18000, 1, 23) + ->setTime(22, 42, 31) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // february + $rrule->next(); + $expected += 31 * 24 * 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + // march + $rrule->next(); + $expected += 29 * 24 * 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + // april + $rrule->next(); + $expected += 31 * 24 * 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + // may + $rrule->next(); + $expected += 30 * 24 * 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + // june + $rrule->next(); + $expected += 31 * 24 * 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + // july + $rrule->next(); + $expected += 30 * 24 * 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + // august + $rrule->next(); + $expected += 31 * 24 * 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + } + + public function testFastForwardMonthly31thDay() + { + $timezone = 'America/New_York'; + $startDate = new \DateTime('1970-01-31 00:00:00', new \DateTimeZone($timezone)); + $ffDate = new \DateTime('midnight', new DateTimeZone('zulu')); + $ffDate->setDate(18000, 1, 1); + $rrule = new RRuleIterator('FREQ=MONTHLY', $startDate); + + $this->fastForward($rrule, $ffDate); + + $expected = (new DateTime('midnight', new DateTimeZone('America/New_York'))) + ->setDate(18000, 1, 31) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // march + $rrule->next(); + $expected += (29 + 31) * 24 * 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // may + $rrule->next(); + $expected += (30 + 31) * 24 * 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // july + $rrule->next(); + $expected += (30 + 31) * 24 * 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // august + $rrule->next(); + $expected += 31 * 24 * 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // october + $rrule->next(); + $expected += (30 + 31) * 24 * 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // december + $rrule->next(); + $expected += (30 + 31) * 24 * 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + } + + public function testFastForwardMonthlyAdvanced() + { + $timezone = 'America/New_York'; + $startDate = new \DateTime('1970-01-31 00:00:00', new DateTimeZone($timezone)); + $ffDate = new \DateTime('midnight', new DateTimeZone('zulu')); + $ffDate->setDate(8000, 1, 1); + $rrule = new RRuleIterator('FREQ=MONTHLY;INTERVAL=2;BYDAY=1MO,2TU,3WE,4TH', $startDate); + + $this->fastForward($rrule, $ffDate); + + // monday + $expected = (new DateTime('midnight', new DateTimeZone($timezone))) + ->setDate(8000, 1, 3) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // tuesday + $expected += 8 * 24 * 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // wednesday + $expected += 8 * 24 * 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // thursday + $expected += 8 * 24 * 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // monday march + $expected += (29 + 10) * 24 * 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // tuesday + $expected += 8 * 24 * 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // wednesday (this month starts on wednesday so that's just the next day) + $expected += 1 * 24 * 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // thursday + $expected += 8 * 24 * 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + } + + public function testFastForwardDailyBasic() + { + $timezone = 'America/New_York'; + $startDate = new \DateTime('1970-10-23 00:00:00', new \DateTimeZone($timezone)); + $ffDate = new \DateTime('midnight', new DateTimeZone('zulu')); + $ffDate->setDate(4000, 1, 1); + $rrule = new RRuleIterator('FREQ=DAILY', $startDate); + + $this->fastForward($rrule, $ffDate); + + $expected = (new DateTime('midnight', new DateTimeZone($timezone))) + ->setDate(4000, 1, 1) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $expected += 24 * 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $expected += 24 * 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $expected += 24 * 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $expected += 24 * 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $expected += 24 * 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $expected += 24 * 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + } + + public function testFastForwardDailyAdvanced() + { + $timezone = 'America/New_York'; + $startDate = new \DateTime('1970-10-23 00:00:00', new \DateTimeZone($timezone)); + $ffDate = new \DateTime('midnight', new DateTimeZone('zulu')); + $ffDate->setDate(4000, 1, 1); + $rrule = new RRuleIterator('FREQ=DAILY;BYHOUR=16,17,18;INTERVAL=10', $startDate); + + $this->fastForward($rrule, $ffDate); + + $expected = (new DateTime('midnight', new DateTimeZone($timezone))) + ->setDate(4000, 1, 4) + ->setTime(16, 0, 0) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // 17:00 + $expected += 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // 18:00 + $expected += 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // 16:00 + $expected += 10 * 24 * 60 * 60 - 2 * 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // 17:00 + $expected += 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // 18:00 + $expected += 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // 16:00 + $expected += 10 * 24 * 60 * 60 - 2 * 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + } +} From af51976fd12ddbd47cac82178fb892ca3684aafa Mon Sep 17 00:00:00 2001 From: Valentin BONNEAUD Date: Tue, 10 Sep 2019 16:35:34 +0200 Subject: [PATCH 05/92] Update README (#5) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5030cf276..97a5e6dc3 100644 --- a/README.md +++ b/README.md @@ -12,12 +12,12 @@ Installation Make sure you have [Composer][1] installed, and then run: - composer require sabre/vobject "^4.0" + composer require protonlabs/vobject "^4.0" This package requires PHP 5.5. If you need the PHP 5.3/5.4 version of this package instead, use: - composer require sabre/vobject "^3.4" + composer require protonlabs/vobject "^3.4" Usage From be709bca0cc1236db159b806b796a59e0764e372 Mon Sep 17 00:00:00 2001 From: Valentin BONNEAUD Date: Fri, 24 Jan 2020 19:14:52 +0100 Subject: [PATCH 06/92] Add FastForward Before (#6) * Add FastForward Before * Fix case no occurrence in frequency window * Split functions * Review * Review --- lib/Recur/RRuleIterator.php | 138 +++-- tests/VObject/Recur/FastForwardBeforeTest.php | 528 ++++++++++++++++++ 2 files changed, 630 insertions(+), 36 deletions(-) create mode 100644 tests/VObject/Recur/FastForwardBeforeTest.php diff --git a/lib/Recur/RRuleIterator.php b/lib/Recur/RRuleIterator.php index 64c581a31..90a1d5533 100644 --- a/lib/Recur/RRuleIterator.php +++ b/lib/Recur/RRuleIterator.php @@ -127,55 +127,121 @@ public function isInfinite() } /** - * This method allows you to quickly go to the next occurrence after the - * specified date. + * This method allows you to quickly go to the next occurrence after the specified date. * * @param DateTimeInterface $dt */ public function fastForward(DateTimeInterface $dt) { + // We don't do any jumps if we have a count limit as we have to keep track of the number of occurrences if (!isset($this->count)) { - do { - $diff = $this->currentDate->diff($dt); - switch ($this->frequency) { - case 'hourly': - $i = $diff->days * 24; - break; - case 'daily': - $i = $diff->days; - break; - case 'weekly': - $i = $diff->days / 7; - break; - case 'monthly': - $i = $diff->days / 30; - break; - case 'yearly': - $i = $diff->days / 365; - break; - } - $i /= $this->interval; - $i /= 4; - $i = floor($i); - $i = max(1, $i); - do { - $previousDate = clone $this->currentDate; - $this->next($i); - } while ($this->valid() && $this->currentDate < $dt); - - $this->currentDate = $previousDate; - // do one step to avoid deadlock - $this->next(); - } while ($i > 5 && $this->valid() && $this->currentDate < $dt); + $this->jumpForward($dt); } while ($this->valid() && $this->currentDate < $dt) { $this->next(); } + } + /** + * This method allows you to quickly go to the next occurrence before the specified date. + * + * @param DateTimeInterface $dt + */ + public function fastForwardBefore(DateTimeInterface $dt) + { + // We don't do any jumps if we have a count limit as we have to keep track of the number of occurrences if (!isset($this->count)) { - // We don't know the counter at this point anymore - $this->counter = NAN; + $this->jumpForward($dt); + } + + $previousDate = null; + while ($this->valid() && $this->currentDate < $dt) { + $previousDate = clone $this->currentDate; + $this->next(); + } + + isset($previousDate) && $this->currentDate = $previousDate; + } + + /** + * Return the frequency in number of days. + * + * @return float|int|null + */ + private function getFrequencyCoeff() + { + $frequencyCoeff = null; + + switch ($this->frequency) { + case 'hourly': + $frequencyCoeff = 1 / 24; + break; + case 'daily': + $frequencyCoeff = 1; + break; + case 'weekly': + $frequencyCoeff = 7; + break; + case 'monthly': + $frequencyCoeff = 30; + break; + case 'yearly': + $frequencyCoeff = 365; + break; + } + + return $frequencyCoeff; + } + + /** + * Perform a fast forward by doing jumps based on the distance of the requested date and the frequency of the + * recurrence rule. Will set the position of the iterator to the last occurrence before the requested date. If the + * fast forwarding failed, the position will be reset. + * + * @param DateTimeInterface $dt + */ + private function jumpForward(DateTimeInterface $dt) + { + $frequencyCoeff = $this->getFrequencyCoeff(); + + do { + // We estimate the number of jumps to reach $dt. This is an estimate as the number of generated event within + // a frequency interval is assumed to be 1 (in reality, it could be anything >= 0) + $diff = $this->currentDate->diff($dt); + $estimatedOccurrences = $diff->days / $frequencyCoeff; + $estimatedOccurrences /= $this->interval; + + // We want to do small jumps to not overshot + $jumpSize = floor($estimatedOccurrences / 4); + $jumpSize = (int) max(1, $jumpSize); + + // If we are too close to the desired occurrence, we abort the jumping + if ($jumpSize <= 4) { + break; + } + + do { + $previousDate = clone $this->currentDate; + $this->next($jumpSize); + } while ($this->valid() && $this->currentDate < $dt); + + $this->currentDate = clone $previousDate; + // Do one step to avoid deadlock + $this->next(); + } while ($this->valid() && $this->currentDate < $dt); + + // We undo the last next as it made the $this->currentDate < $dt false + // we want the last that validate it. + isset($previousDate) && $this->currentDate = clone $previousDate; + + // We don't know the counter at this point anymore + $this->counter = NAN; + + // It's possible that we miss the previous occurrence by jumping too much, in this case we reset the rrule and + // do the normal forward. + if ($this->currentDate >= $dt) { + $this->rewind(); } } diff --git a/tests/VObject/Recur/FastForwardBeforeTest.php b/tests/VObject/Recur/FastForwardBeforeTest.php new file mode 100644 index 000000000..8b7755b1c --- /dev/null +++ b/tests/VObject/Recur/FastForwardBeforeTest.php @@ -0,0 +1,528 @@ +fastForwardBefore($ffDate); + $ru = getrusage(); + $endTime = $ru['ru_utime.tv_sec'] * 1000000 + $ru['ru_utime.tv_usec']; + $this->assertLessThan(self::FF_TIMEOUT, $endTime - $startTime); + } + + public function testFastForwardBeforeYearlyBasic() + { + $startDate = new DateTime('1970-10-23 00:00:00', new DateTimeZone('zulu')); + $ffDate = new DateTime('midnight', new DateTimeZone('zulu')); + $ffDate->setDate(99999, 1, 1); + $rrule = new RRuleIterator('FREQ=YEARLY', $startDate); + + $this->fastForward($rrule, $ffDate); + + $year = 60 * 60 * 24 * 365; + $expected = (new DateTime()) + ->setTimezone(new DateTimeZone('zulu')) + ->setDate(99998, 10, 23) + ->setTime(0, 0, 0) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $rrule->next(); + $expected += $year; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $rrule->next(); + // It's a leap + $expected += $year + 24 * 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $rrule->next(); + $expected += $year; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $rrule->next(); + $expected += $year; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $rrule->next(); + $expected += $year; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $rrule->next(); + // leap + $expected += $year + 24 * 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $rrule->next(); + $expected += $year; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $rrule->next(); + $expected += $year; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $rrule->next(); + $expected += $year; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $rrule->next(); + // leap + $expected += $year + 24 * 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $rrule->next(); + } + + public function testFastForwardBeforeYearlyByYearDay() + { + $startDate = new \DateTime('1970-10-23 00:00:00', new \DateTimeZone('zulu')); + $ffDate = new \DateTime('midnight', new DateTimeZone('zulu')); + $ffDate->setDate(99999, 1, 5); + $rrule = new RRuleIterator('FREQ=YEARLY;BYYEARDAY=1,20,300', $startDate); + + $this->fastForward($rrule, $ffDate); + + // 1st day + $day = 60 * 60 * 24; + $expected = (new DateTime()) + ->setTimezone(new DateTimeZone('zulu')) + ->setDate(99999, 1, 1) + ->setTime(0, 0, 0) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // 20th day + $rrule->next(); + $expected += 19 * $day; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // 300th day + $rrule->next(); + $expected += 280 * $day; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // 1st day + $expected += 66 * $day; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // 20th day + $rrule->next(); + $expected += 19 * $day; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // 300th day + $rrule->next(); + $expected += 280 * $day; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // 1st day (leap year, we have 366 days in this year) + $rrule->next(); + $expected += 67 * $day; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // 20th day + $rrule->next(); + $expected += 19 * $day; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + } + + public function testFastForwardBeforeYearlyByWeekNo() + { + $startDate = new \DateTime('1970-10-23 00:00:00', new \DateTimeZone('zulu')); + $ffDate = new \DateTime('midnight', new DateTimeZone('zulu')); + $ffDate->setDate(99999, 1, 5); + $rrule = new RRuleIterator('FREQ=YEARLY;BYWEEKNO=1,20', $startDate); + + $this->fastForward($rrule, $ffDate); + + $day = 60 * 60 * 24; + $week = 7 * $day; + + // 1st week + $expected = (new DateTime()) + ->setTimezone(new DateTimeZone('zulu')) + ->setDate(99999, 1, 4) + ->setTime(0, 0, 0) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // 20st week + $rrule->next(); + $expected += $week * 19; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + } + + public function testFastForwardBeforeYearlyAdvanced() + { + $startDate = new \DateTime('1970-10-23 12:34:56', new \DateTimeZone('zulu')); + $ffDate = new \DateTime('midnight', new DateTimeZone('zulu')); + $ffDate->setDate(10000, 1, 2)->setTime(8, 44, 13); + $rrule = new RRuleIterator('FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=SU;BYHOUR=8,9;BYMINUTE=30', $startDate); + + $this->fastForward($rrule, $ffDate); + + $expected = (new DateTime('midnight', new DateTimeZone('zulu'))) + ->setDate(10000, 1, 2) + ->setTime(8, 30, 56) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $rrule->next(); + $expected += 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $rrule->next(); + $expected += 7 * 24 * 60 * 60 - 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $rrule->next(); + $expected += 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $rrule->next(); + $expected += 7 * 24 * 60 * 60 - 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $rrule->next(); + $expected += 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $rrule->next(); + $expected += 7 * 24 * 60 * 60 - 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $rrule->next(); + $expected += 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $rrule->next(); + $expected += 7 * 24 * 60 * 60 - 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $rrule->next(); + $expected += 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // jump to 6th january 10002 + $rrule->next(); + $expected = (new DateTime('midnight', new DateTimeZone('zulu'))) + ->setDate(10002, 1, 6) + ->setTime(8, 30, 56) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $rrule->next(); + $expected += 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + } + + public function testFastForwardBeforeMonthlyBasic() + { + $startDate = new \DateTime('1970-10-23 22:42:31', new \DateTimeZone('zulu')); + $ffDate = new \DateTime('midnight', new DateTimeZone('zulu')); + $ffDate->setDate(18000, 1, 30); + $rrule = new RRuleIterator('FREQ=MONTHLY', $startDate); + + $this->fastForward($rrule, $ffDate); + + $expected = (new DateTime('midnight', new DateTimeZone('zulu'))) + ->setDate(18000, 1, 23) + ->setTime(22, 42, 31) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // february + $rrule->next(); + $expected += 31 * 24 * 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + // march + $rrule->next(); + $expected += 29 * 24 * 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + // april + $rrule->next(); + $expected += 31 * 24 * 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + // may + $rrule->next(); + $expected += 30 * 24 * 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + // june + $rrule->next(); + $expected += 31 * 24 * 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + // july + $rrule->next(); + $expected += 30 * 24 * 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + // august + $rrule->next(); + $expected += 31 * 24 * 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + } + + public function testFastForwardBeforeMonthly31thDay() + { + $timezone = 'America/New_York'; + $startDate = new \DateTime('1970-01-31 00:00:00', new \DateTimeZone($timezone)); + $ffDate = new \DateTime('midnight', new DateTimeZone('zulu')); + $ffDate->setDate(18000, 2, 1); + $rrule = new RRuleIterator('FREQ=MONTHLY', $startDate); + + $this->fastForward($rrule, $ffDate); + + $expected = (new DateTime('midnight', new DateTimeZone('America/New_York'))) + ->setDate(18000, 1, 31) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // march + $rrule->next(); + $expected += (29 + 31) * 24 * 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // may + $rrule->next(); + $expected += (30 + 31) * 24 * 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // july + $rrule->next(); + $expected += (30 + 31) * 24 * 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // august + $rrule->next(); + $expected += 31 * 24 * 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // october + $rrule->next(); + $expected += (30 + 31) * 24 * 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // december + $rrule->next(); + $expected += (30 + 31) * 24 * 60 * 60; + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + } + + public function testFastForwardBeforeMonthlyAdvanced() + { + $timezone = 'America/New_York'; + $startDate = new \DateTime('1970-01-31 00:00:00', new DateTimeZone($timezone)); + $ffDate = new \DateTime('midnight', new DateTimeZone('zulu')); + $ffDate->setDate(8000, 1, 6); + // every 2 months on the 1st Monday, 2nd Tuesday, 3rd Wednesday and 4th Thursday + $rrule = new RRuleIterator('FREQ=MONTHLY;INTERVAL=2;BYDAY=1MO,2TU,3WE,4TH', $startDate); + + $this->fastForward($rrule, $ffDate); + + // monday + $expected = (new DateTime('midnight', new DateTimeZone($timezone))) + ->setDate(8000, 1, 3) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // tuesday + $expected += 8 * 24 * 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // wednesday + $expected += 8 * 24 * 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // thursday + $expected += 8 * 24 * 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // monday march + $expected += (29 + 10) * 24 * 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // tuesday + $expected += 8 * 24 * 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // wednesday (this month starts on wednesday so that's just the next day) + $expected += 1 * 24 * 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // thursday + $expected += 8 * 24 * 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + } + + public function testFastForwardBeforeDailyBasic() + { + $timezone = 'America/New_York'; + $startDate = new \DateTime('1970-10-23 00:00:00', new \DateTimeZone($timezone)); + $ffDate = new \DateTime('midnight', new DateTimeZone('zulu')); + $ffDate->setDate(4000, 1, 2); + $rrule = new RRuleIterator('FREQ=DAILY', $startDate); + + $this->fastForward($rrule, $ffDate); + + $expected = (new DateTime('midnight', new DateTimeZone($timezone))) + ->setDate(4000, 1, 1) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $expected += 24 * 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $expected += 24 * 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $expected += 24 * 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $expected += 24 * 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $expected += 24 * 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $expected += 24 * 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + } + + public function testFastForwardBeforeDailyAdvanced() + { + $timezone = 'America/New_York'; + $startDate = new \DateTime('1970-10-23 00:00:00', new \DateTimeZone($timezone)); + $ffDate = new \DateTime('midnight', new DateTimeZone($timezone)); + $ffDate->setDate(4000, 1, 4)->setTime(16, 30, 0); + // every 10 days at 16, 17 and 18 + $rrule = new RRuleIterator('FREQ=DAILY;BYHOUR=16,17,18;INTERVAL=10', $startDate); + + $this->fastForward($rrule, $ffDate); + + $expected = (new DateTime('midnight', new DateTimeZone($timezone))) + ->setDate(4000, 1, 4) + ->setTime(16, 0, 0) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // 17:00 + $expected += 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // 18:00 + $expected += 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // 16:00 + $expected += 10 * 24 * 60 * 60 - 2 * 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // 17:00 + $expected += 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // 18:00 + $expected += 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // 16:00 + $expected += 10 * 24 * 60 * 60 - 2 * 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + } + + public function testFastForwardBeforeHourlyBasic() + { + $timezone = 'America/New_York'; + $startDate = new \DateTime('1970-10-23 00:12:34', new \DateTimeZone($timezone)); + $ffDate = new \DateTime('midnight', new DateTimeZone($timezone)); + $ffDate->setDate(4000, 1, 2)->setTime(2, 0, 0); + $rrule = new RRuleIterator('FREQ=HOURLY', $startDate); + + $this->fastForward($rrule, $ffDate); + + $expected = (new DateTime('midnight', new DateTimeZone($timezone))) + ->setDate(4000, 1, 2) + ->setTime(1, 12, 34) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $expected += 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $expected += 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $expected += 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $expected += 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $expected += 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + $expected += 60 * 60; + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + } + + public function testFastForwardBeforeNotInFrequency() + { + $timezone = 'America/New_York'; + $startDate = new \DateTime('1970-10-23 00:00:00', new \DateTimeZone($timezone)); + $ffDate = new \DateTime('midnight', new DateTimeZone($timezone)); + $ffDate->setDate(2023, 3, 15)->setTime(1, 0, 0); + // every leap years + $rrule = new RRuleIterator('FREQ=YEARLY;BYMONTH=2;BYMONTHDAY=29', $startDate); + + $this->fastForward($rrule, $ffDate); + + $expected = (new DateTime('midnight', new DateTimeZone($timezone))) + ->setDate(2020, 2, 29) + ->setTime(0, 0, 0) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + + // the next leap year + $expected = (new DateTime('midnight', new DateTimeZone($timezone))) + ->setDate(2024, 2, 29) + ->setTime(0, 0, 0) + ->getTimestamp(); + $rrule->next(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + } +} From d78a3ac33deba388e84ecc16acd462dcaad475c8 Mon Sep 17 00:00:00 2001 From: Valentin BONNEAUD Date: Fri, 24 Jan 2020 19:16:48 +0100 Subject: [PATCH 07/92] Add fastForwardToEnd (#7) --- lib/Recur/RRuleIterator.php | 26 ++ tests/VObject/Recur/FastForwardToEndTest.php | 328 +++++++++++++++++++ 2 files changed, 354 insertions(+) create mode 100644 tests/VObject/Recur/FastForwardToEndTest.php diff --git a/lib/Recur/RRuleIterator.php b/lib/Recur/RRuleIterator.php index 90a1d5533..da9448d4b 100644 --- a/lib/Recur/RRuleIterator.php +++ b/lib/Recur/RRuleIterator.php @@ -164,6 +164,32 @@ public function fastForwardBefore(DateTimeInterface $dt) isset($previousDate) && $this->currentDate = $previousDate; } + /** + * This method allows you to quickly go to the last occurrence. + */ + public function fastForwardToEnd() + { + if ($this->isInfinite()) { + throw new \LogicException('Cannot fast forward to the end an infinite event.'); + } + + $hasCount = isset($this->count); + + if (isset($this->until) && !$hasCount) { + $this->jumpForward($this->until); + } + + // We fast forward until the last event occurrence + $previous = clone $this->currentDate; + while ($this->valid()) { + $previous = clone $this->currentDate; + $this->next(); + } + + $hasCount && $this->counter--; + $this->currentDate = $previous; + } + /** * Return the frequency in number of days. * diff --git a/tests/VObject/Recur/FastForwardToEndTest.php b/tests/VObject/Recur/FastForwardToEndTest.php new file mode 100644 index 000000000..a02930bb7 --- /dev/null +++ b/tests/VObject/Recur/FastForwardToEndTest.php @@ -0,0 +1,328 @@ +fastForwardToEnd(); + $ru = getrusage(); + $endTime = $ru['ru_utime.tv_sec'] * 1000000 + $ru['ru_utime.tv_usec']; + $this->assertLessThan(self::FF_TIMEOUT, $endTime - $startTime); + + echo "taken " . ($endTime - $startTime) / 1000 . "ms\n"; + $this->assertTrue($ruleIterator->valid()); + $this->assertNotNull($ruleIterator->current()); + } + + public function testFastForwardToEndWithoutEndYearlyBasic() + { + $startDate = new DateTime('1970-10-23 00:00:00', new DateTimeZone('zulu')); + $rrule = new RRuleIterator('FREQ=YEARLY', $startDate); + + $this->expectException(\LogicException::class); + $rrule->fastForwardToEnd(); + } + + public function testFastForwardToEndCountYearlyBasic() + { + $startDate = new DateTime('1970-10-23 00:00:00', new DateTimeZone('zulu')); + $rrule = new RRuleIterator('FREQ=YEARLY;COUNT=7777', $startDate); + + $this->fastForwardToEnd($rrule); + + $expected = (new DateTime()) + ->setTimezone(new DateTimeZone('zulu')) + ->setDate(9746, 10, 23) + ->setTime(0, 0, 0) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + } + + public function testFastForwardToEndUntilYearlyBasic() + { + $startDate = new DateTime('1970-10-23 00:00:00', new DateTimeZone('zulu')); + $rrule = new RRuleIterator('FREQ=YEARLY;UNTIL=97461212T000000', $startDate); + + $this->fastForwardToEnd($rrule); + + $expected = (new DateTime()) + ->setTimezone(new DateTimeZone('zulu')) + ->setDate(9746, 10, 23) + ->setTime(0, 0, 0) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + } + + public function testFastForwardToEndCountYearlyByYearDay() + { + $startDate = new \DateTime('1970-10-23 00:00:00', new \DateTimeZone('zulu')); + $rrule = new RRuleIterator('FREQ=YEARLY;BYYEARDAY=1,20,300;COUNT=10000', $startDate); + + $this->fastForwardToEnd($rrule); + + $expected = (new DateTime()) + ->setTimezone(new DateTimeZone('zulu')) + ->setDate(5303, 1, 20) + ->setTime(0, 0, 0) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + } + + public function testFastForwardToEndUntilYearlyByYearDay() + { + $startDate = new \DateTime('1970-10-23 00:00:00', new \DateTimeZone('zulu')); + $rrule = new RRuleIterator('FREQ=YEARLY;BYYEARDAY=1,20,300;UNTIL=53030808T000000', $startDate); + + $this->fastForwardToEnd($rrule); + + $expected = (new DateTime()) + ->setTimezone(new DateTimeZone('zulu')) + ->setDate(5303, 1, 20) + ->setTime(0, 0, 0) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + } + + /* + * Issue CALENDAR-587 + public function testFastForwardToEndCountYearlyByWeekNo() + { + $startDate = new \DateTime('1970-10-23 00:00:00', new \DateTimeZone('zulu')); + $rrule = new RRuleIterator('FREQ=YEARLY;BYWEEKNO=1,20;COUNT=100', $startDate); + + $this->fastForwardToEnd($rrule); + + $expected = (new DateTime()) + ->setTimezone(new DateTimeZone('zulu')) + ->setDate(2019, 12, 30) + ->setTime(0, 0, 0) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + } + + public function testFastForwardToEndUntilYearlyByWeekNo() + { + $startDate = new \DateTime('1970-10-23 00:00:00', new \DateTimeZone('zulu')); + $rrule = new RRuleIterator('FREQ=YEARLY;BYWEEKNO=1,20;UNTIL=20030808T000000', $startDate); + + $this->fastForwardToEnd($rrule); + + $expected = (new DateTime()) + ->setTimezone(new DateTimeZone('zulu')) + ->setDate(2019, 12, 30) + ->setTime(0, 0, 0) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + } + */ + + public function testFastForwardToEndCountYearlyAdvanced() + { + $startDate = new \DateTime('1970-10-23 12:34:56', new \DateTimeZone('zulu')); + $rrule = new RRuleIterator('FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=SU;BYHOUR=8,9;BYMINUTE=30;COUNT=10000', $startDate); + + $this->fastForwardToEnd($rrule); + + $expected = (new DateTime('midnight', new DateTimeZone('zulu'))) + ->setDate(4226, 1, 1) + ->setTime(8, 30, 56) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + } + + public function testFastForwardToEndUntilYearlyAdvanced() + { + $startDate = new \DateTime('1970-10-23 12:34:56', new \DateTimeZone('zulu')); + $rrule = new RRuleIterator('FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=SU;BYHOUR=8,9;BYMINUTE=30;UNTIL=42180125T092500', $startDate); + + $this->fastForwardToEnd($rrule); + + $expected = (new DateTime('midnight', new DateTimeZone('zulu'))) + ->setDate(4218, 1, 25) + ->setTime(8, 30, 56) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + } + + public function testFastForwardToEndCountMonthlyBasic() + { + $startDate = new \DateTime('1970-10-23 22:42:31', new \DateTimeZone('zulu')); + $rrule = new RRuleIterator('FREQ=MONTHLY;COUNT=10000', $startDate); + + $this->fastForwardToEnd($rrule); + + $expected = (new DateTime('midnight', new DateTimeZone('zulu'))) + ->setDate(2804, 1, 23) + ->setTime(22, 42, 31) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + } + + public function testFastForwardToEndUntilMonthlyBasic() + { + $startDate = new \DateTime('1970-10-23 22:42:31', new \DateTimeZone('zulu')); + $rrule = new RRuleIterator('FREQ=MONTHLY;UNTIL=28040122T092500', $startDate); + + $this->fastForwardToEnd($rrule); + + $expected = (new DateTime('midnight', new DateTimeZone('zulu'))) + ->setDate(2803, 12, 23) + ->setTime(22, 42, 31) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + } + + public function testFastForwardToEndCountMonthly31thDay() + { + $startDate = new \DateTime('1970-01-31 00:00:00', new \DateTimeZone('America/New_York')); + $rrule = new RRuleIterator('FREQ=MONTHLY;COUNT=10000', $startDate); + + $this->fastForwardToEnd($rrule); + + $expected = (new DateTime('midnight', new DateTimeZone('America/New_York'))) + ->setDate(3398, 10, 31) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + } + + public function testFastForwardToEndUntilMonthly31thDay() + { + $startDate = new \DateTime('1970-01-31 00:00:00', new \DateTimeZone('America/New_York')); + $rrule = new RRuleIterator('FREQ=MONTHLY;UNTIL=33980909T092500', $startDate); + + $this->fastForwardToEnd($rrule); + + $expected = (new DateTime('midnight', new DateTimeZone('America/New_York'))) + ->setDate(3398, 8, 31) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + } + + public function testFastForwardToEndCountMonthlyAdvanced() + { + $startDate = new \DateTime('1970-01-31 00:00:00', new DateTimeZone('America/New_York')); + // every 2 months on the 1st Monday, 2nd Tuesday, 3rd Wednesday and 4th Thursday + $rrule = new RRuleIterator('FREQ=MONTHLY;INTERVAL=2;BYDAY=1MO,2TU,3WE,4TH;COUNT=10000', $startDate); + + $this->fastForwardToEnd($rrule); + + $expected = (new DateTime('midnight', new DateTimeZone('America/New_York'))) + ->setDate(2386, 9, 17) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + } + + public function testFastForwardToEndUntilMonthlyAdvanced() + { + $startDate = new \DateTime('1970-01-31 00:00:00', new DateTimeZone('America/New_York')); + // every 2 months on the 1st Monday, 2nd Tuesday, 3rd Wednesday and 4th Thursday + $rrule = new RRuleIterator('FREQ=MONTHLY;INTERVAL=2;BYDAY=1MO,2TU,3WE,4TH;UNTIL=23860914T092500', $startDate); + + $this->fastForwardToEnd($rrule); + + $expected = (new DateTime('midnight', new DateTimeZone('America/New_York'))) + ->setDate(2386, 9, 9) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + } + + public function testFastForwardToEndCountDailyBasic() + { + $timezone = 'America/New_York'; + $startDate = new \DateTime('1970-10-23 00:00:00', new \DateTimeZone($timezone)); + $rrule = new RRuleIterator('FREQ=DAILY;COUNT=100000', $startDate); + + $this->fastForwardToEnd($rrule); + + $expected = (new DateTime('midnight', new DateTimeZone($timezone))) + ->setDate(2244, 8, 6) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + } + + public function testFastForwardToEndUntilDailyBasic() + { + $timezone = 'America/New_York'; + $startDate = new \DateTime('1970-10-23 00:00:00', new \DateTimeZone($timezone)); + $rrule = new RRuleIterator('FREQ=DAILY;UNTIL=22440806T092500', $startDate); + + $this->fastForwardToEnd($rrule); + + $expected = (new DateTime('midnight', new DateTimeZone($timezone))) + ->setDate(2244, 8, 6) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + } + + public function testFastForwardToEndCountDailyAdvanced() + { + $timezone = 'America/New_York'; + $startDate = new \DateTime('1970-10-23 00:00:00', new \DateTimeZone($timezone)); + // every 10 days at 16, 17 and 18 + $rrule = new RRuleIterator('FREQ=DAILY;BYHOUR=16,17,18;INTERVAL=10;COUNT=10000', $startDate); + + $this->fastForwardToEnd($rrule); + + $expected = (new DateTime('midnight', new DateTimeZone($timezone))) + ->setDate(2062, 1, 13) + ->setTime(18, 0, 0) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + } + + public function testFastForwardToEndUntilDailyAdvanced() + { + $timezone = 'America/New_York'; + $startDate = new \DateTime('1970-10-23 00:00:00', new \DateTimeZone($timezone)); + // every 10 days at 16, 17 and 18 + $rrule = new RRuleIterator('FREQ=DAILY;BYHOUR=16,17,18;INTERVAL=10;UNTIL=20620113T183456', $startDate); + + $this->fastForwardToEnd($rrule); + + $expected = (new DateTime('midnight', new DateTimeZone($timezone))) + ->setDate(2062, 1, 13) + ->setTime(18, 0, 0) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + } + + public function testFastForwardToEndCountHourlyBasic() + { + $timezone = 'America/New_York'; + $startDate = new \DateTime('1970-10-23 00:12:34', new \DateTimeZone($timezone)); + $rrule = new RRuleIterator('FREQ=HOURLY;COUNT=100000', $startDate); + + $this->fastForwardToEnd($rrule); + + $expected = (new DateTime('midnight', new DateTimeZone($timezone))) + ->setDate(1982, 3, 21) + ->setTime(2, 12, 34) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + } + + public function testFastForwardToEndUntilHourlyBasic() + { + $timezone = 'America/New_York'; + $startDate = new \DateTime('1970-10-23 00:12:34', new \DateTimeZone($timezone)); + $rrule = new RRuleIterator('FREQ=HOURLY;UNTIL=19820321T024032', $startDate); + + $this->fastForwardToEnd($rrule); + + $expected = (new DateTime('midnight', new DateTimeZone($timezone))) + ->setDate(1982, 3, 21) + ->setTime(2, 12, 34) + ->getTimestamp(); + $this->assertEquals($expected, $rrule->current()->getTimestamp()); + } +} From 1889acda2b43cfa661a2d6e9b03d1a24963917df Mon Sep 17 00:00:00 2001 From: Valentin BONNEAUD Date: Mon, 27 Jan 2020 12:21:54 +0100 Subject: [PATCH 08/92] Remove timing test in case of count (#8) --- tests/VObject/Recur/FastForwardToEndTest.php | 37 ++++++++++++-------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/tests/VObject/Recur/FastForwardToEndTest.php b/tests/VObject/Recur/FastForwardToEndTest.php index a02930bb7..0fc839406 100644 --- a/tests/VObject/Recur/FastForwardToEndTest.php +++ b/tests/VObject/Recur/FastForwardToEndTest.php @@ -10,16 +10,14 @@ class FastForwardToEndTest extends TestCase { const FF_TIMEOUT = 1000000; // in usec - private function fastForwardToEnd(RRuleIterator $ruleIterator) + private function fastForwardToEnd(RRuleIterator $ruleIterator, $enfoceTiming = true) { $ru = getrusage(); $startTime = $ru['ru_utime.tv_sec'] * 1000000 + $ru['ru_utime.tv_usec']; $ruleIterator->fastForwardToEnd(); $ru = getrusage(); $endTime = $ru['ru_utime.tv_sec'] * 1000000 + $ru['ru_utime.tv_usec']; - $this->assertLessThan(self::FF_TIMEOUT, $endTime - $startTime); - - echo "taken " . ($endTime - $startTime) / 1000 . "ms\n"; + $enfoceTiming && $this->assertLessThan(self::FF_TIMEOUT, $endTime - $startTime); $this->assertTrue($ruleIterator->valid()); $this->assertNotNull($ruleIterator->current()); } @@ -38,7 +36,8 @@ public function testFastForwardToEndCountYearlyBasic() $startDate = new DateTime('1970-10-23 00:00:00', new DateTimeZone('zulu')); $rrule = new RRuleIterator('FREQ=YEARLY;COUNT=7777', $startDate); - $this->fastForwardToEnd($rrule); + // We do not enforce the timing in case of a count rule as we cannot optimize it + $this->fastForwardToEnd($rrule, false); $expected = (new DateTime()) ->setTimezone(new DateTimeZone('zulu')) @@ -68,7 +67,8 @@ public function testFastForwardToEndCountYearlyByYearDay() $startDate = new \DateTime('1970-10-23 00:00:00', new \DateTimeZone('zulu')); $rrule = new RRuleIterator('FREQ=YEARLY;BYYEARDAY=1,20,300;COUNT=10000', $startDate); - $this->fastForwardToEnd($rrule); + // We do not enforce the timing in case of a count rule as we cannot optimize it + $this->fastForwardToEnd($rrule, false); $expected = (new DateTime()) ->setTimezone(new DateTimeZone('zulu')) @@ -100,7 +100,8 @@ public function testFastForwardToEndCountYearlyByWeekNo() $startDate = new \DateTime('1970-10-23 00:00:00', new \DateTimeZone('zulu')); $rrule = new RRuleIterator('FREQ=YEARLY;BYWEEKNO=1,20;COUNT=100', $startDate); - $this->fastForwardToEnd($rrule); + // We do not enforce the timing in case of a count rule as we cannot optimize it + $this->fastForwardToEnd($rrule, false); $expected = (new DateTime()) ->setTimezone(new DateTimeZone('zulu')) @@ -131,7 +132,8 @@ public function testFastForwardToEndCountYearlyAdvanced() $startDate = new \DateTime('1970-10-23 12:34:56', new \DateTimeZone('zulu')); $rrule = new RRuleIterator('FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=SU;BYHOUR=8,9;BYMINUTE=30;COUNT=10000', $startDate); - $this->fastForwardToEnd($rrule); + // We do not enforce the timing in case of a count rule as we cannot optimize it + $this->fastForwardToEnd($rrule, false); $expected = (new DateTime('midnight', new DateTimeZone('zulu'))) ->setDate(4226, 1, 1) @@ -159,7 +161,8 @@ public function testFastForwardToEndCountMonthlyBasic() $startDate = new \DateTime('1970-10-23 22:42:31', new \DateTimeZone('zulu')); $rrule = new RRuleIterator('FREQ=MONTHLY;COUNT=10000', $startDate); - $this->fastForwardToEnd($rrule); + // We do not enforce the timing in case of a count rule as we cannot optimize it + $this->fastForwardToEnd($rrule, false); $expected = (new DateTime('midnight', new DateTimeZone('zulu'))) ->setDate(2804, 1, 23) @@ -182,12 +185,14 @@ public function testFastForwardToEndUntilMonthlyBasic() $this->assertEquals($expected, $rrule->current()->getTimestamp()); } + // FIXME fails in <=PHP 7.1 public function testFastForwardToEndCountMonthly31thDay() { $startDate = new \DateTime('1970-01-31 00:00:00', new \DateTimeZone('America/New_York')); $rrule = new RRuleIterator('FREQ=MONTHLY;COUNT=10000', $startDate); - $this->fastForwardToEnd($rrule); + // We do not enforce the timing in case of a count rule as we cannot optimize it + $this->fastForwardToEnd($rrule, false); $expected = (new DateTime('midnight', new DateTimeZone('America/New_York'))) ->setDate(3398, 10, 31) @@ -214,7 +219,8 @@ public function testFastForwardToEndCountMonthlyAdvanced() // every 2 months on the 1st Monday, 2nd Tuesday, 3rd Wednesday and 4th Thursday $rrule = new RRuleIterator('FREQ=MONTHLY;INTERVAL=2;BYDAY=1MO,2TU,3WE,4TH;COUNT=10000', $startDate); - $this->fastForwardToEnd($rrule); + // We do not enforce the timing in case of a count rule as we cannot optimize it + $this->fastForwardToEnd($rrule, false); $expected = (new DateTime('midnight', new DateTimeZone('America/New_York'))) ->setDate(2386, 9, 17) @@ -242,7 +248,8 @@ public function testFastForwardToEndCountDailyBasic() $startDate = new \DateTime('1970-10-23 00:00:00', new \DateTimeZone($timezone)); $rrule = new RRuleIterator('FREQ=DAILY;COUNT=100000', $startDate); - $this->fastForwardToEnd($rrule); + // We do not enforce the timing in case of a count rule as we cannot optimize it + $this->fastForwardToEnd($rrule, false); $expected = (new DateTime('midnight', new DateTimeZone($timezone))) ->setDate(2244, 8, 6) @@ -271,7 +278,8 @@ public function testFastForwardToEndCountDailyAdvanced() // every 10 days at 16, 17 and 18 $rrule = new RRuleIterator('FREQ=DAILY;BYHOUR=16,17,18;INTERVAL=10;COUNT=10000', $startDate); - $this->fastForwardToEnd($rrule); + // We do not enforce the timing in case of a count rule as we cannot optimize it + $this->fastForwardToEnd($rrule, false); $expected = (new DateTime('midnight', new DateTimeZone($timezone))) ->setDate(2062, 1, 13) @@ -302,7 +310,8 @@ public function testFastForwardToEndCountHourlyBasic() $startDate = new \DateTime('1970-10-23 00:12:34', new \DateTimeZone($timezone)); $rrule = new RRuleIterator('FREQ=HOURLY;COUNT=100000', $startDate); - $this->fastForwardToEnd($rrule); + // We do not enforce the timing in case of a count rule as we cannot optimize it + $this->fastForwardToEnd($rrule, false); $expected = (new DateTime('midnight', new DateTimeZone($timezone))) ->setDate(1982, 3, 21) From d0d44983060ec582fe6b601492d70a67961efa12 Mon Sep 17 00:00:00 2001 From: Valentin BONNEAUD Date: Mon, 27 Jan 2020 12:27:54 +0100 Subject: [PATCH 09/92] Update changelog (#9) --- CHANGELOG.md | 13 +++++++++++++ lib/Version.php | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a2c935fb..4a89215a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,19 @@ ChangeLog ========= +4.2.2 (2020-01-27) +------------------ + +* #6 Add FastForward Before +* #7 Add FastForward to end + +4.2.1 (2019-09-10) +------------------ + +* #2 Fix bug in by year day +* #3 Add daily occurrences to nextMonth and NextYear +* #4 Enhance fast forward speed if no count value has been given + 4.2.0 (2019-02-19) ------------------ diff --git a/lib/Version.php b/lib/Version.php index 257e66a79..e040dd4d9 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -14,5 +14,5 @@ class Version /** * Full version number. */ - const VERSION = '4.2.0'; + const VERSION = '4.2.2'; } From 7933fc20d5bbb8c195878ddf651dafe0eab7851f Mon Sep 17 00:00:00 2001 From: Thomas Hareau Date: Wed, 29 Jan 2020 12:19:00 +0100 Subject: [PATCH 10/92] Fix phpstan (#10) * Fix phpstan * Fixing tests <= PHP7.1 * Adding badges --- README.md | 18 +++--------------- phpstan.neon | 3 +-- tests/VObject/Recur/FastForwardToEndTest.php | 6 ++++-- 3 files changed, 8 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 97a5e6dc3..53fa93ebc 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ sabre/vobject ============= +[![Build Status](https://img.shields.io/travis/ProtonMail/vobject?style=flat-square)](https://travis-ci.org/ProtonMail/vobject) +[![Coverage](https://img.shields.io/codecov/c/github/ProtonMail/vobject?style=flat-square)](https://codecov.io/gh/ProtonMail/vobject) + The VObject library allows you to easily parse and manipulate [iCalendar](https://tools.ietf.org/html/rfc5545) and [vCard](https://tools.ietf.org/html/rfc6350) objects using PHP. @@ -27,21 +30,6 @@ Usage * [Working with iCalendar](http://sabre.io/vobject/icalendar/) - -Build status ------------- - -| branch | status | -| ------ | ------ | -| master | [![Build Status](https://travis-ci.org/sabre-io/vobject.svg?branch=master)](https://travis-ci.org/sabre-io/vobject) | -| 3.5 | [![Build Status](https://travis-ci.org/sabre-io/vobject.svg?branch=3.5)](https://travis-ci.org/sabre-io/vobject) | -| 3.4 | [![Build Status](https://travis-ci.org/sabre-io/vobject.svg?branch=3.4)](https://travis-ci.org/sabre-io/vobject) | -| 3.1 | [![Build Status](https://travis-ci.org/sabre-io/vobject.svg?branch=3.1)](https://travis-ci.org/sabre-io/vobject) | -| 2.1 | [![Build Status](https://travis-ci.org/sabre-io/vobject.svg?branch=2.1)](https://travis-ci.org/sabre-io/vobject) | -| 2.0 | [![Build Status](https://travis-ci.org/sabre-io/vobject.svg?branch=2.0)](https://travis-ci.org/sabre-io/vobject) | - - - Support ------- diff --git a/phpstan.neon b/phpstan.neon index 241663a72..5335bc651 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,3 +1,2 @@ parameters: - level: 1 - bootstrap: %currentWorkingDirectory%/vendor/autoload.php + level: 0 diff --git a/tests/VObject/Recur/FastForwardToEndTest.php b/tests/VObject/Recur/FastForwardToEndTest.php index 0fc839406..c4de5c0ff 100644 --- a/tests/VObject/Recur/FastForwardToEndTest.php +++ b/tests/VObject/Recur/FastForwardToEndTest.php @@ -185,8 +185,10 @@ public function testFastForwardToEndUntilMonthlyBasic() $this->assertEquals($expected, $rrule->current()->getTimestamp()); } - // FIXME fails in <=PHP 7.1 - public function testFastForwardToEndCountMonthly31thDay() + /** + * FIXME fails in <=PHP 7.1 + * @requires PHP 7.2 + */ public function testFastForwardToEndCountMonthly31thDay() { $startDate = new \DateTime('1970-01-31 00:00:00', new \DateTimeZone('America/New_York')); $rrule = new RRuleIterator('FREQ=MONTHLY;COUNT=10000', $startDate); From 647533a5338063347fb67111eb3582e66f9823b6 Mon Sep 17 00:00:00 2001 From: Valentin BONNEAUD Date: Thu, 30 Jan 2020 12:18:14 +0100 Subject: [PATCH 11/92] FREQ is mandatory in the RRule (#11) * FREQ is mandatory in the RRule * Review --- lib/Recur/RRuleIterator.php | 5 +++++ tests/VObject/Recur/RRuleIteratorTest.php | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/lib/Recur/RRuleIterator.php b/lib/Recur/RRuleIterator.php index da9448d4b..1513cf125 100644 --- a/lib/Recur/RRuleIterator.php +++ b/lib/Recur/RRuleIterator.php @@ -962,6 +962,11 @@ protected function parseRRule($rrule) throw new InvalidDataException('Not supported: '.strtoupper($key)); } } + + // FREQ is mandatory + if (!isset($this->frequency)) { + throw new InvalidDataException('Unknown value for FREQ'); + } } /** diff --git a/tests/VObject/Recur/RRuleIteratorTest.php b/tests/VObject/Recur/RRuleIteratorTest.php index 63f6aae5b..92cab901b 100644 --- a/tests/VObject/Recur/RRuleIteratorTest.php +++ b/tests/VObject/Recur/RRuleIteratorTest.php @@ -6,9 +6,20 @@ use DateTimeImmutable; use DateTimeZone; use PHPUnit\Framework\TestCase; +use Sabre\VObject\InvalidDataException; class RRuleIteratorTest extends TestCase { + public function testInvalidMissingFreq() + { + $this->expectException(InvalidDataException::class); + $this->parse( + 'COUNT=6;BYMONTHDAY=24;BYMONTH=1', + '2011-04-07 00:00:00', + [] + ); + } + public function testHourly() { $this->parse( From 2fe3a9a01574981a3e21da8e031a8fdac40f13fb Mon Sep 17 00:00:00 2001 From: Valentin BONNEAUD Date: Thu, 30 Jan 2020 15:47:19 +0100 Subject: [PATCH 12/92] Add test on BYMONTHDAY (#12) --- lib/Recur/RRuleIterator.php | 9 +++++++ tests/VObject/Recur/RRuleIteratorTest.php | 30 +++++++++++++++-------- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/lib/Recur/RRuleIterator.php b/lib/Recur/RRuleIterator.php index 1513cf125..ea804dd63 100644 --- a/lib/Recur/RRuleIterator.php +++ b/lib/Recur/RRuleIterator.php @@ -921,6 +921,15 @@ protected function parseRRule($rrule) case 'BYMONTHDAY': $this->byMonthDay = (array) $value; + foreach ($this->byMonthDay as $byMonthDay) { + if (!is_numeric($byMonthDay)) { + throw new InvalidDataException('BYMONTHDAY in RRULE has a not numeric value(s)!'); + } + $byMonthDay = (int) $byMonthDay; + if ($byMonthDay < -31 || 0 === $byMonthDay || $byMonthDay > 31) { + throw new InvalidDataException('BYMONTHDAY in RRULE must have value(s) from 1 to 31, or -31 to -1!'); + } + } break; case 'BYYEARDAY': diff --git a/tests/VObject/Recur/RRuleIteratorTest.php b/tests/VObject/Recur/RRuleIteratorTest.php index 92cab901b..e5d37d05a 100644 --- a/tests/VObject/Recur/RRuleIteratorTest.php +++ b/tests/VObject/Recur/RRuleIteratorTest.php @@ -10,16 +10,6 @@ class RRuleIteratorTest extends TestCase { - public function testInvalidMissingFreq() - { - $this->expectException(InvalidDataException::class); - $this->parse( - 'COUNT=6;BYMONTHDAY=24;BYMONTH=1', - '2011-04-07 00:00:00', - [] - ); - } - public function testHourly() { $this->parse( @@ -316,6 +306,16 @@ public function testMonthlyByMonthDay() ); } + public function testInvalidByMonthDay() + { + $this->expectException(InvalidDataException::class); + $this->parse( + 'FREQ=MONTHLY;COUNT=6;BYMONTHDAY=1,5,10,42', + '2011-04-07 00:00:00', + [] + ); + } + public function testMonthlyByDay() { $this->parse( @@ -895,6 +895,16 @@ public function testInvalidFreq() ); } + public function testInvalidMissingFreq() + { + $this->expectException(InvalidDataException::class); + $this->parse( + 'COUNT=6;BYMONTHDAY=24;BYMONTH=1', + '2011-04-07 00:00:00', + [] + ); + } + /** * @expectedException \Sabre\VObject\InvalidDataException */ From 0a003c739bbf8cd0aab0d6aa9cd00740c3b32157 Mon Sep 17 00:00:00 2001 From: Valentin BONNEAUD Date: Tue, 11 Feb 2020 20:09:12 +0100 Subject: [PATCH 13/92] Fix counter when fast forward before (#13) --- lib/Recur/RRuleIterator.php | 9 +++++++-- tests/VObject/Recur/FastForwardBeforeTest.php | 17 +++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/lib/Recur/RRuleIterator.php b/lib/Recur/RRuleIterator.php index ea804dd63..6079efb4a 100644 --- a/lib/Recur/RRuleIterator.php +++ b/lib/Recur/RRuleIterator.php @@ -150,8 +150,10 @@ public function fastForward(DateTimeInterface $dt) */ public function fastForwardBefore(DateTimeInterface $dt) { + $hasCount = isset($this->count); + // We don't do any jumps if we have a count limit as we have to keep track of the number of occurrences - if (!isset($this->count)) { + if (!$hasCount) { $this->jumpForward($dt); } @@ -161,7 +163,10 @@ public function fastForwardBefore(DateTimeInterface $dt) $this->next(); } - isset($previousDate) && $this->currentDate = $previousDate; + if (isset($previousDate)) { + $this->currentDate = $previousDate; + $hasCount && $this->counter--; + } } /** diff --git a/tests/VObject/Recur/FastForwardBeforeTest.php b/tests/VObject/Recur/FastForwardBeforeTest.php index 8b7755b1c..ba71d23dd 100644 --- a/tests/VObject/Recur/FastForwardBeforeTest.php +++ b/tests/VObject/Recur/FastForwardBeforeTest.php @@ -525,4 +525,21 @@ public function testFastForwardBeforeNotInFrequency() $rrule->next(); $this->assertEquals($expected, $rrule->current()->getTimestamp()); } + + public function testFastForwardBeforeMultipleTimesBasic() + { + $startDate = new DateTime('2020-01-02 00:00:00', new DateTimeZone('zulu')); + $ffDate = new DateTime('2020-01-18 00:00:00', new DateTimeZone('zulu')); + $rrule = new RRuleIterator('FREQ=WEEKLY', $startDate); + $expected = new DateTime('2020-01-16 00:00:00', new DateTimeZone('zulu')); + + $this->fastForward($rrule, $ffDate); + $this->assertEquals($expected->getTimestamp(), $rrule->current()->getTimestamp()); + + $this->fastForward($rrule, $ffDate); + $this->assertEquals($expected->getTimestamp(), $rrule->current()->getTimestamp()); + + $this->fastForward($rrule, $ffDate); + $this->assertEquals($expected->getTimestamp(), $rrule->current()->getTimestamp()); + } } From 1c89b54849f9092b50327635673dd359b506aa21 Mon Sep 17 00:00:00 2001 From: Valentin BONNEAUD Date: Wed, 20 May 2020 13:34:05 +0200 Subject: [PATCH 14/92] Add extra timezones support (#14) --- CHANGELOG.md | 5 + lib/TimeZoneUtil.php | 7 +- lib/timezonedata/extrazones.php | 196 ++++++++++++++++++++++++++++++++ 3 files changed, 205 insertions(+), 3 deletions(-) create mode 100644 lib/timezonedata/extrazones.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a89215a8..1444e4175 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ ChangeLog ========= +4.2.3 (2020-05-20) +------------------ + +* Add timezones data mapping. + 4.2.2 (2020-01-27) ------------------ diff --git a/lib/TimeZoneUtil.php b/lib/TimeZoneUtil.php index 5b1a775c2..8d8f163b0 100644 --- a/lib/TimeZoneUtil.php +++ b/lib/TimeZoneUtil.php @@ -50,12 +50,12 @@ class TimeZoneUtil 48 => 'Asia/Kabul', 58 => 'Asia/Yekaterinburg', 47 => 'Asia/Karachi', - 23 => 'Asia/Calcutta', + 23 => 'Asia/Kolkata', 62 => 'Asia/Kathmandu', 46 => 'Asia/Almaty', 71 => 'Asia/Dhaka', 66 => 'Asia/Colombo', - 61 => 'Asia/Rangoon', + 61 => 'Asia/Yangon', 22 => 'Asia/Bangkok', 64 => 'Asia/Krasnoyarsk', 45 => 'Asia/Shanghai', @@ -243,7 +243,8 @@ public static function loadTzMaps() include __DIR__.'/timezonedata/windowszones.php', include __DIR__.'/timezonedata/lotuszones.php', include __DIR__.'/timezonedata/exchangezones.php', - include __DIR__.'/timezonedata/php-workaround.php' + include __DIR__.'/timezonedata/php-workaround.php', + include __DIR__.'/timezonedata/extrazones.php' ); } diff --git a/lib/timezonedata/extrazones.php b/lib/timezonedata/extrazones.php new file mode 100644 index 000000000..d855ff831 --- /dev/null +++ b/lib/timezonedata/extrazones.php @@ -0,0 +1,196 @@ + 'America/Rio_Branco', + 'Africa Central' => 'Africa/Maputo', + 'Africa Eastern' => 'Africa/Nairobi', + 'Africa FarWestern' => 'Africa/El_Aaiun', + 'Africa Southern' => 'Africa/Johannesburg', + 'Africa Western' => 'Africa/Lagos', + 'Aktyubinsk' => 'Asia/Aqtobe', + 'Alaska Hawaii' => 'America/Anchorage', + 'Almaty' => 'Asia/Almaty', + 'Amazon' => 'America/Manaus', + 'America Central' => 'America/Chicago', + 'America Eastern' => 'America/New_York', + 'America Mountain' => 'America/Denver', + 'America Pacific' => 'America/Los_Angeles', + 'Anadyr' => 'Asia/Anadyr', + 'Apia' => 'Pacific/Apia', + 'Aqtau' => 'Asia/Aqtau', + 'Aqtobe' => 'Asia/Aqtobe', + 'Argentina Western' => 'America/Argentina/San_Luis', + 'Armenia' => 'Asia/Yerevan', + 'Armenian Standard Time' => 'Asia/Yerevan', + 'Ashkhabad' => 'Asia/Ashgabat', + 'Australia Central' => 'Australia/Adelaide', + 'Australia CentralWestern' => 'Australia/Eucla', + 'Australia Eastern' => 'Australia/Sydney', + 'Australia Western' => 'Australia/Perth', + 'Azerbaijan' => 'Asia/Baku', + 'Baku' => 'Asia/Baku', + 'Bangladesh' => 'Asia/Dhaka', + 'Bering' => 'America/Adak', + 'Bhutan' => 'Asia/Thimphu', + 'Bolivia' => 'America/La_Paz', + 'Borneo' => 'Asia/Kuching', + 'British' => 'Europe/London', + 'Brunei' => 'Asia/Brunei', + 'Casey' => 'Antarctica/Casey', + 'Chamorro' => 'Pacific/Saipan', + 'Chatham' => 'Pacific/Chatham', + 'Chile' => 'America/Santiago', + 'Choibalsan' => 'Asia/Choibalsan', + 'Christmas' => 'Indian/Christmas', + 'Cocos' => 'Indian/Cocos', + 'Colombia' => 'America/Bogota', + 'Cook' => 'Pacific/Rarotonga', + 'Dacca' => 'Asia/Dhaka', + 'Davis' => 'Antarctica/Davis', + 'Dominican' => 'America/Santo_Domingo', + 'DumontDUrville' => 'Antarctica/DumontDUrville', + 'Dushanbe' => 'Asia/Dushanbe', + 'Dutch Guiana' => 'America/Paramaribo', + 'East Timor' => 'Asia/Dili', + 'Easter' => 'Pacific/Easter', + 'Ecuador' => 'America/Guayaquil', + 'Europe Central' => 'Europe/Paris', + 'Europe Eastern' => 'Europe/Bucharest', + 'Europe Further Eastern' => 'Europe/Minsk', + 'Europe Western' => 'Atlantic/Canary', + 'Falkland' => 'Atlantic/Stanley', + 'Fiji Islands Standard Time' => 'Pacific/Fiji', + 'French Guiana' => 'America/Cayenne', + 'French Southern' => 'Indian/Kerguelen', + 'Frunze' => 'Asia/Bishkek', + 'Galapagos' => 'Pacific/Galapagos', + 'Gambier' => 'Pacific/Gambier', + 'Georgia' => 'Asia/Tbilisi', + 'Gilbert Islands' => 'Pacific/Tarawa', + 'GMT' => 'Europe/London', + 'Goose Bay' => 'America/Goose_Bay', + 'Greenland Central' => 'America/Scoresbysund', + 'Greenland Eastern' => 'America/Scoresbysund', + 'Greenland Western' => 'America/Godthab', + 'Guam' => 'Pacific/Guam', + 'Gulf' => 'Asia/Dubai', + 'Guyana' => 'America/Guyana', + 'Hawaii Aleutian' => 'Pacific/Honolulu', + 'Hong Kong' => 'Asia/Hong_Kong', + 'Hovd' => 'Asia/Hovd', + 'Indian Ocean' => 'Indian/Chagos', + 'Indochina' => 'Asia/Bangkok', + 'Indonesia Central' => 'Asia/Makassar', + 'Indonesia Eastern' => 'Asia/Jayapura', + 'Indonesia Western' => 'Asia/Jakarta', + 'Irish' => 'Europe/Dublin', + 'Irkutsk' => 'Asia/Irkutsk', + 'Kamchatka' => 'Asia/Kamchatka', + 'Kamchatka Standard Time' => 'Asia/Kamchatka', + 'Karachi' => 'Asia/Karachi', + 'Kazakhstan Eastern' => 'Asia/Almaty', + 'Kazakhstan Western' => 'Asia/Aqtobe', + 'Kizilorda' => 'Asia/Qyzylorda', + 'Kosrae' => 'Pacific/Kosrae', + 'Kuybyshev' => 'Europe/Samara', + 'Kyrgystan' => 'Asia/Bishkek', + 'Lanka' => 'Asia/Colombo', + 'Liberia' => 'Africa/Monrovia', + 'Line Islands' => 'Pacific/Kiritimati', + 'Lord Howe' => 'Australia/Lord_Howe', + 'Macau' => 'Asia/Macau', + 'Macquarie' => 'Antarctica/Macquarie', + 'Magadan' => 'Asia/Magadan', + 'Magallanes Standard Time' => 'America/Punta_Arenas', + 'Malaya' => 'Asia/Kuala_Lumpur', + 'Malaysia' => 'Asia/Kuching', + 'Maldives' => 'Indian/Maldives', + 'Marquesas' => 'Pacific/Marquesas', + 'Marshall Islands' => 'Pacific/Majuro', + 'Mawson' => 'Antarctica/Mawson', + 'Mexico Pacific' => 'America/Mazatlan', + 'Mexico Standard Time' => 'America/Mexico_City', + 'Mid-Atlantic Standard Time' => 'Atlantic/Cape_Verde', + 'Mongolia' => 'Asia/Ulaanbaatar', + 'Moscow' => 'Europe/Moscow', + 'Nauru' => 'Pacific/Nauru', + 'New Caledonia' => 'Pacific/Noumea', + 'Newfoundland And Labrador Standard Time' => 'America/St_Johns', + 'Niue' => 'Pacific/Niue', + 'Norfolk' => 'Pacific/Norfolk', + 'Noronha' => 'America/Noronha', + 'North Mariana' => 'Pacific/Saipan', + 'Novosibirsk' => 'Asia/Novosibirsk', + 'Omsk' => 'Asia/Omsk', + 'Omsk Standard Time' => 'Asia/Omsk', + 'Oral' => 'Asia/Oral', + 'Palau' => 'Pacific/Palau', + 'Papua New Guinea' => 'Pacific/Port_Moresby', + 'Paraguay' => 'America/Asuncion', + 'Peru' => 'America/Lima', + 'Philippines' => 'Asia/Manila', + 'Phoenix Islands' => 'Pacific/Enderbury', + 'Pierre Miquelon' => 'America/Miquelon', + 'Pitcairn' => 'Pacific/Pitcairn', + 'Pyongyang' => 'Asia/Pyongyang', + 'Qyzylorda' => 'Asia/Qyzylorda', + 'Qyzylorda Standard Time' => 'Asia/Qyzylorda', + 'Reunion' => 'Indian/Reunion', + 'Rothera' => 'Antarctica/Rothera', + 'Sakhalin' => 'Asia/Sakhalin', + 'Samara' => 'Europe/Samara', + 'Samarkand' => 'Asia/Samarkand', + 'Sao Tome Standard Time' => 'Africa/Sao_Tome', + 'Saratov Standard Time' => 'Europe/Saratov', + 'Seychelles' => 'Indian/Mahe', + 'Shevchenko' => 'Asia/Aqtau', + 'Solomon' => 'Pacific/Guadalcanal', + 'South Georgia' => 'Atlantic/South_Georgia', + 'Sudan Standard Time' => 'Africa/Khartoum', + 'Suriname' => 'America/Paramaribo', + 'Sverdlovsk' => 'Asia/Yekaterinburg', + 'Syowa' => 'Antarctica/Syowa', + 'Tahiti' => 'Pacific/Tahiti', + 'Tajikistan' => 'Asia/Dushanbe', + 'Tashkent' => 'Asia/Tashkent', + 'Tbilisi' => 'Asia/Tbilisi', + 'Tokelau' => 'Pacific/Fakaofo', + 'Transitional Islamic State Of Afghanistan Standard Time' => 'Asia/Kabul', + 'Turkmenistan' => 'Asia/Ashgabat', + 'Tuvalu' => 'Pacific/Funafuti', + 'Uralsk' => 'Asia/Oral', + 'Uruguay' => 'America/Montevideo', + 'Urumqi' => 'Asia/Urumqi', + 'Uzbekistan' => 'Asia/Tashkent', + 'Vanuatu' => 'Pacific/Efate', + 'Volgograd' => 'Europe/Volgograd', + 'Volgograd Standard Time' => 'Europe/Volgograd', + 'Vostok' => 'Antarctica/Vostok', + 'Wake' => 'Pacific/Wake', + 'Wallis' => 'Pacific/Wallis', + 'Yekaterinburg' => 'Asia/Yekaterinburg', + 'Yerevan' => 'Asia/Yerevan', + 'Yukon' => 'America/Yakutat', + // Overwrite + 'Argentina Standard Time' => 'America/Argentina/Buenos_Aires', + 'Dateline' => 'Pacific/Auckland', + 'Dateline Standard Time' => 'Pacific/Niue', + 'India' => 'Asia/Kolkata', + 'India Standard Time' => 'Asia/Kolkata', + 'Kolkata, Chennai, Mumbai, New Delhi, India Standard Time' => 'Asia/Kolkata', + 'Myanmar' => 'Asia/Yangon', + 'Myanmar Standard Time' => 'Asia/Yangon', + 'Nepal Standard Time' => 'Asia/Kathmandu', + 'Rangoon' => 'Asia/Yangon', + 'Greenwich' => 'Atlantic/Reykjavik', + 'UTC-02' => 'America/Noronha', + 'UTC-08' => 'Pacific/Pitcairn', + 'UTC-09' => 'Pacific/Gambier', + 'UTC-11' => 'Pacific/Niue', + 'UTC+12' => 'Pacific/Auckland', + 'US Eastern Standard Time' => 'America/New_York', +]; From ca647a216e5568342016047e7eff06229b2bf851 Mon Sep 17 00:00:00 2001 From: Valentin BONNEAUD Date: Wed, 20 May 2020 13:38:17 +0200 Subject: [PATCH 15/92] Correct typo in changelog --- CHANGELOG.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1444e4175..e94063471 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,18 @@ ChangeLog ========= -4.2.3 (2020-05-20) +4.3.2 (2020-05-20) ------------------ -* Add timezones data mapping. +* #14 Add timezones data mapping. -4.2.2 (2020-01-27) +4.3.1 (2020-01-27) ------------------ * #6 Add FastForward Before * #7 Add FastForward to end +* #11 FREQ is mandatory in the RRule +* #12 Validate BYMONTHDAY 4.2.1 (2019-09-10) ------------------ From b16c3e7799c60cc1bb1a99a8e706c7f12a0653f0 Mon Sep 17 00:00:00 2001 From: bcaller Date: Wed, 22 Jul 2020 11:14:53 +0100 Subject: [PATCH 16/92] Expose RRULE properties from RRuleIterator (#15) * Expose RRULE properties from RRuleIterator COUNT FREQ INTERVAL UNTIL * Test get accessors for RRULE properties --- lib/Recur/RRuleIterator.php | 20 +++ tests/VObject/Recur/RRuleIteratorTest.php | 146 +++++++++++++++------- 2 files changed, 124 insertions(+), 42 deletions(-) diff --git a/lib/Recur/RRuleIterator.php b/lib/Recur/RRuleIterator.php index 6079efb4a..3ef8aec2f 100644 --- a/lib/Recur/RRuleIterator.php +++ b/lib/Recur/RRuleIterator.php @@ -195,6 +195,26 @@ public function fastForwardToEnd() $this->currentDate = $previous; } + public function getCount() + { + return $this->count; + } + + public function getInterval() + { + return $this->interval; + } + + public function getUntil() + { + return $this->until; + } + + public function getFrequency() + { + return $this->frequency; + } + /** * Return the frequency in number of days. * diff --git a/tests/VObject/Recur/RRuleIteratorTest.php b/tests/VObject/Recur/RRuleIteratorTest.php index e5d37d05a..a89b83552 100644 --- a/tests/VObject/Recur/RRuleIteratorTest.php +++ b/tests/VObject/Recur/RRuleIteratorTest.php @@ -28,7 +28,8 @@ public function testHourly() '2011-10-08 15:00:00', '2011-10-08 18:00:00', '2011-10-08 21:00:00', - ] + ], + 'hourly', 12, 3, null ); } @@ -45,7 +46,8 @@ public function testDaily() '2011-10-19 00:00:00', '2011-10-22 00:00:00', '2011-10-25 00:00:00', - ] + ], + 'daily', null, 3, new DateTime('2011-10-25') ); } @@ -67,7 +69,8 @@ public function testDailyByDayByHour() '2011-10-22 07:00:00', '2011-10-23 06:00:00', '2011-10-23 07:00:00', - ] + ], + 'daily', null, 1, null ); } @@ -89,7 +92,8 @@ public function testDailyByHour() '2012-10-13 15:00:00', '2012-10-15 10:00:00', '2012-10-15 11:00:00', - ] + ], + 'daily', null, 2, null ); } @@ -111,7 +115,8 @@ public function testDailyByDay() '2011-11-18 12:00:00', '2011-11-22 12:00:00', '2011-11-30 12:00:00', - ] + ], + 'daily', null, 2, null ); } @@ -126,7 +131,8 @@ public function testDailyCount() '2014-08-03 18:03:00', '2014-08-04 18:03:00', '2014-08-05 18:03:00', - ] + ], + 'daily', 5, 1, null ); } @@ -143,6 +149,7 @@ public function testDailyByMonth() '2013-10-27 16:00:00', '2014-09-07 16:00:00', ], + 'daily', null, 1, null, '2013-09-28' ); } @@ -163,7 +170,8 @@ public function testWeekly() '2012-01-13 00:00:00', '2012-01-27 00:00:00', '2012-02-10 00:00:00', - ] + ], + 'weekly', 10, 2, null ); } @@ -177,7 +185,8 @@ public function testWeeklyByDay() '2014-08-04 00:00:00', '2014-08-11 00:00:00', '2014-08-18 00:00:00', - ] + ], + 'weekly', 4, 1, null ); } @@ -199,7 +208,8 @@ public function testWeeklyByDay2() '2011-11-18 00:00:00', '2011-11-29 00:00:00', '2011-11-30 00:00:00', - ] + ], + 'weekly', null, 2, null ); } @@ -224,7 +234,8 @@ public function testWeeklyByDayByHour() '2011-11-01 08:00:00', '2011-11-01 09:00:00', '2011-11-01 10:00:00', - ] + ], + 'weekly', null, 2, null ); } @@ -246,7 +257,8 @@ public function testWeeklyByDaySpecificHour() '2011-11-18 18:00:00', '2011-11-29 18:00:00', '2011-11-30 18:00:00', - ] + ], + 'weekly', null, 2, null ); } @@ -261,7 +273,8 @@ public function testMonthly() '2012-06-05 00:00:00', '2012-09-05 00:00:00', '2012-12-05 00:00:00', - ] + ], + 'monthly', 5, 3, null ); } @@ -283,7 +296,8 @@ public function testMonlthyEndOfMonth() '2014-12-31 00:00:00', '2015-08-31 00:00:00', '2015-10-31 00:00:00', - ] + ], + 'monthly', 12, 2, null ); } @@ -302,7 +316,8 @@ public function testMonthlyByMonthDay() '2011-11-24 00:00:00', '2012-04-01 00:00:00', '2012-04-24 00:00:00', - ] + ], + 'monthly', 9, 5, null ); } @@ -338,7 +353,8 @@ public function testMonthlyByDay() '2011-03-22 00:00:00', '2011-03-28 00:00:00', '2011-05-02 00:00:00', - ] + ], + 'monthly', 16, 2, null ); } @@ -358,7 +374,8 @@ public function testMonthlyByDayByMonthDay() '2016-02-01 00:00:00', '2016-08-01 00:00:00', '2017-05-01 00:00:00', - ] + ], + 'monthly', 10, 1, null ); } @@ -378,7 +395,8 @@ public function testMonthlyByDayBySetPos() '2011-04-29 00:00:00', '2011-05-02 00:00:00', '2011-05-31 00:00:00', - ] + ], + 'monthly', 10, 1, null ); } @@ -398,7 +416,8 @@ public function testYearly() '2032-01-01 00:00:00', '2035-01-01 00:00:00', '2038-01-01 00:00:00', - ] + ], + 'yearly', 10, 3, null ); } @@ -411,7 +430,8 @@ public function testYearlyLeapYear() '2012-02-29 00:00:00', '2016-02-29 00:00:00', '2020-02-29 00:00:00', - ] + ], + 'yearly', 3, 1, null ); } @@ -429,7 +449,8 @@ public function testYearlyByMonth() '2019-10-07 00:00:00', '2023-04-07 00:00:00', '2023-10-07 00:00:00', - ] + ], + 'yearly', 8, 4, null ); } @@ -495,7 +516,8 @@ public function testYearlyByMonthByDay() '2016-04-24 00:00:00', '2016-10-03 00:00:00', '2016-10-30 00:00:00', - ] + ], + 'yearly', 8, 5, null ); } @@ -512,7 +534,8 @@ public function testYearlyNewYearsEve() '2019-01-01 03:07:00', '2021-01-01 03:07:00', '2023-01-01 03:07:00', - ] + ], + 'yearly', 7, 2, null ); } @@ -529,7 +552,8 @@ public function testYearlyByYearDay() '2019-07-09 03:07:00', '2021-07-09 03:07:00', '2023-07-09 03:07:00', - ] + ], + 'yearly', 7, 2, null ); } @@ -566,7 +590,8 @@ public function testYearlyByYearDayMultiple() '2017-10-28 14:53:11', '2020-07-08 14:53:11', '2020-10-27 14:53:11', - ] + ], + 'yearly', 8, 3, null ); } @@ -582,7 +607,8 @@ public function testYearlyByYearDayByDay() '2024-04-06 14:53:11', '2029-04-07 14:53:11', '2035-04-07 14:53:11', - ] + ], + 'yearly', 6, 1, null ); } @@ -600,7 +626,8 @@ public function testYearlyByYearDayNegative() '2003-12-27 14:53:11', '2004-09-26 14:53:11', '2004-12-27 14:53:11', - ] + ], + 'yearly', 8, 1, null ); } @@ -621,7 +648,9 @@ public function testFirstLastSundayEveryOtherYearAt1530and1730InJanuary() '2003-01-26 15:30:15', '2003-01-26 15:30:56', '2003-01-26 15:35:15', '2003-01-26 15:35:56', '2003-01-26 17:30:15', '2003-01-26 17:30:56', '2003-01-26 17:35:15', '2003-01-26 17:35:56', - ]); + ], + 'yearly', null, 2, null + ); } public function testFirstFourthSundayEveryOtherMonthAt830and930() @@ -641,7 +670,9 @@ public function testFirstFourthSundayEveryOtherMonthAt830and930() '2001-03-25 15:30:11', '2001-03-25 15:30:12', '2001-03-25 15:32:11', '2001-03-25 15:32:12', '2001-03-25 17:30:11', '2001-03-25 17:30:12', '2001-03-25 17:32:11', '2001-03-25 17:32:12', - ]); + ], + 'monthly', null, 2, null + ); } /** @@ -678,6 +709,7 @@ public function testFastForward() 'FREQ=YEARLY;COUNT=8;INTERVAL=5;BYMONTH=4,10;BYDAY=1MO,-1SU', '2011-04-04 00:00:00', [], + 'yearly', 8, 5, null, '2020-05-05 00:00:00' ); } @@ -699,7 +731,8 @@ public function testFifthTuesdayProblem() '2007-10-04 14:46:42', [ '2007-10-04 14:46:42', - ] + ], + 'monthly', null, 1, new DateTime('2007-10-30 03:59:59') ); } @@ -724,7 +757,8 @@ public function testFastFowardTooFar() '2009-06-15 18:00:00', '2009-06-22 18:00:00', '2009-06-29 18:00:00', - ] + ], + 'weekly', null, 1, new DateTime('2009-07-04 20:59:59') ); } @@ -746,7 +780,8 @@ public function testValidByWeekNo() '2019-05-14 00:00:00', '2020-05-12 00:00:00', '2021-05-18 00:00:00', - ] + ], + 'yearly', null, 1, null ); } @@ -768,7 +803,8 @@ public function testNegativeValidByWeekNo() '2016-08-09 00:00:00', '2016-08-12 00:00:00', '2017-08-08 00:00:00', - ] + ], + 'yearly', null, 1, null ); } @@ -790,7 +826,8 @@ public function testTwoValidByWeekNo() '2016-05-17 09:00:00', '2016-05-20 09:00:00', '2017-05-16 09:00:00', - ] + ], + 'yearly', null, 1, null ); } @@ -812,7 +849,8 @@ public function testValidByWeekNoByDayDefault() '2020-05-11 00:00:00', '2021-05-17 00:00:00', '2022-05-16 00:00:00', - ] + ], + 'yearly', null, 1, null ); } @@ -834,7 +872,8 @@ public function testMultipleValidByWeekNo() '2013-05-14 00:00:00', '2013-05-17 00:00:00', '2013-12-10 00:00:00', - ] + ], + 'yearly', null, 1, null ); } @@ -862,6 +901,7 @@ public function testYearlyByMonthLoop() [ '2012-02-01 15:45:00', ], + 'yearly', null, 1, new DateTime('2012-02-03 22:59:59'), '2012-01-29 23:00:00' ); } @@ -879,6 +919,7 @@ public function testZeroInterval() 'FREQ=YEARLY;INTERVAL=0', '2012-08-24 14:57:00', [], + 'yearly', null, 0, null, '2013-01-01 23:00:00' ); } @@ -933,6 +974,7 @@ public function testUntilBeginHasTimezone() '2013-11-11 18:30:00', '2013-11-18 18:30:00', ], + 'weekly', null, 1, new DateTime('2013-11-18 18:30:00-0500'), null, 'America/New_York' ); @@ -940,12 +982,14 @@ public function testUntilBeginHasTimezone() public function testUntilBeforeDtStart() { + $dtstart = '2014-08-02 00:15:00'; $this->parse( 'FREQ=DAILY;UNTIL=20140101T000000Z', - '2014-08-02 00:15:00', + $dtstart, [ - '2014-08-02 00:15:00', - ] + $dtstart, + ], + 'daily', null, 1, new DateTime($dtstart) ); } @@ -957,7 +1001,8 @@ public function testIgnoredStuff() [ '2014-08-02 00:15:00', '2014-08-03 00:15:00', - ] + ], + 'daily', 2, 1, null ); } @@ -971,7 +1016,8 @@ public function testMinusFifthThursday() '2015-01-08 00:15:00', '2015-02-05 00:15:00', '2015-03-05 00:15:00', - ] + ], + 'monthly', 4, 1, null ); } @@ -983,6 +1029,7 @@ public function testNeverEnding() [ '2015-01-01 00:15:00', ], + 'monthly', null, 1, null, null, 'UTC', true @@ -1026,11 +1073,26 @@ public function testIteratorFunctions() ); } - public function parse($rule, $start, $expected, $fastForward = null, $tz = 'UTC', $runTillTheEnd = false) - { + public function parse( + $rule, + $start, + $expected, + $expectedFreq = null, + $expectedCount = null, + $expectedInterval = null, + $expectedUntil = null, + $fastForward = null, + $tz = 'UTC', + $runTillTheEnd = false + ) { $dt = new DateTime($start, new DateTimeZone($tz)); $parser = new RRuleIterator($rule, $dt); + $this->assertEquals($expectedFreq, $parser->getFrequency()); + $this->assertEquals($expectedCount, $parser->getCount()); + $this->assertEquals($expectedInterval, $parser->getInterval()); + $this->assertEquals($expectedUntil, $parser->getUntil()); + if ($fastForward) { $parser->fastForward(new DateTime($fastForward)); } From 10a03a8d8c4195645db38de9549fcf2fce594c17 Mon Sep 17 00:00:00 2001 From: bcaller Date: Tue, 28 Jul 2020 16:40:04 +0100 Subject: [PATCH 17/92] Merge upstream (#16) * fix Bad file descriptor (7.4) (#469) * travis: allow failure for phpstan for now (#470) * travis: add php 7.4snapshot build (#471) * Fixed typo in vobject CLI help (#477) * Run phpstan on PHP >= 7.1 (#483) * Add TZ in iTip REPLY messages * Added phpstan for tests folder (#485) * Add PHONE-NUMBER value type (used for TEL in vCard 3.0) (#486) Signed-off-by: Christian Kraus * use latest phpstan 0.12.5 in CI * Add PHPstorm .idea to .gitignore * Fix various typos * Prepare next iteration * Apply php-cs-fixer code style changes * Use phpunit8 where possible * Fixed phpstan level 1 errors * Make sure there is no logic change * Prevent setting foreach key beforehand * Refactored fqcn strings to ::class to allow checking with phpstan (#495) * Added convenient development commands * Decoupled cs-fixer command from chosen tool * Use phpunit 9 where possible * Replace assertRegExp with assertMatchesRegularExpression in unit test * fix an incomplete phpdoc type annotation * php-cs-fixer fixes * Ignore phpdoc_summary check Seriously you want me to put a full stop directly after a link?? * Drop php < 7.1, pull composer / travis changes from upstream * Release 4.3.4 Co-authored-by: Remi Collet Co-authored-by: Markus Staab Co-authored-by: Dominik Co-authored-by: Jeroen van Oort Co-authored-by: Renaud BOYER Co-authored-by: Christian Kraus Co-authored-by: Phil Davis Co-authored-by: Michael Stilkerich --- .gitignore | 2 + .php_cs.dist | 5 +- .travis.yml | 29 ++-- CHANGELOG.md | 10 ++ composer.json | 30 +++- lib/Cli.php | 14 +- lib/Component.php | 10 +- lib/Component/VAvailability.php | 3 - lib/Component/VCalendar.php | 158 +++++++++--------- lib/Component/VCard.php | 135 +++++++-------- lib/Component/VEvent.php | 5 +- lib/Component/VFreeBusy.php | 3 - lib/Component/VJournal.php | 3 - lib/Component/VTodo.php | 3 - lib/Document.php | 4 +- lib/FreeBusyGenerator.php | 12 +- lib/ITip/Broker.php | 37 ++-- lib/Node.php | 2 - lib/PHPUnitAssertions.php | 5 +- lib/Parameter.php | 2 - lib/Parser/Json.php | 10 +- lib/Parser/MimeDir.php | 4 +- lib/Parser/XML.php | 22 +-- lib/Parser/XML/Element/KeyValue.php | 2 +- lib/Property.php | 6 - lib/Property/Binary.php | 2 - lib/Property/Boolean.php | 4 +- lib/Property/FloatValue.php | 2 - lib/Property/ICalendar/DateTime.php | 22 +-- lib/Property/ICalendar/Period.php | 2 - lib/Property/ICalendar/Recur.php | 2 - lib/Property/IntegerValue.php | 4 +- lib/Property/Text.php | 2 +- lib/Property/Time.php | 4 - lib/Property/Uri.php | 2 +- lib/Property/UtcOffset.php | 2 - lib/Property/VCard/Date.php | 2 - lib/Property/VCard/DateAndOrTime.php | 4 - lib/Property/VCard/PhoneNumber.php | 30 ++++ lib/Recur/EventIterator.php | 2 - lib/Recur/NoInstancesException.php | 2 +- lib/Recur/RDateIterator.php | 5 +- lib/Recur/RRuleIterator.php | 25 +-- lib/Settings.php | 2 +- lib/TimeZoneUtil.php | 2 +- lib/VCardConverter.php | 25 +-- lib/Version.php | 2 +- lib/Writer.php | 7 +- phpstan.neon | 4 +- .../VObject/BirthdayCalendarGeneratorTest.php | 12 +- tests/VObject/CliTest.php | 25 ++- tests/VObject/Component/AvailableTest.php | 2 +- tests/VObject/Component/VAlarmTest.php | 5 +- tests/VObject/Component/VAvailabilityTest.php | 4 +- tests/VObject/Component/VCalendarTest.php | 5 +- tests/VObject/Component/VCardTest.php | 6 +- tests/VObject/ComponentTest.php | 43 ++--- tests/VObject/DateTimeParserTest.php | 20 +-- tests/VObject/DocumentTest.php | 12 +- tests/VObject/ElementListTest.php | 7 +- tests/VObject/EmptyParameterTest.php | 2 +- tests/VObject/FreeBusyGeneratorTest.php | 6 +- tests/VObject/ICalendar/AttachParseTest.php | 3 +- .../VObject/ITip/BrokerAttendeeReplyTest.php | 118 +++++++++++++ tests/VObject/ITip/BrokerNewEventTest.php | 16 +- tests/VObject/ITip/BrokerProcessReplyTest.php | 99 +++++++++++ tests/VObject/ITip/BrokerTester.php | 5 +- tests/VObject/Issue36WorkAroundTest.php | 2 +- tests/VObject/Issue64Test.php | 2 +- tests/VObject/Issue96Test.php | 2 +- tests/VObject/IssueUndefinedIndexTest.php | 4 +- tests/VObject/JCalTest.php | 42 ++--- tests/VObject/JCardTest.php | 36 ++-- tests/VObject/Parser/JsonTest.php | 87 +++++----- tests/VObject/Parser/MimeDirTest.php | 15 +- tests/VObject/Parser/QuotedPrintableTest.php | 9 +- tests/VObject/Property/BinaryTest.php | 4 +- tests/VObject/Property/FloatTest.php | 2 +- .../Property/ICalendar/DateTimeTest.php | 7 +- .../VObject/Property/ICalendar/RecurTest.php | 6 +- tests/VObject/Property/UriTest.php | 2 +- .../Property/VCard/DateAndOrTimeTest.php | 4 +- .../Property/VCard/LanguageTagTest.php | 4 +- .../Property/VCard/PhoneNumberTest.php | 19 +++ tests/VObject/PropertyTest.php | 14 +- tests/VObject/ReaderTest.php | 69 ++++---- .../EventIterator/ByMonthInDailyTest.php | 6 +- .../Recur/EventIterator/BySetPosHangTest.php | 6 +- .../EventIterator/ExpandFloatingTimesTest.php | 5 +- .../EventIterator/HandleRDateExpandTest.php | 3 +- .../EventIterator/IncorrectExpandTest.php | 3 +- .../EventIterator/InfiniteLoopProblemTest.php | 9 +- .../Recur/EventIterator/Issue26Test.php | 8 +- .../Recur/EventIterator/Issue48Test.php | 9 +- .../Recur/EventIterator/Issue50Test.php | 9 +- .../VObject/Recur/EventIterator/MainTest.php | 13 +- .../Recur/EventIterator/MaxInstancesTest.php | 5 +- .../EventIterator/MissingOverriddenTest.php | 3 +- .../Recur/EventIterator/NoInstancesTest.php | 11 +- .../EventIterator/OverrideFirstEventTest.php | 2 +- .../SameDateForRecurringEventsTest.php | 3 +- tests/VObject/Recur/FastForwardToEndTest.php | 6 +- tests/VObject/Recur/RRuleIteratorTest.php | 43 ++--- tests/VObject/Splitter/ICalendarTest.php | 11 +- tests/VObject/Splitter/VCardTest.php | 15 +- tests/VObject/TimeZoneUtilTest.php | 8 +- tests/VObject/VCardConverterTest.php | 40 ++++- tests/bootstrap.php | 10 -- tests/phpunit.xml | 12 +- 109 files changed, 894 insertions(+), 742 deletions(-) create mode 100644 lib/Property/VCard/PhoneNumber.php create mode 100644 tests/VObject/Property/VCard/PhoneNumberTest.php diff --git a/.gitignore b/.gitignore index f08b31359..b00b0fca1 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ vendor/ composer.lock tests/cov/ tests/temp +tests/.phpunit.result.cache #vim .*.swp @@ -17,6 +18,7 @@ bin/hoa # Development stuff testdata/ .php_cs.cache +.idea # OS X .DS_Store diff --git a/.php_cs.dist b/.php_cs.dist index 8d61ee259..7c6cf6749 100644 --- a/.php_cs.dist +++ b/.php_cs.dist @@ -6,7 +6,8 @@ $config->getFinder() ->in(__DIR__); $config->setRules([ '@PSR1' => true, - '@Symfony' =>true + '@Symfony' => true, + 'phpdoc_summary' => false ]); -return $config; \ No newline at end of file +return $config; diff --git a/.travis.yml b/.travis.yml index 686982b66..d1feb60de 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,38 +1,37 @@ language: php -sudo: required php: - - 5.5 - - 5.6 - - 7.0 - 7.1 - 7.2 - 7.3 + - 7.4 env: global: + - MEMCACHED_SERVER=127.0.0.1 - RUN_PHPSTAN="FALSE" + matrix: + - PREFER_LOWEST="" REPORT_COVERAGE="TRUE" WITH_COVERAGE="--coverage-clover=coverage.xml" + - PREFER_LOWEST="--prefer-lowest" REPORT_COVERAGE="FALSE" WITH_COVERAGE="" matrix: include: - name: 'PHPStan' - php: 7.2 - env: RUN_PHPSTAN="TRUE" + php: 7.4 + env: + - RUN_PHPSTAN="TRUE" + - REPORT_COVERAGE="FALSE" fast_finish: true - allow_failures: - - php: 5.5 - -install: - - if [ $RUN_PHPSTAN == "TRUE" ]; then wget https://github.com/phpstan/phpstan/releases/download/0.11.8/phpstan.phar; fi before_script: - - composer install + - composer update $PREFER_LOWEST script: - - if [ $RUN_PHPSTAN == "FALSE" ]; then ./bin/phpunit --configuration tests/phpunit.xml --coverage-clover=coverage.xml; fi - - if [ $RUN_PHPSTAN == "TRUE" ]; then php phpstan.phar analyse -c phpstan.neon lib; fi + - if [ $RUN_PHPSTAN == "FALSE" ]; then php vendor/bin/php-cs-fixer fix --dry-run --diff; fi + - if [ $RUN_PHPSTAN == "FALSE" ]; then php vendor/bin/phpunit --configuration tests/phpunit.xml $WITH_COVERAGE; fi + - if [ $RUN_PHPSTAN == "TRUE" ]; then composer phpstan; fi after_success: - - bash <(curl -s https://codecov.io/bash) + - if [ $REPORT_COVERAGE == "TRUE" ]; then bash <(curl -s https://codecov.io/bash); fi cache: directories: diff --git a/CHANGELOG.md b/CHANGELOG.md index e94063471..ff4480d92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,16 @@ ChangeLog ========= +4.3.4 (2020-07-27) +------------------ + +* # 16 Merge upstream changes from sabre-io/vobject:4.3.1 into protonlabs/vobject + +4.3.3 (2020-07-22) +------------------ + +* #15 Expose RRULE properties + 4.3.2 (2020-05-20) ------------------ diff --git a/composer.json b/composer.json index d757a4bc8..2187f77fe 100644 --- a/composer.json +++ b/composer.json @@ -32,12 +32,14 @@ "homepage" : "http://sabre.io/vobject/", "license" : "BSD-3-Clause", "require" : { - "php" : ">=5.5", + "php" : "^7.1", "ext-mbstring" : "*", - "sabre/xml" : ">=1.5 <3.0" + "sabre/xml" : "^2.1" }, "require-dev" : { - "phpunit/phpunit" : "> 4.8.35, <6.0.0" + "friendsofphp/php-cs-fixer": "~2.16.1", + "phpunit/phpunit" : "^7.5 || ^8.5 || ^9.0", + "phpstan/phpstan": "^0.12" }, "suggest" : { "hoa/bench" : "If you would like to run the benchmark scripts" @@ -71,6 +73,11 @@ "Sabre\\VObject\\" : "lib/" } }, + "autoload-dev" : { + "psr-4" : { + "Sabre\\VObject\\" : "tests/VObject" + } + }, "bin" : [ "bin/vobject", "bin/generate_vcards" @@ -80,7 +87,20 @@ "dev-master" : "4.0.x-dev" } }, - "config" : { - "bin-dir" : "bin" + "scripts": { + "phpstan": [ + "phpstan analyse lib tests" + ], + "cs-fixer": [ + "php-cs-fixer fix" + ], + "phpunit": [ + "phpunit --configuration tests/phpunit.xml" + ], + "test": [ + "composer phpstan", + "composer cs-fixer", + "composer phpunit" + ] } } diff --git a/lib/Cli.php b/lib/Cli.php index 70b5e8d6e..f3e419b15 100644 --- a/lib/Cli.php +++ b/lib/Cli.php @@ -29,7 +29,7 @@ class Cli protected $showHelp = false; /** - * Wether to spit out 'mimedir' or 'json' format. + * Whether to spit out 'mimedir' or 'json' format. * * @var string */ @@ -289,7 +289,7 @@ protected function showHelp() $this->log($this->colorize('green', ' validate').' source_file Validates a file for correctness.'); $this->log($this->colorize('green', ' repair').' source_file [output_file] Repairs a file.'); $this->log($this->colorize('green', ' convert').' source_file [output_file] Converts a file.'); - $this->log($this->colorize('green', ' color').' source_file Colorize a file, useful for debbugging.'); + $this->log($this->colorize('green', ' color').' source_file Colorize a file, useful for debugging.'); $this->log( << 'Sabre\\VObject\\Component\\VCalendar', - 'VALARM' => 'Sabre\\VObject\\Component\\VAlarm', - 'VEVENT' => 'Sabre\\VObject\\Component\\VEvent', - 'VFREEBUSY' => 'Sabre\\VObject\\Component\\VFreeBusy', - 'VAVAILABILITY' => 'Sabre\\VObject\\Component\\VAvailability', - 'AVAILABLE' => 'Sabre\\VObject\\Component\\Available', - 'VJOURNAL' => 'Sabre\\VObject\\Component\\VJournal', - 'VTIMEZONE' => 'Sabre\\VObject\\Component\\VTimeZone', - 'VTODO' => 'Sabre\\VObject\\Component\\VTodo', + 'VCALENDAR' => self::class, + 'VALARM' => VAlarm::class, + 'VEVENT' => VEvent::class, + 'VFREEBUSY' => VFreeBusy::class, + 'VAVAILABILITY' => VAvailability::class, + 'AVAILABLE' => Available::class, + 'VJOURNAL' => VJournal::class, + 'VTIMEZONE' => VTimeZone::class, + 'VTODO' => VTodo::class, ]; /** @@ -54,21 +54,21 @@ class VCalendar extends VObject\Document * @var array */ public static $valueMap = [ - 'BINARY' => 'Sabre\\VObject\\Property\\Binary', - 'BOOLEAN' => 'Sabre\\VObject\\Property\\Boolean', - 'CAL-ADDRESS' => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress', - 'DATE' => 'Sabre\\VObject\\Property\\ICalendar\\Date', - 'DATE-TIME' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', - 'DURATION' => 'Sabre\\VObject\\Property\\ICalendar\\Duration', - 'FLOAT' => 'Sabre\\VObject\\Property\\FloatValue', - 'INTEGER' => 'Sabre\\VObject\\Property\\IntegerValue', - 'PERIOD' => 'Sabre\\VObject\\Property\\ICalendar\\Period', - 'RECUR' => 'Sabre\\VObject\\Property\\ICalendar\\Recur', - 'TEXT' => 'Sabre\\VObject\\Property\\Text', - 'TIME' => 'Sabre\\VObject\\Property\\Time', - 'UNKNOWN' => 'Sabre\\VObject\\Property\\Unknown', // jCard / jCal-only. - 'URI' => 'Sabre\\VObject\\Property\\Uri', - 'UTC-OFFSET' => 'Sabre\\VObject\\Property\\UtcOffset', + 'BINARY' => VObject\Property\Binary::class, + 'BOOLEAN' => VObject\Property\Boolean::class, + 'CAL-ADDRESS' => VObject\Property\ICalendar\CalAddress::class, + 'DATE' => VObject\Property\ICalendar\Date::class, + 'DATE-TIME' => VObject\Property\ICalendar\DateTime::class, + 'DURATION' => VObject\Property\ICalendar\Duration::class, + 'FLOAT' => VObject\Property\FloatValue::class, + 'INTEGER' => VObject\Property\IntegerValue::class, + 'PERIOD' => VObject\Property\ICalendar\Period::class, + 'RECUR' => VObject\Property\ICalendar\Recur::class, + 'TEXT' => VObject\Property\Text::class, + 'TIME' => VObject\Property\Time::class, + 'UNKNOWN' => VObject\Property\Unknown::class, // jCard / jCal-only. + 'URI' => VObject\Property\Uri::class, + 'UTC-OFFSET' => VObject\Property\UtcOffset::class, ]; /** @@ -78,78 +78,78 @@ class VCalendar extends VObject\Document */ public static $propertyMap = [ // Calendar properties - 'CALSCALE' => 'Sabre\\VObject\\Property\\FlatText', - 'METHOD' => 'Sabre\\VObject\\Property\\FlatText', - 'PRODID' => 'Sabre\\VObject\\Property\\FlatText', - 'VERSION' => 'Sabre\\VObject\\Property\\FlatText', + 'CALSCALE' => VObject\Property\FlatText::class, + 'METHOD' => VObject\Property\FlatText::class, + 'PRODID' => VObject\Property\FlatText::class, + 'VERSION' => VObject\Property\FlatText::class, // Component properties - 'ATTACH' => 'Sabre\\VObject\\Property\\Uri', - 'CATEGORIES' => 'Sabre\\VObject\\Property\\Text', - 'CLASS' => 'Sabre\\VObject\\Property\\FlatText', - 'COMMENT' => 'Sabre\\VObject\\Property\\FlatText', - 'DESCRIPTION' => 'Sabre\\VObject\\Property\\FlatText', - 'GEO' => 'Sabre\\VObject\\Property\\FloatValue', - 'LOCATION' => 'Sabre\\VObject\\Property\\FlatText', - 'PERCENT-COMPLETE' => 'Sabre\\VObject\\Property\\IntegerValue', - 'PRIORITY' => 'Sabre\\VObject\\Property\\IntegerValue', - 'RESOURCES' => 'Sabre\\VObject\\Property\\Text', - 'STATUS' => 'Sabre\\VObject\\Property\\FlatText', - 'SUMMARY' => 'Sabre\\VObject\\Property\\FlatText', + 'ATTACH' => VObject\Property\Uri::class, + 'CATEGORIES' => VObject\Property\Text::class, + 'CLASS' => VObject\Property\FlatText::class, + 'COMMENT' => VObject\Property\FlatText::class, + 'DESCRIPTION' => VObject\Property\FlatText::class, + 'GEO' => VObject\Property\FloatValue::class, + 'LOCATION' => VObject\Property\FlatText::class, + 'PERCENT-COMPLETE' => VObject\Property\IntegerValue::class, + 'PRIORITY' => VObject\Property\IntegerValue::class, + 'RESOURCES' => VObject\Property\Text::class, + 'STATUS' => VObject\Property\FlatText::class, + 'SUMMARY' => VObject\Property\FlatText::class, // Date and Time Component Properties - 'COMPLETED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', - 'DTEND' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', - 'DUE' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', - 'DTSTART' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', - 'DURATION' => 'Sabre\\VObject\\Property\\ICalendar\\Duration', - 'FREEBUSY' => 'Sabre\\VObject\\Property\\ICalendar\\Period', - 'TRANSP' => 'Sabre\\VObject\\Property\\FlatText', + 'COMPLETED' => VObject\Property\ICalendar\DateTime::class, + 'DTEND' => VObject\Property\ICalendar\DateTime::class, + 'DUE' => VObject\Property\ICalendar\DateTime::class, + 'DTSTART' => VObject\Property\ICalendar\DateTime::class, + 'DURATION' => VObject\Property\ICalendar\Duration::class, + 'FREEBUSY' => VObject\Property\ICalendar\Period::class, + 'TRANSP' => VObject\Property\FlatText::class, // Time Zone Component Properties - 'TZID' => 'Sabre\\VObject\\Property\\FlatText', - 'TZNAME' => 'Sabre\\VObject\\Property\\FlatText', - 'TZOFFSETFROM' => 'Sabre\\VObject\\Property\\UtcOffset', - 'TZOFFSETTO' => 'Sabre\\VObject\\Property\\UtcOffset', - 'TZURL' => 'Sabre\\VObject\\Property\\Uri', + 'TZID' => VObject\Property\FlatText::class, + 'TZNAME' => VObject\Property\FlatText::class, + 'TZOFFSETFROM' => VObject\Property\UtcOffset::class, + 'TZOFFSETTO' => VObject\Property\UtcOffset::class, + 'TZURL' => VObject\Property\Uri::class, // Relationship Component Properties - 'ATTENDEE' => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress', - 'CONTACT' => 'Sabre\\VObject\\Property\\FlatText', - 'ORGANIZER' => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress', - 'RECURRENCE-ID' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', - 'RELATED-TO' => 'Sabre\\VObject\\Property\\FlatText', - 'URL' => 'Sabre\\VObject\\Property\\Uri', - 'UID' => 'Sabre\\VObject\\Property\\FlatText', + 'ATTENDEE' => VObject\Property\ICalendar\CalAddress::class, + 'CONTACT' => VObject\Property\FlatText::class, + 'ORGANIZER' => VObject\Property\ICalendar\CalAddress::class, + 'RECURRENCE-ID' => VObject\Property\ICalendar\DateTime::class, + 'RELATED-TO' => VObject\Property\FlatText::class, + 'URL' => VObject\Property\Uri::class, + 'UID' => VObject\Property\FlatText::class, // Recurrence Component Properties - 'EXDATE' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', - 'RDATE' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', - 'RRULE' => 'Sabre\\VObject\\Property\\ICalendar\\Recur', - 'EXRULE' => 'Sabre\\VObject\\Property\\ICalendar\\Recur', // Deprecated since rfc5545 + 'EXDATE' => VObject\Property\ICalendar\DateTime::class, + 'RDATE' => VObject\Property\ICalendar\DateTime::class, + 'RRULE' => VObject\Property\ICalendar\Recur::class, + 'EXRULE' => VObject\Property\ICalendar\Recur::class, // Deprecated since rfc5545 // Alarm Component Properties - 'ACTION' => 'Sabre\\VObject\\Property\\FlatText', - 'REPEAT' => 'Sabre\\VObject\\Property\\IntegerValue', - 'TRIGGER' => 'Sabre\\VObject\\Property\\ICalendar\\Duration', + 'ACTION' => VObject\Property\FlatText::class, + 'REPEAT' => VObject\Property\IntegerValue::class, + 'TRIGGER' => VObject\Property\ICalendar\Duration::class, // Change Management Component Properties - 'CREATED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', - 'DTSTAMP' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', - 'LAST-MODIFIED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', - 'SEQUENCE' => 'Sabre\\VObject\\Property\\IntegerValue', + 'CREATED' => VObject\Property\ICalendar\DateTime::class, + 'DTSTAMP' => VObject\Property\ICalendar\DateTime::class, + 'LAST-MODIFIED' => VObject\Property\ICalendar\DateTime::class, + 'SEQUENCE' => VObject\Property\IntegerValue::class, // Request Status - 'REQUEST-STATUS' => 'Sabre\\VObject\\Property\\Text', + 'REQUEST-STATUS' => VObject\Property\Text::class, // Additions from draft-daboo-valarm-extensions-04 - 'ALARM-AGENT' => 'Sabre\\VObject\\Property\\Text', - 'ACKNOWLEDGED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', - 'PROXIMITY' => 'Sabre\\VObject\\Property\\Text', - 'DEFAULT-ALARM' => 'Sabre\\VObject\\Property\\Boolean', + 'ALARM-AGENT' => VObject\Property\Text::class, + 'ACKNOWLEDGED' => VObject\Property\ICalendar\DateTime::class, + 'PROXIMITY' => VObject\Property\Text::class, + 'DEFAULT-ALARM' => VObject\Property\Boolean::class, // Additions from draft-daboo-calendar-availability-05 - 'BUSYTYPE' => 'Sabre\\VObject\\Property\\Text', + 'BUSYTYPE' => VObject\Property\Text::class, ]; /** @@ -276,10 +276,8 @@ public function getBaseComponent($componentName = null) * In addition, this method will cause timezone information to be stripped, * and normalized to UTC. * - * @param DateTimeInterface $start - * @param DateTimeInterface $end - * @param DateTimeZone $timeZone reference timezone for floating dates and - * times + * @param DateTimeZone $timeZone reference timezone for floating dates and + * times * * @return VCalendar */ diff --git a/lib/Component/VCard.php b/lib/Component/VCard.php index 860e45ffa..51321949f 100644 --- a/lib/Component/VCard.php +++ b/lib/Component/VCard.php @@ -39,7 +39,7 @@ class VCard extends VObject\Document * @var array */ public static $componentMap = [ - 'VCARD' => 'Sabre\\VObject\\Component\\VCard', + 'VCARD' => VCard::class, ]; /** @@ -48,22 +48,23 @@ class VCard extends VObject\Document * @var array */ public static $valueMap = [ - 'BINARY' => 'Sabre\\VObject\\Property\\Binary', - 'BOOLEAN' => 'Sabre\\VObject\\Property\\Boolean', - 'CONTENT-ID' => 'Sabre\\VObject\\Property\\FlatText', // vCard 2.1 only - 'DATE' => 'Sabre\\VObject\\Property\\VCard\\Date', - 'DATE-TIME' => 'Sabre\\VObject\\Property\\VCard\\DateTime', - 'DATE-AND-OR-TIME' => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime', // vCard only - 'FLOAT' => 'Sabre\\VObject\\Property\\FloatValue', - 'INTEGER' => 'Sabre\\VObject\\Property\\IntegerValue', - 'LANGUAGE-TAG' => 'Sabre\\VObject\\Property\\VCard\\LanguageTag', - 'TIMESTAMP' => 'Sabre\\VObject\\Property\\VCard\\TimeStamp', - 'TEXT' => 'Sabre\\VObject\\Property\\Text', - 'TIME' => 'Sabre\\VObject\\Property\\Time', - 'UNKNOWN' => 'Sabre\\VObject\\Property\\Unknown', // jCard / jCal-only. - 'URI' => 'Sabre\\VObject\\Property\\Uri', - 'URL' => 'Sabre\\VObject\\Property\\Uri', // vCard 2.1 only - 'UTC-OFFSET' => 'Sabre\\VObject\\Property\\UtcOffset', + 'BINARY' => VObject\Property\Binary::class, + 'BOOLEAN' => VObject\Property\Boolean::class, + 'CONTENT-ID' => VObject\Property\FlatText::class, // vCard 2.1 only + 'DATE' => VObject\Property\VCard\Date::class, + 'DATE-TIME' => VObject\Property\VCard\DateTime::class, + 'DATE-AND-OR-TIME' => VObject\Property\VCard\DateAndOrTime::class, // vCard only + 'FLOAT' => VObject\Property\FloatValue::class, + 'INTEGER' => VObject\Property\IntegerValue::class, + 'LANGUAGE-TAG' => VObject\Property\VCard\LanguageTag::class, + 'PHONE-NUMBER' => VObject\Property\VCard\PhoneNumber::class, // vCard 3.0 only + 'TIMESTAMP' => VObject\Property\VCard\TimeStamp::class, + 'TEXT' => VObject\Property\Text::class, + 'TIME' => VObject\Property\Time::class, + 'UNKNOWN' => VObject\Property\Unknown::class, // jCard / jCal-only. + 'URI' => VObject\Property\Uri::class, + 'URL' => VObject\Property\Uri::class, // vCard 2.1 only + 'UTC-OFFSET' => VObject\Property\UtcOffset::class, ]; /** @@ -73,68 +74,68 @@ class VCard extends VObject\Document */ public static $propertyMap = [ // vCard 2.1 properties and up - 'N' => 'Sabre\\VObject\\Property\\Text', - 'FN' => 'Sabre\\VObject\\Property\\FlatText', - 'PHOTO' => 'Sabre\\VObject\\Property\\Binary', - 'BDAY' => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime', - 'ADR' => 'Sabre\\VObject\\Property\\Text', - 'LABEL' => 'Sabre\\VObject\\Property\\FlatText', // Removed in vCard 4.0 - 'TEL' => 'Sabre\\VObject\\Property\\FlatText', - 'EMAIL' => 'Sabre\\VObject\\Property\\FlatText', - 'MAILER' => 'Sabre\\VObject\\Property\\FlatText', // Removed in vCard 4.0 - 'GEO' => 'Sabre\\VObject\\Property\\FlatText', - 'TITLE' => 'Sabre\\VObject\\Property\\FlatText', - 'ROLE' => 'Sabre\\VObject\\Property\\FlatText', - 'LOGO' => 'Sabre\\VObject\\Property\\Binary', + 'N' => VObject\Property\Text::class, + 'FN' => VObject\Property\FlatText::class, + 'PHOTO' => VObject\Property\Binary::class, + 'BDAY' => VObject\Property\VCard\DateAndOrTime::class, + 'ADR' => VObject\Property\Text::class, + 'LABEL' => VObject\Property\FlatText::class, // Removed in vCard 4.0 + 'TEL' => VObject\Property\FlatText::class, + 'EMAIL' => VObject\Property\FlatText::class, + 'MAILER' => VObject\Property\FlatText::class, // Removed in vCard 4.0 + 'GEO' => VObject\Property\FlatText::class, + 'TITLE' => VObject\Property\FlatText::class, + 'ROLE' => VObject\Property\FlatText::class, + 'LOGO' => VObject\Property\Binary::class, // 'AGENT' => 'Sabre\\VObject\\Property\\', // Todo: is an embedded vCard. Probably rare, so // not supported at the moment - 'ORG' => 'Sabre\\VObject\\Property\\Text', - 'NOTE' => 'Sabre\\VObject\\Property\\FlatText', - 'REV' => 'Sabre\\VObject\\Property\\VCard\\TimeStamp', - 'SOUND' => 'Sabre\\VObject\\Property\\FlatText', - 'URL' => 'Sabre\\VObject\\Property\\Uri', - 'UID' => 'Sabre\\VObject\\Property\\FlatText', - 'VERSION' => 'Sabre\\VObject\\Property\\FlatText', - 'KEY' => 'Sabre\\VObject\\Property\\FlatText', - 'TZ' => 'Sabre\\VObject\\Property\\Text', + 'ORG' => VObject\Property\Text::class, + 'NOTE' => VObject\Property\FlatText::class, + 'REV' => VObject\Property\VCard\TimeStamp::class, + 'SOUND' => VObject\Property\FlatText::class, + 'URL' => VObject\Property\Uri::class, + 'UID' => VObject\Property\FlatText::class, + 'VERSION' => VObject\Property\FlatText::class, + 'KEY' => VObject\Property\FlatText::class, + 'TZ' => VObject\Property\Text::class, // vCard 3.0 properties - 'CATEGORIES' => 'Sabre\\VObject\\Property\\Text', - 'SORT-STRING' => 'Sabre\\VObject\\Property\\FlatText', - 'PRODID' => 'Sabre\\VObject\\Property\\FlatText', - 'NICKNAME' => 'Sabre\\VObject\\Property\\Text', - 'CLASS' => 'Sabre\\VObject\\Property\\FlatText', // Removed in vCard 4.0 + 'CATEGORIES' => VObject\Property\Text::class, + 'SORT-STRING' => VObject\Property\FlatText::class, + 'PRODID' => VObject\Property\FlatText::class, + 'NICKNAME' => VObject\Property\Text::class, + 'CLASS' => VObject\Property\FlatText::class, // Removed in vCard 4.0 // rfc2739 properties - 'FBURL' => 'Sabre\\VObject\\Property\\Uri', - 'CAPURI' => 'Sabre\\VObject\\Property\\Uri', - 'CALURI' => 'Sabre\\VObject\\Property\\Uri', - 'CALADRURI' => 'Sabre\\VObject\\Property\\Uri', + 'FBURL' => VObject\Property\Uri::class, + 'CAPURI' => VObject\Property\Uri::class, + 'CALURI' => VObject\Property\Uri::class, + 'CALADRURI' => VObject\Property\Uri::class, // rfc4770 properties - 'IMPP' => 'Sabre\\VObject\\Property\\Uri', + 'IMPP' => VObject\Property\Uri::class, // vCard 4.0 properties - 'SOURCE' => 'Sabre\\VObject\\Property\\Uri', - 'XML' => 'Sabre\\VObject\\Property\\FlatText', - 'ANNIVERSARY' => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime', - 'CLIENTPIDMAP' => 'Sabre\\VObject\\Property\\Text', - 'LANG' => 'Sabre\\VObject\\Property\\VCard\\LanguageTag', - 'GENDER' => 'Sabre\\VObject\\Property\\Text', - 'KIND' => 'Sabre\\VObject\\Property\\FlatText', - 'MEMBER' => 'Sabre\\VObject\\Property\\Uri', - 'RELATED' => 'Sabre\\VObject\\Property\\Uri', + 'SOURCE' => VObject\Property\Uri::class, + 'XML' => VObject\Property\FlatText::class, + 'ANNIVERSARY' => VObject\Property\VCard\DateAndOrTime::class, + 'CLIENTPIDMAP' => VObject\Property\Text::class, + 'LANG' => VObject\Property\VCard\LanguageTag::class, + 'GENDER' => VObject\Property\Text::class, + 'KIND' => VObject\Property\FlatText::class, + 'MEMBER' => VObject\Property\Uri::class, + 'RELATED' => VObject\Property\Uri::class, // rfc6474 properties - 'BIRTHPLACE' => 'Sabre\\VObject\\Property\\FlatText', - 'DEATHPLACE' => 'Sabre\\VObject\\Property\\FlatText', - 'DEATHDATE' => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime', + 'BIRTHPLACE' => VObject\Property\FlatText::class, + 'DEATHPLACE' => VObject\Property\FlatText::class, + 'DEATHDATE' => VObject\Property\VCard\DateAndOrTime::class, // rfc6715 properties - 'EXPERTISE' => 'Sabre\\VObject\\Property\\FlatText', - 'HOBBY' => 'Sabre\\VObject\\Property\\FlatText', - 'INTEREST' => 'Sabre\\VObject\\Property\\FlatText', - 'ORG-DIRECTORY' => 'Sabre\\VObject\\Property\\FlatText', + 'EXPERTISE' => VObject\Property\FlatText::class, + 'HOBBY' => VObject\Property\FlatText::class, + 'INTEREST' => VObject\Property\FlatText::class, + 'ORG-DIRECTORY' => VObject\Property\FlatText::class, ]; /** @@ -525,8 +526,8 @@ public function getClassNameForPropertyName($propertyName) $className = parent::getClassNameForPropertyName($propertyName); // In vCard 4, BINARY no longer exists, and we need URI instead. - if ('Sabre\\VObject\\Property\\Binary' == $className && self::VCARD40 === $this->getDocumentType()) { - return 'Sabre\\VObject\\Property\\Uri'; + if (VObject\Property\Binary::class == $className && self::VCARD40 === $this->getDocumentType()) { + return VObject\Property\Uri::class; } return $className; diff --git a/lib/Component/VEvent.php b/lib/Component/VEvent.php index 09f37033c..6ea93ed5e 100644 --- a/lib/Component/VEvent.php +++ b/lib/Component/VEvent.php @@ -25,9 +25,6 @@ class VEvent extends VObject\Component * The rules used to determine if an event falls within the specified * time-range is based on the CalDAV specification. * - * @param DateTimeInterface $start - * @param DateTimeInterface $end - * * @return bool */ public function isInTimeRange(DateTimeInterface $start, DateTimeInterface $end) @@ -36,7 +33,7 @@ public function isInTimeRange(DateTimeInterface $start, DateTimeInterface $end) try { $it = new EventIterator($this, null, $start->getTimezone()); } catch (NoInstancesException $e) { - // If we've catched this exception, there are no instances + // If we've caught this exception, there are no instances // for the event that fall into the specified time-range. return false; } diff --git a/lib/Component/VFreeBusy.php b/lib/Component/VFreeBusy.php index 558a85233..fef418b53 100644 --- a/lib/Component/VFreeBusy.php +++ b/lib/Component/VFreeBusy.php @@ -21,9 +21,6 @@ class VFreeBusy extends VObject\Component * Checks based on the contained FREEBUSY information, if a timeslot is * available. * - * @param DateTimeInterface $start - * @param DateTimeInterface $end - * * @return bool */ public function isFree(DateTimeInterface $start, DatetimeInterface $end) diff --git a/lib/Component/VJournal.php b/lib/Component/VJournal.php index 9bd336776..9b7f1b873 100644 --- a/lib/Component/VJournal.php +++ b/lib/Component/VJournal.php @@ -23,9 +23,6 @@ class VJournal extends VObject\Component * The rules used to determine if an event falls within the specified * time-range is based on the CalDAV specification. * - * @param DateTimeInterface $start - * @param DateTimeInterface $end - * * @return bool */ public function isInTimeRange(DateTimeInterface $start, DateTimeInterface $end) diff --git a/lib/Component/VTodo.php b/lib/Component/VTodo.php index 9de77e841..6f022ba6d 100644 --- a/lib/Component/VTodo.php +++ b/lib/Component/VTodo.php @@ -23,9 +23,6 @@ class VTodo extends VObject\Component * The rules used to determine if an event falls within the specified * time-range is based on the CalDAV specification. * - * @param DateTimeInterface $start - * @param DateTimeInterface $end - * * @return bool */ public function isInTimeRange(DateTimeInterface $start, DateTimeInterface $end) diff --git a/lib/Document.php b/lib/Document.php index 0cb2e0978..14a77c911 100644 --- a/lib/Document.php +++ b/lib/Document.php @@ -160,7 +160,7 @@ public function create($name) public function createComponent($name, array $children = null, $defaults = true) { $name = strtoupper($name); - $class = 'Sabre\\VObject\\Component'; + $class = Component::class; if (isset(static::$componentMap[$name])) { $class = static::$componentMap[$name]; @@ -258,7 +258,7 @@ public function getClassNameForPropertyName($propertyName) if (isset(static::$propertyMap[$propertyName])) { return static::$propertyMap[$propertyName]; } else { - return 'Sabre\\VObject\\Property\\Unknown'; + return Property\Unknown::class; } } } diff --git a/lib/FreeBusyGenerator.php b/lib/FreeBusyGenerator.php index adb214c08..a1c24044c 100644 --- a/lib/FreeBusyGenerator.php +++ b/lib/FreeBusyGenerator.php @@ -109,8 +109,6 @@ public function __construct(DateTimeInterface $start = null, DateTimeInterface $ * for setting things like the METHOD, CALSCALE, VERSION, etc.. * * The VFREEBUSY object will be automatically added though. - * - * @param Document $vcalendar */ public function setBaseObject(Document $vcalendar) { @@ -119,8 +117,6 @@ public function setBaseObject(Document $vcalendar) /** * Sets a VAVAILABILITY document. - * - * @param Document $vcalendar */ public function setVAvailability(Document $vcalendar) { @@ -176,8 +172,6 @@ public function setTimeRange(DateTimeInterface $start = null, DateTimeInterface /** * Sets the reference timezone for floating times. - * - * @param DateTimeZone $timeZone */ public function setTimeZone(DateTimeZone $timeZone) { @@ -208,9 +202,6 @@ public function getResult() /** * This method takes a VAVAILABILITY component and figures out all the * available times. - * - * @param FreeBusyData $fbData - * @param VCalendar $vavailability */ protected function calculateAvailability(FreeBusyData $fbData, VCalendar $vavailability) { @@ -363,8 +354,7 @@ function ($a, $b) { * This method takes an array of iCalendar objects and applies its busy * times on fbData. * - * @param FreeBusyData $fbData - * @param VCalendar[] $objects + * @param VCalendar[] $objects */ protected function calculateBusy(FreeBusyData $fbData, array $objects) { diff --git a/lib/ITip/Broker.php b/lib/ITip/Broker.php index 4f37b75d0..c09cdf3be 100644 --- a/lib/ITip/Broker.php +++ b/lib/ITip/Broker.php @@ -104,7 +104,6 @@ class Broker * * If the iTip message was not supported, we will always return false. * - * @param Message $itipMessage * @param VCalendar $existingObject * * @return VCalendar|null @@ -263,8 +262,6 @@ public function parseEvent($calendar = null, $userHref, $oldCalendar = null) * This is message from an organizer, and is either a new event * invite, or an update to an existing one. * - * - * @param Message $itipMessage * @param VCalendar $existingObject * * @return VCalendar|null @@ -300,7 +297,6 @@ protected function processMessageRequest(Message $itipMessage, VCalendar $existi * attendee got removed from an event, or an event got cancelled * altogether. * - * @param Message $itipMessage * @param VCalendar $existingObject * * @return VCalendar|null @@ -326,7 +322,6 @@ protected function processMessageCancel(Message $itipMessage, VCalendar $existin * The message is a reply. This is for example an attendee telling * an organizer he accepted the invite, or declined it. * - * @param Message $itipMessage * @param VCalendar $existingObject * * @return VCalendar|null @@ -452,10 +447,6 @@ protected function processMessageReply(Message $itipMessage, VCalendar $existing * We will detect which attendees got added, which got removed and create * specific messages for these situations. * - * @param VCalendar $calendar - * @param array $eventInfo - * @param array $oldEventInfo - * * @return array */ protected function parseEventForOrganizer(VCalendar $calendar, array $eventInfo, array $oldEventInfo) @@ -505,20 +496,21 @@ protected function parseEventForOrganizer(VCalendar $calendar, array $eventInfo, $message->recipient = $attendee['href']; $message->recipientName = $attendee['name']; + // Creating the new iCalendar body. + $icalMsg = new VCalendar(); + + foreach ($calendar->select('VTIMEZONE') as $timezone) { + $icalMsg->add(clone $timezone); + } + if (!$attendee['newInstances']) { // If there are no instances the attendee is a part of, it // means the attendee was removed and we need to send him a // CANCEL. $message->method = 'CANCEL'; - // Creating the new iCalendar body. - $icalMsg = new VCalendar(); $icalMsg->METHOD = $message->method; - foreach ($calendar->select('VTIMEZONE') as $timezone) { - $icalMsg->add(clone $timezone); - } - $event = $icalMsg->add('VEVENT', [ 'UID' => $message->uid, 'SEQUENCE' => $message->sequence, @@ -545,14 +537,8 @@ protected function parseEventForOrganizer(VCalendar $calendar, array $eventInfo, // The attendee gets the updated event body $message->method = 'REQUEST'; - // Creating the new iCalendar body. - $icalMsg = new VCalendar(); $icalMsg->METHOD = $message->method; - foreach ($calendar->select('VTIMEZONE') as $timezone) { - $icalMsg->add(clone $timezone); - } - // We need to find out that this change is significant. If it's // not, systems may opt to not send messages. // @@ -625,10 +611,7 @@ protected function parseEventForOrganizer(VCalendar $calendar, array $eventInfo, * * This function figures out if we need to send a reply to an organizer. * - * @param VCalendar $calendar - * @param array $eventInfo - * @param array $oldEventInfo - * @param string $attendee + * @param string $attendee * * @return Message[] */ @@ -711,6 +694,10 @@ protected function parseEventForAttendee(VCalendar $calendar, array $eventInfo, $icalMsg = new VCalendar(); $icalMsg->METHOD = 'REPLY'; + foreach ($calendar->select('VTIMEZONE') as $timezone) { + $icalMsg->add(clone $timezone); + } + $hasReply = false; foreach ($instances as $instance) { diff --git a/lib/Node.php b/lib/Node.php index 154a7fac5..4c0c04f72 100644 --- a/lib/Node.php +++ b/lib/Node.php @@ -115,8 +115,6 @@ public function getIterator() * Sets the overridden iterator. * * Note that this is not actually part of the iterator interface - * - * @param ElementList $iterator */ public function setIterator(ElementList $iterator) { diff --git a/lib/PHPUnitAssertions.php b/lib/PHPUnitAssertions.php index d77e4b1ed..45c0a21c6 100644 --- a/lib/PHPUnitAssertions.php +++ b/lib/PHPUnitAssertions.php @@ -15,7 +15,7 @@ trait PHPUnitAssertions { /** - * This method tests wether two vcards or icalendar objects are + * This method tests whether two vcards or icalendar objects are * semantically identical. * * It supports objects being supplied as strings, streams or @@ -34,8 +34,7 @@ trait PHPUnitAssertions */ public function assertVObjectEqualsVObject($expected, $actual, $message = '') { - $self = $this; - $getObj = function ($input) use ($self) { + $getObj = function ($input) { if (is_resource($input)) { $input = stream_get_contents($input); } diff --git a/lib/Parameter.php b/lib/Parameter.php index 2c9a8e7fd..e39d320a1 100644 --- a/lib/Parameter.php +++ b/lib/Parameter.php @@ -201,8 +201,6 @@ public function getValue() /** * Sets multiple values for this parameter. - * - * @param array $value */ public function setParts(array $value) { diff --git a/lib/Parser/Json.php b/lib/Parser/Json.php index 3fd307e97..f33603207 100644 --- a/lib/Parser/Json.php +++ b/lib/Parser/Json.php @@ -7,6 +7,8 @@ use Sabre\VObject\Document; use Sabre\VObject\EofException; use Sabre\VObject\ParseException; +use Sabre\VObject\Property\FlatText; +use Sabre\VObject\Property\Text; /** * Json Parser. @@ -87,8 +89,6 @@ public function parse($input = null, $options = 0) /** * Parses a component. * - * @param array $jComp - * * @return \Sabre\VObject\Component */ public function parseComponent(array $jComp) @@ -124,8 +124,6 @@ function ($jComp) use ($self) { /** * Parses properties. * - * @param array $jProp - * * @return \Sabre\VObject\Property */ public function parseProperty(array $jProp) @@ -160,8 +158,8 @@ public function parseProperty(array $jProp) // represents TEXT values. We have to normalize these here. In the // future we can get rid of FlatText once we're allowed to break BC // again. - if ('Sabre\VObject\Property\FlatText' === $defaultPropertyClass) { - $defaultPropertyClass = 'Sabre\VObject\Property\Text'; + if (FlatText::class === $defaultPropertyClass) { + $defaultPropertyClass = Text::class; } // If the value type we received (e.g.: TEXT) was not the default value diff --git a/lib/Parser/MimeDir.php b/lib/Parser/MimeDir.php index 26a7101e5..ea5ac0326 100644 --- a/lib/Parser/MimeDir.php +++ b/lib/Parser/MimeDir.php @@ -124,7 +124,7 @@ public function setInput($input) $this->startLine = 0; if (is_string($input)) { - // Convering to a stream. + // Converting to a stream. $stream = fopen('php://temp', 'r+'); fwrite($stream, $input); rewind($stream); @@ -480,7 +480,7 @@ protected function readProperty($line) * vCard 3.0 says: * * (rfc2425) Backslashes, newlines (\n or \N) and comma's must be * escaped, all time time. - * * Comma's are used for delimeters in multiple values + * * Comma's are used for delimiters in multiple values * * (rfc2426) Adds to to this that the semi-colon MUST also be escaped, * as in some properties semi-colon is used for separators. * * Properties using semi-colons: N, ADR, GEO, ORG diff --git a/lib/Parser/XML.php b/lib/Parser/XML.php index 90f262d9e..78773173d 100644 --- a/lib/Parser/XML.php +++ b/lib/Parser/XML.php @@ -112,8 +112,6 @@ public function parse($input = null, $options = 0) /** * Parse a xCalendar component. - * - * @param Component $parentComponent */ protected function parseVCalendarComponents(Component $parentComponent) { @@ -134,8 +132,6 @@ protected function parseVCalendarComponents(Component $parentComponent) /** * Parse a xCard component. - * - * @param Component $parentComponent */ protected function parseVCardComponents(Component $parentComponent) { @@ -146,8 +142,7 @@ protected function parseVCardComponents(Component $parentComponent) /** * Parse xCalendar and xCard properties. * - * @param Component $parentComponent - * @param string $propertyNamePrefix + * @param string $propertyNamePrefix */ protected function parseProperties(Component $parentComponent, $propertyNamePrefix = '') { @@ -302,8 +297,6 @@ protected function parseProperties(Component $parentComponent, $propertyNamePref /** * Parse a component. - * - * @param Component $parentComponent */ protected function parseComponent(Component $parentComponent) { @@ -327,11 +320,10 @@ protected function parseComponent(Component $parentComponent) /** * Create a property. * - * @param Component $parentComponent - * @param string $name - * @param array $parameters - * @param string $type - * @param mixed $value + * @param string $name + * @param array $parameters + * @param string $type + * @param mixed $value */ protected function createProperty(Component $parentComponent, $name, $parameters, $type, $value) { @@ -359,9 +351,9 @@ public function setInput($input) if (is_string($input)) { $reader = new SabreXml\Reader(); $reader->elementMap['{'.self::XCAL_NAMESPACE.'}period'] - = 'Sabre\VObject\Parser\XML\Element\KeyValue'; + = XML\Element\KeyValue::class; $reader->elementMap['{'.self::XCAL_NAMESPACE.'}recur'] - = 'Sabre\VObject\Parser\XML\Element\KeyValue'; + = XML\Element\KeyValue::class; $reader->xml($input); $input = $reader->parse(); } diff --git a/lib/Parser/XML/Element/KeyValue.php b/lib/Parser/XML/Element/KeyValue.php index e26540036..c0bbf0d9b 100644 --- a/lib/Parser/XML/Element/KeyValue.php +++ b/lib/Parser/XML/Element/KeyValue.php @@ -18,7 +18,7 @@ class KeyValue extends SabreXml\Element\KeyValue /** * The deserialize method is called during xml parsing. * - * This method is called staticly, this is because in theory this method + * This method is called statically, this is because in theory this method * may be used as a type of constructor, or factory method. * * Often you want to return an instance of the current class, but you are diff --git a/lib/Property.php b/lib/Property.php index 6105cb0f0..f9cf8e38e 100644 --- a/lib/Property.php +++ b/lib/Property.php @@ -123,8 +123,6 @@ public function getValue() /** * Sets a multi-valued property. - * - * @param array $parts */ public function setParts(array $parts) { @@ -262,8 +260,6 @@ public function getJsonValue() * Sets the JSON value, as it would appear in a jCard or jCal object. * * The value must always be an array. - * - * @param array $value */ public function setJsonValue(array $value) { @@ -309,8 +305,6 @@ public function jsonSerialize() /** * Hydrate data from a XML subtree, as it would appear in a xCard or xCal * object. - * - * @param array $value */ public function setXmlValue(array $value) { diff --git a/lib/Property/Binary.php b/lib/Property/Binary.php index 830dd9028..ec6713fdd 100644 --- a/lib/Property/Binary.php +++ b/lib/Property/Binary.php @@ -100,8 +100,6 @@ public function getJsonValue() * Sets the json value, as it would appear in a jCard or jCal object. * * The value must always be an array. - * - * @param array $value */ public function setJsonValue(array $value) { diff --git a/lib/Property/Boolean.php b/lib/Property/Boolean.php index 1b219bb8c..9fb2bce35 100644 --- a/lib/Property/Boolean.php +++ b/lib/Property/Boolean.php @@ -8,7 +8,7 @@ /** * Boolean property. * - * This object represents BOOLEAN values. These are always the case-insenstive + * This object represents BOOLEAN values. These are always the case-insensitive * string TRUE or FALSE. * * Automatic conversion to PHP's true and false are done. @@ -59,8 +59,6 @@ public function getValueType() /** * Hydrate data from a XML subtree, as it would appear in a xCard or xCal * object. - * - * @param array $value */ public function setXmlValue(array $value) { diff --git a/lib/Property/FloatValue.php b/lib/Property/FloatValue.php index 208d74516..0d0346968 100644 --- a/lib/Property/FloatValue.php +++ b/lib/Property/FloatValue.php @@ -93,8 +93,6 @@ public function getJsonValue() /** * Hydrate data from a XML subtree, as it would appear in a xCard or xCal * object. - * - * @param array $value */ public function setXmlValue(array $value) { diff --git a/lib/Property/ICalendar/DateTime.php b/lib/Property/ICalendar/DateTime.php index 7eb3e0bb7..f2dbdeba3 100644 --- a/lib/Property/ICalendar/DateTime.php +++ b/lib/Property/ICalendar/DateTime.php @@ -38,8 +38,6 @@ class DateTime extends Property * Sets a multi-valued property. * * You may also specify DateTime objects here. - * - * @param array $parts */ public function setParts(array $parts) { @@ -175,7 +173,6 @@ public function getDateTimes(DateTimeZone $timeZone = null) /** * Sets the property as a DateTime object. * - * @param DateTimeInterface $dt * @param bool isFloating If set to true, timezones will be ignored */ public function setDateTime(DateTimeInterface $dt, $isFloating = false) @@ -279,8 +276,6 @@ function (DateTimeInterface $dt) use ($hasTime, $isUtc) { * Sets the json value, as it would appear in a jCard or jCal object. * * The value must always be an array. - * - * @param array $value */ public function setJsonValue(array $value) { @@ -343,8 +338,8 @@ public function validate($options = 0) $messages = parent::validate($options); $valueType = $this->getValueType(); $values = $this->getParts(); - try { - foreach ($values as $value) { + foreach ($values as $value) { + try { switch ($valueType) { case 'DATE': DateTimeParser::parseDate($value); @@ -353,13 +348,14 @@ public function validate($options = 0) DateTimeParser::parseDateTime($value); break; } + } catch (InvalidDataException $e) { + $messages[] = [ + 'level' => 3, + 'message' => 'The supplied value ('.$value.') is not a correct '.$valueType, + 'node' => $this, + ]; + break; } - } catch (InvalidDataException $e) { - $messages[] = [ - 'level' => 3, - 'message' => 'The supplied value ('.$value.') is not a correct '.$valueType, - 'node' => $this, - ]; } return $messages; diff --git a/lib/Property/ICalendar/Period.php b/lib/Property/ICalendar/Period.php index 17bfa5c5c..eb3752770 100644 --- a/lib/Property/ICalendar/Period.php +++ b/lib/Property/ICalendar/Period.php @@ -67,8 +67,6 @@ public function getValueType() * Sets the json value, as it would appear in a jCard or jCal object. * * The value must always be an array. - * - * @param array $value */ public function setJsonValue(array $value) { diff --git a/lib/Property/ICalendar/Recur.php b/lib/Property/ICalendar/Recur.php index baeda781e..3d632fec1 100644 --- a/lib/Property/ICalendar/Recur.php +++ b/lib/Property/ICalendar/Recur.php @@ -88,8 +88,6 @@ public function getValue() /** * Sets a multi-valued property. - * - * @param array $parts */ public function setParts(array $parts) { diff --git a/lib/Property/IntegerValue.php b/lib/Property/IntegerValue.php index ddd71d731..6f709bfff 100644 --- a/lib/Property/IntegerValue.php +++ b/lib/Property/IntegerValue.php @@ -9,7 +9,7 @@ * Integer property. * * This object represents INTEGER values. These are always a single integer. - * They may be preceeded by either + or -. + * They may be preceded by either + or -. * * @copyright Copyright (C) fruux GmbH (https://fruux.com/) * @author Evert Pot (http://evertpot.com/) @@ -68,8 +68,6 @@ public function getJsonValue() /** * Hydrate data from a XML subtree, as it would appear in a xCard or xCal * object. - * - * @param array $value */ public function setXmlValue(array $value) { diff --git a/lib/Property/Text.php b/lib/Property/Text.php index 23c945551..ac8aa066b 100644 --- a/lib/Property/Text.php +++ b/lib/Property/Text.php @@ -111,7 +111,7 @@ public function setQuotedPrintableValue($val) // that. // // We also don't have to unescape \\, so all we need to look for is a ; - // that's not preceeded with a \. + // that's not preceded with a \. $regex = '# (?setValue($matches); diff --git a/lib/Property/Time.php b/lib/Property/Time.php index 7aeafc8d0..544b5ced3 100644 --- a/lib/Property/Time.php +++ b/lib/Property/Time.php @@ -40,8 +40,6 @@ public function getValueType() * Sets the JSON value, as it would appear in a jCard or jCal object. * * The value must always be an array. - * - * @param array $value */ public function setJsonValue(array $value) { @@ -119,8 +117,6 @@ public function getJsonValue() /** * Hydrate data from a XML subtree, as it would appear in a xCard or xCal * object. - * - * @param array $value */ public function setXmlValue(array $value) { diff --git a/lib/Property/Uri.php b/lib/Property/Uri.php index 3449ba1f2..830cd3f18 100644 --- a/lib/Property/Uri.php +++ b/lib/Property/Uri.php @@ -72,7 +72,7 @@ public function setRawMimeDirValue($val) { // Normally we don't need to do any type of unescaping for these // properties, however.. we've noticed that Google Contacts - // specifically escapes the colon (:) with a blackslash. While I have + // specifically escapes the colon (:) with a backslash. While I have // no clue why they thought that was a good idea, I'm unescaping it // anyway. // diff --git a/lib/Property/UtcOffset.php b/lib/Property/UtcOffset.php index 732239e23..248ed40ea 100644 --- a/lib/Property/UtcOffset.php +++ b/lib/Property/UtcOffset.php @@ -38,8 +38,6 @@ public function getValueType() * Sets the JSON value, as it would appear in a jCard or jCal object. * * The value must always be an array. - * - * @param array $value */ public function setJsonValue(array $value) { diff --git a/lib/Property/VCard/Date.php b/lib/Property/VCard/Date.php index a018ccbb8..fc679d572 100644 --- a/lib/Property/VCard/Date.php +++ b/lib/Property/VCard/Date.php @@ -28,8 +28,6 @@ public function getValueType() /** * Sets the property as a DateTime object. - * - * @param \DateTimeInterface $dt */ public function setDateTime(\DateTimeInterface $dt) { diff --git a/lib/Property/VCard/DateAndOrTime.php b/lib/Property/VCard/DateAndOrTime.php index b7e17492a..09918b31a 100644 --- a/lib/Property/VCard/DateAndOrTime.php +++ b/lib/Property/VCard/DateAndOrTime.php @@ -45,8 +45,6 @@ public function getValueType() * Sets a multi-valued property. * * You may also specify DateTimeInterface objects here. - * - * @param array $parts */ public function setParts(array $parts) { @@ -80,8 +78,6 @@ public function setValue($value) /** * Sets the property as a DateTime object. - * - * @param DateTimeInterface $dt */ public function setDateTime(DateTimeInterface $dt) { diff --git a/lib/Property/VCard/PhoneNumber.php b/lib/Property/VCard/PhoneNumber.php new file mode 100644 index 000000000..b714ffd03 --- /dev/null +++ b/lib/Property/VCard/PhoneNumber.php @@ -0,0 +1,30 @@ + + */ +class PhoneNumber extends Property\Text +{ + protected $structuredValues = []; + + /** + * Returns the type of value. + * + * This corresponds to the VALUE= parameter. Every property also has a + * 'default' valueType. + * + * @return string + */ + public function getValueType() + { + return 'PHONE-NUMBER'; + } +} diff --git a/lib/Recur/EventIterator.php b/lib/Recur/EventIterator.php index 135ecf00e..fd904b383 100644 --- a/lib/Recur/EventIterator.php +++ b/lib/Recur/EventIterator.php @@ -380,8 +380,6 @@ public function next() /** * Quickly jump to a date in the future. - * - * @param DateTimeInterface $dateTime */ public function fastForward(DateTimeInterface $dateTime) { diff --git a/lib/Recur/NoInstancesException.php b/lib/Recur/NoInstancesException.php index b55af567d..348c02306 100644 --- a/lib/Recur/NoInstancesException.php +++ b/lib/Recur/NoInstancesException.php @@ -7,7 +7,7 @@ /** * This exception gets thrown when a recurrence iterator produces 0 instances. * - * This may happen when every occurence in a rrule is also in EXDATE. + * This may happen when every occurrence in a rrule is also in EXDATE. * * @copyright Copyright (C) fruux GmbH (https://fruux.com/) * @author Evert Pot (http://evertpot.com/) diff --git a/lib/Recur/RDateIterator.php b/lib/Recur/RDateIterator.php index 013694b95..d117e152c 100644 --- a/lib/Recur/RDateIterator.php +++ b/lib/Recur/RDateIterator.php @@ -24,8 +24,7 @@ class RDateIterator implements Iterator /** * Creates the Iterator. * - * @param string|array $rrule - * @param DateTimeInterface $start + * @param string|array $rrule */ public function __construct($rrule, DateTimeInterface $start) { @@ -107,8 +106,6 @@ public function isInfinite() /** * This method allows you to quickly go to the next occurrence after the * specified date. - * - * @param DateTimeInterface $dt */ public function fastForward(DateTimeInterface $dt) { diff --git a/lib/Recur/RRuleIterator.php b/lib/Recur/RRuleIterator.php index 3ef8aec2f..c916c9c3c 100644 --- a/lib/Recur/RRuleIterator.php +++ b/lib/Recur/RRuleIterator.php @@ -26,8 +26,7 @@ class RRuleIterator implements Iterator /** * Creates the Iterator. * - * @param string|array $rrule - * @param DateTimeInterface $start + * @param string|array $rrule */ public function __construct($rrule, DateTimeInterface $start) { @@ -127,9 +126,8 @@ public function isInfinite() } /** - * This method allows you to quickly go to the next occurrence after the specified date. - * - * @param DateTimeInterface $dt + * This method allows you to quickly go to the next occurrence after the + * specified date. */ public function fastForward(DateTimeInterface $dt) { @@ -145,8 +143,6 @@ public function fastForward(DateTimeInterface $dt) /** * This method allows you to quickly go to the next occurrence before the specified date. - * - * @param DateTimeInterface $dt */ public function fastForwardBefore(DateTimeInterface $dt) { @@ -249,8 +245,6 @@ private function getFrequencyCoeff() * Perform a fast forward by doing jumps based on the distance of the requested date and the frequency of the * recurrence rule. Will set the position of the iterator to the last occurrence before the requested date. If the * fast forwarding failed, the position will be reset. - * - * @param DateTimeInterface $dt */ private function jumpForward(DateTimeInterface $dt) { @@ -386,7 +380,7 @@ private function jumpForward(DateTimeInterface $dt) * * This is an array of weekdays * - * This may also be preceeded by a positive or negative integer. If present, + * This may also be preceded by a positive or negative integer. If present, * this indicates the nth occurrence of a specific day within the monthly or * yearly rrule. For instance, -2TU indicates the second-last tuesday of * the month, or year. @@ -479,14 +473,17 @@ protected function nextDaily($amount = 1) return; } + $recurrenceHours = []; if (!empty($this->byHour)) { $recurrenceHours = $this->getHours(); } + $recurrenceDays = []; if (!empty($this->byDay)) { $recurrenceDays = $this->getDays(); } + $recurrenceMonths = []; if (!empty($this->byMonth)) { $recurrenceMonths = $this->getMonths(); } @@ -531,10 +528,12 @@ protected function nextWeekly($amount = 1) return; } + $recurrenceHours = []; if ($this->byHour) { $recurrenceHours = $this->getHours(); } + $recurrenceDays = []; if ($this->byDay) { $recurrenceDays = $this->getDays(); } @@ -598,6 +597,7 @@ protected function nextMonthly($amount = 1) return; } + $occurrence = -1; while (true) { $occurrences = $this->getMonthlyOccurrences(); @@ -789,6 +789,7 @@ protected function nextYearly($amount = 1) // If we got a byDay or getMonthDay filter, we must first expand // further. if ($this->byDay || $this->byMonthDay) { + $occurrence = -1; while (true) { // If the start date is incorrect we must directly jump to the next value if (in_array($currentMonth, $this->byMonth)) { @@ -979,7 +980,7 @@ protected function parseRRule($rrule) $this->byMonth = (array) $value; foreach ($this->byMonth as $byMonth) { if (!is_numeric($byMonth) || (int) $byMonth < 1 || (int) $byMonth > 12) { - throw new InvalidDataException('BYMONTH in RRULE must have value(s) betweeen 1 and 12!'); + throw new InvalidDataException('BYMONTH in RRULE must have value(s) between 1 and 12!'); } } break; @@ -1206,7 +1207,7 @@ protected function getDays() { $recurrenceDays = []; foreach ($this->byDay as $byDay) { - // The day may be preceeded with a positive (+n) or + // The day may be preceded with a positive (+n) or // negative (-n) integer. However, this does not make // sense in 'weekly' so we ignore it here. $recurrenceDays[] = $this->dayMap[substr($byDay, -2)]; diff --git a/lib/Settings.php b/lib/Settings.php index afc586b0c..b0bb80a82 100644 --- a/lib/Settings.php +++ b/lib/Settings.php @@ -46,7 +46,7 @@ class Settings * specific events that recur many, many times, potentially DDOSing the * server. * - * The default (3500) allows creation of a dialy event that goes on for 10 + * The default (3500) allows creation of a daily event that goes on for 10 * years, which is hopefully long enough for most. * * Set this value to -1 to disable this control altogether. diff --git a/lib/TimeZoneUtil.php b/lib/TimeZoneUtil.php index 8d8f163b0..4113eed12 100644 --- a/lib/TimeZoneUtil.php +++ b/lib/TimeZoneUtil.php @@ -139,7 +139,7 @@ public static function getTimeZone($tzid, Component $vcalendar = null, $failIfUn // PHP has a bug that logs PHP warnings even it shouldn't: // https://bugs.php.net/bug.php?id=67881 // - // That's why we're checking if we'll be able to successfull instantiate + // That's why we're checking if we'll be able to successfully instantiate // \DateTimeZone() before doing so. Otherwise we could simply instantiate // and catch the exception. $tzIdentifiers = \DateTimeZone::listIdentifiers(); diff --git a/lib/VCardConverter.php b/lib/VCardConverter.php index 156b83b4e..04932fe67 100644 --- a/lib/VCardConverter.php +++ b/lib/VCardConverter.php @@ -26,8 +26,7 @@ class VCardConverter * * If input and output version are identical, a clone is returned. * - * @param Component\VCard $input - * @param int $targetVersion + * @param int $targetVersion */ public function convert(Component\VCard $input, $targetVersion) { @@ -62,10 +61,7 @@ public function convert(Component\VCard $input, $targetVersion) /** * Handles conversion of a single property. * - * @param Component\VCard $input - * @param Component\VCard $output - * @param Property $property - * @param int $targetVersion + * @param int $targetVersion */ protected function convertProperty(Component\VCard $input, Component\VCard $output, Property $property, $targetVersion) { @@ -83,6 +79,9 @@ protected function convertProperty(Component\VCard $input, Component\VCard $outp if (!$valueType) { $valueType = $property->getValueType(); } + if (Document::VCARD30 !== $targetVersion && 'PHONE-NUMBER' === $valueType) { + $valueType = null; + } $newProperty = $output->createProperty( $property->name, $property->getParts(), @@ -227,7 +226,7 @@ protected function convertProperty(Component\VCard $input, Component\VCard $outp // Lastly, we need to see if there's a need for a VALUE parameter. // - // We can do that by instantating a empty property with that name, and + // We can do that by instantiating a empty property with that name, and // seeing if the default valueType is identical to the current one. $tempProperty = $output->createProperty($newProperty->name); if ($tempProperty->getValueType() !== $newProperty->getValueType()) { @@ -242,8 +241,7 @@ protected function convertProperty(Component\VCard $input, Component\VCard $outp * * vCard 4.0 no longer supports BINARY properties. * - * @param Component\VCard $output - * @param Property\Uri $property the input property + * @param Property\Uri $property the input property * @param $parameters list of parameters that will eventually be added to * the new property * @@ -296,8 +294,7 @@ protected function convertBinaryToUri(Component\VCard $output, Property\Binary $ * be valid in vCard 3.0 as well, we should convert those to BINARY if * possible, to improve compatibility. * - * @param Component\VCard $output - * @param Property\Uri $property the input property + * @param Property\Uri $property the input property * * @return Property\Binary|null */ @@ -344,9 +341,6 @@ protected function convertUriToBinary(Component\VCard $output, Property\Uri $new /** * Adds parameters to a new property for vCard 4.0. - * - * @param Property $newProperty - * @param array $parameters */ protected function convertParameters40(Property $newProperty, array $parameters) { @@ -383,9 +377,6 @@ protected function convertParameters40(Property $newProperty, array $parameters) /** * Adds parameters to a new property for vCard 3.0. - * - * @param Property $newProperty - * @param array $parameters */ protected function convertParameters30(Property $newProperty, array $parameters) { diff --git a/lib/Version.php b/lib/Version.php index e040dd4d9..83c82a573 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -14,5 +14,5 @@ class Version /** * Full version number. */ - const VERSION = '4.2.2'; + const VERSION = '4.3.4'; } diff --git a/lib/Writer.php b/lib/Writer.php index c70a6ae4d..cbd22022e 100644 --- a/lib/Writer.php +++ b/lib/Writer.php @@ -19,8 +19,6 @@ class Writer /** * Serializes a vCard or iCalendar object. * - * @param Component $component - * * @return string */ public static function write(Component $component) @@ -31,8 +29,7 @@ public static function write(Component $component) /** * Serializes a jCal or jCard object. * - * @param Component $component - * @param int $options + * @param int $options * * @return string */ @@ -44,8 +41,6 @@ public static function writeJson(Component $component, $options = 0) /** * Serializes a xCal or xCard object. * - * @param Component $component - * * @return string */ public static function writeXml(Component $component) diff --git a/phpstan.neon b/phpstan.neon index 5335bc651..c705178c9 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,2 +1,4 @@ parameters: - level: 0 + level: 1 + universalObjectCratesClasses: + - \Sabre\VObject\Component diff --git a/tests/VObject/BirthdayCalendarGeneratorTest.php b/tests/VObject/BirthdayCalendarGeneratorTest.php index 6e4f89a4c..d27362837 100644 --- a/tests/VObject/BirthdayCalendarGeneratorTest.php +++ b/tests/VObject/BirthdayCalendarGeneratorTest.php @@ -458,11 +458,9 @@ public function testVcardStringWithEmptyBirthdayProperty() ); } - /** - * @expectedException \Sabre\VObject\ParseException - */ public function testParseException() { + $this->expectException(ParseException::class); $generator = new BirthdayCalendarGenerator(); $input = <<setObjects($input); } - /** - * @expectedException \InvalidArgumentException - */ public function testInvalidArgumentException() { + $this->expectException(\InvalidArgumentException::class); $generator = new BirthdayCalendarGenerator(); $input = <<setObjects($input); } - /** - * @expectedException \InvalidArgumentException - */ public function testInvalidArgumentExceptionForPartiallyInvalidArray() { + $this->expectException(\InvalidArgumentException::class); $generator = new BirthdayCalendarGenerator(); $input = []; diff --git a/tests/VObject/CliTest.php b/tests/VObject/CliTest.php index 11c969c9d..a4124b76b 100644 --- a/tests/VObject/CliTest.php +++ b/tests/VObject/CliTest.php @@ -11,8 +11,17 @@ */ class CliTest extends TestCase { - public function setUp() + /** @var CliMock */ + private $cli; + + private $sabreTempDir = __DIR__.'/../temp/'; + + public function setUp(): void { + if (!file_exists($this->sabreTempDir)) { + mkdir($this->sabreTempDir); + } + $this->cli = new CliMock(); $this->cli->stderr = fopen('php://memory', 'r+'); $this->cli->stdout = fopen('php://memory', 'r+'); @@ -266,7 +275,7 @@ public function testConvertMimeDir() public function testConvertDefaultFormats() { - $outputFile = SABRE_TEMPDIR.'bar.json'; + $outputFile = $this->sabreTempDir.'bar.json'; $this->assertEquals( 2, @@ -279,7 +288,7 @@ public function testConvertDefaultFormats() public function testConvertDefaultFormats2() { - $outputFile = SABRE_TEMPDIR.'bar.ics'; + $outputFile = $this->sabreTempDir.'bar.ics'; $this->assertEquals( 2, @@ -474,7 +483,15 @@ public function testRepair() ); rewind($this->cli->stdout); - $this->assertRegExp("/^BEGIN:VCARD\r\nVERSION:2.1\r\nUID:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\r\nEND:VCARD\r\n$/", stream_get_contents($this->cli->stdout)); + $regularExpression = "/^BEGIN:VCARD\r\nVERSION:2.1\r\nUID:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\r\nEND:VCARD\r\n$/"; + $data = stream_get_contents($this->cli->stdout); + // ToDo: when we do not need to run phpunit 7 or 8, remove this 'if' block and just use + // the new assertMatchesRegularExpression that exists since phpunit 9. + if (method_exists($this, 'assertMatchesRegularExpression')) { + $this->assertMatchesRegularExpression($regularExpression, $data); + } else { + $this->assertRegExp($regularExpression, $data); + } } public function testRepairNothing() diff --git a/tests/VObject/Component/AvailableTest.php b/tests/VObject/Component/AvailableTest.php index 55292424e..bf0a6716f 100644 --- a/tests/VObject/Component/AvailableTest.php +++ b/tests/VObject/Component/AvailableTest.php @@ -22,7 +22,7 @@ public function testAvailableComponent() END:VCALENDAR VCAL; $document = Reader::read($vcal); - $this->assertInstanceOf(__NAMESPACE__.'\Available', $document->AVAILABLE); + $this->assertInstanceOf(Available::class, $document->AVAILABLE); } public function testGetEffectiveStartEnd() diff --git a/tests/VObject/Component/VAlarmTest.php b/tests/VObject/Component/VAlarmTest.php index 1e7a55ad7..2823d16da 100644 --- a/tests/VObject/Component/VAlarmTest.php +++ b/tests/VObject/Component/VAlarmTest.php @@ -4,6 +4,7 @@ use DateTime; use PHPUnit\Framework\TestCase; +use Sabre\VObject\InvalidDataException; use Sabre\VObject\Reader; class VAlarmTest extends TestCase @@ -126,11 +127,9 @@ public function timeRangeTestData() return $tests; } - /** - * @expectedException \Sabre\VObject\InvalidDataException - */ public function testInTimeRangeInvalidComponent() { + $this->expectException(InvalidDataException::class); $calendar = new VCalendar(); $valarm = $calendar->createComponent('VALARM'); $valarm->TRIGGER = '-P1D'; diff --git a/tests/VObject/Component/VAvailabilityTest.php b/tests/VObject/Component/VAvailabilityTest.php index b6b9a2b80..2fd9c0dde 100644 --- a/tests/VObject/Component/VAvailabilityTest.php +++ b/tests/VObject/Component/VAvailabilityTest.php @@ -24,7 +24,7 @@ public function testVAvailabilityComponent() VCAL; $document = Reader::read($vcal); - $this->assertInstanceOf(__NAMESPACE__.'\VAvailability', $document->VAVAILABILITY); + $this->assertInstanceOf(VAvailability::class, $document->VAVAILABILITY); } public function testGetEffectiveStartEnd() @@ -236,7 +236,7 @@ public function testAvailableSubComponent() VCAL; $document = Reader::read($vcal); - $this->assertInstanceOf(__NAMESPACE__, $document->VAVAILABILITY->AVAILABLE); + $this->assertInstanceOf(Available::class, $document->VAVAILABILITY->AVAILABLE); } public function testRFCxxxSection3_1_availableprop_required() diff --git a/tests/VObject/Component/VCalendarTest.php b/tests/VObject/Component/VCalendarTest.php index dbf2fef0a..c2f0ce978 100644 --- a/tests/VObject/Component/VCalendarTest.php +++ b/tests/VObject/Component/VCalendarTest.php @@ -5,6 +5,7 @@ use DateTimeZone; use PHPUnit\Framework\TestCase; use Sabre\VObject; +use Sabre\VObject\InvalidDataException; class VCalendarTest extends TestCase { @@ -330,11 +331,9 @@ public function expandData() return $tests; } - /** - * @expectedException \Sabre\VObject\InvalidDataException - */ public function testBrokenEventExpand() { + $this->expectException(InvalidDataException::class); $input = 'BEGIN:VCALENDAR CALSCALE:GREGORIAN VERSION:2.0 diff --git a/tests/VObject/Component/VCardTest.php b/tests/VObject/Component/VCardTest.php index 1895ce6c5..3124fec84 100644 --- a/tests/VObject/Component/VCardTest.php +++ b/tests/VObject/Component/VCardTest.php @@ -204,7 +204,7 @@ public function testNoUIDCardDAV() VCF; $this->assertValidate( $vcard, - VCARD::PROFILE_CARDDAV, + VCard::PROFILE_CARDDAV, 3, 'vCards on CardDAV servers MUST have a UID property.' ); @@ -236,7 +236,7 @@ public function testNoUIDNoCardDAVRepair() VCF; $this->assertValidate( $vcard, - VCARD::REPAIR, + VCard::REPAIR, 1, 'Adding a UID to a vCard property is recommended.' ); @@ -253,7 +253,7 @@ public function testVCard21CardDAV() VCF; $this->assertValidate( $vcard, - VCARD::PROFILE_CARDDAV, + VCard::PROFILE_CARDDAV, 3, 'CardDAV servers are not allowed to accept vCard 2.1.' ); diff --git a/tests/VObject/ComponentTest.php b/tests/VObject/ComponentTest.php index 8c0a0d7c6..f56d55531 100644 --- a/tests/VObject/ComponentTest.php +++ b/tests/VObject/ComponentTest.php @@ -21,10 +21,13 @@ public function testIterate() $count = 0; foreach ($comp->children() as $key => $subcomponent) { ++$count; - $this->assertInstanceOf('Sabre\\VObject\\Component', $subcomponent); + $this->assertInstanceOf(Component::class, $subcomponent); + + if (2 === $count) { + $this->assertEquals(1, $key); + } } $this->assertEquals(2, $count); - $this->assertEquals(1, $key); } public function testMagicGet() @@ -38,10 +41,10 @@ public function testMagicGet() $comp->add($sub); $event = $comp->vevent; - $this->assertInstanceOf('Sabre\\VObject\\Component', $event); + $this->assertInstanceOf(Component::class, $event); $this->assertEquals('VEVENT', $event->name); - $this->assertInternalType('null', $comp->vjournal); + $this->assertNull($comp->vjournal); } public function testMagicGetGroups() @@ -89,7 +92,7 @@ public function testMagicSetScalar() $comp = new VCalendar(); $comp->myProp = 'myValue'; - $this->assertInstanceOf('Sabre\\VObject\\Property', $comp->MYPROP); + $this->assertInstanceOf(Property::class, $comp->MYPROP); $this->assertEquals('myValue', (string) $comp->MYPROP); } @@ -100,7 +103,7 @@ public function testMagicSetScalarTwice() $comp->myProp = 'myValue'; $this->assertEquals(1, count($comp->children())); - $this->assertInstanceOf('Sabre\\VObject\\Property', $comp->MYPROP); + $this->assertInstanceOf(Property::class, $comp->MYPROP); $this->assertEquals('myValue', (string) $comp->MYPROP); } @@ -109,7 +112,7 @@ public function testMagicSetArray() $comp = new VCalendar(); $comp->ORG = ['Acme Inc', 'Section 9']; - $this->assertInstanceOf('Sabre\\VObject\\Property', $comp->ORG); + $this->assertInstanceOf(Property::class, $comp->ORG); $this->assertEquals(['Acme Inc', 'Section 9'], $comp->ORG->getParts()); } @@ -174,20 +177,16 @@ public function testArrayAccessExists() $this->assertTrue(isset($comp->vevent[1])); } - /** - * @expectedException \LogicException - */ public function testArrayAccessSet() { + $this->expectException(\LogicException::class); $comp = new VCalendar(); $comp['hey'] = 'hi there'; } - /** - * @expectedException \LogicException - */ public function testArrayAccessUnset() { + $this->expectException(\LogicException::class); $comp = new VCalendar(); unset($comp[0]); } @@ -217,7 +216,7 @@ public function testAddScalarParams() $bla = $comp->children()[0]; - $this->assertInstanceOf('Sabre\\VObject\\Property', $bla); + $this->assertInstanceOf(Property::class, $bla); $this->assertEquals('MYPROP', $bla->name); $this->assertEquals('value', (string) $bla); @@ -250,20 +249,16 @@ public function testAddComponentTwice() $this->assertEquals('VEVENT', $comp->VEVENT->name); } - /** - * @expectedException \InvalidArgumentException - */ public function testAddArgFail() { + $this->expectException(\InvalidArgumentException::class); $comp = new VCalendar(); $comp->add($comp->createComponent('VEVENT'), 'hello'); } - /** - * @expectedException \InvalidArgumentException - */ public function testAddArgFail2() { + $this->expectException(\InvalidArgumentException::class); $comp = new VCalendar(); $comp->add([]); } @@ -293,7 +288,7 @@ public function testChildren() $comp->add($comp->createComponent('VTODO')); $r = $comp->children(); - $this->assertInternalType('array', $r); + $this->assertIsArray($r); $this->assertEquals(2, count($r)); } @@ -305,7 +300,7 @@ public function testGetComponents() $comp->add($comp->createComponent('VTODO')); $r = $comp->getComponents(); - $this->assertInternalType('array', $r); + $this->assertIsArray($r); $this->assertEquals(1, count($r)); $this->assertEquals('VTODO', $r[0]->name); } @@ -414,11 +409,9 @@ public function testRemoveByObj() $this->assertTrue(isset($comp->prop1)); } - /** - * @expectedException \InvalidArgumentException - */ public function testRemoveNotFound() { + $this->expectException(\InvalidArgumentException::class); $comp = new VCalendar([], false); $prop = $comp->createProperty('A', 'B'); $comp->remove($prop); diff --git a/tests/VObject/DateTimeParserTest.php b/tests/VObject/DateTimeParserTest.php index 44fba80c5..b20a43217 100644 --- a/tests/VObject/DateTimeParserTest.php +++ b/tests/VObject/DateTimeParserTest.php @@ -31,11 +31,9 @@ public function testParseICalendarDurationDateInterval() $this->assertEquals($expected, DateTimeParser::parseDuration('-PT3M')); } - /** - * @expectedException \Sabre\VObject\InvalidDataException - */ public function testParseICalendarDurationFail() { + $this->expectException(InvalidDataException::class); DateTimeParser::parseDuration('P1X', true); } @@ -50,19 +48,19 @@ public function testParseICalendarDateTime() /** * @depends testParseICalendarDateTime - * @expectedException \Sabre\VObject\InvalidDataException */ public function testParseICalendarDateTimeBadFormat() { + $this->expectException(InvalidDataException::class); $dateTime = DateTimeParser::parseDateTime('20100316T141405 '); } /** * @depends testParseICalendarDateTime - * @expectedException \Sabre\VObject\InvalidDataException */ public function testParseICalendarDateTimeInvalidTime() { + $this->expectException(InvalidDataException::class); $dateTime = DateTimeParser::parseDateTime('20100316T251405'); } @@ -143,19 +141,19 @@ public function testParseICalendarDateTimeGreaterThan4000() /** * @depends testParseICalendarDate - * @expectedException \Sabre\VObject\InvalidDataException */ public function testParseICalendarDateBadFormat() { + $this->expectException(InvalidDataException::class); $dateTime = DateTimeParser::parseDate('20100316T141405'); } /** * @depends testParseICalendarDate - * @expectedException \Sabre\VObject\InvalidDataException */ public function testParseICalendarDateInvalidDate() { + $this->expectException(InvalidDataException::class); $dateTime = DateTimeParser::parseDate('20101331'); } @@ -170,19 +168,15 @@ public function testVCardDate($input, $output) ); } - /** - * @expectedException \Sabre\VObject\InvalidDataException - */ public function testBadVCardDate() { + $this->expectException(InvalidDataException::class); DateTimeParser::parseVCardDateTime('1985---01'); } - /** - * @expectedException \Sabre\VObject\InvalidDataException - */ public function testBadVCardTime() { + $this->expectException(InvalidDataException::class); DateTimeParser::parseVCardTime('23:12:166'); } diff --git a/tests/VObject/DocumentTest.php b/tests/VObject/DocumentTest.php index 2665406f6..f2698f65f 100644 --- a/tests/VObject/DocumentTest.php +++ b/tests/VObject/DocumentTest.php @@ -24,11 +24,11 @@ public function testCreateComponent() $event = $vcal->createComponent('VEVENT'); - $this->assertInstanceOf('Sabre\VObject\Component\VEvent', $event); + $this->assertInstanceOf(Component\VEvent::class, $event); $vcal->add($event); $prop = $vcal->createProperty('X-PROP', '1234256', ['X-PARAM' => '3']); - $this->assertInstanceOf('Sabre\VObject\Property', $prop); + $this->assertInstanceOf(Property::class, $prop); $event->add($prop); @@ -46,16 +46,16 @@ public function testCreate() $vcal = new Component\VCalendar([], false); $event = $vcal->create('VEVENT'); - $this->assertInstanceOf('Sabre\VObject\Component\VEvent', $event); + $this->assertInstanceOf(Component\VEvent::class, $event); $prop = $vcal->create('CALSCALE'); - $this->assertInstanceOf('Sabre\VObject\Property\Text', $prop); + $this->assertInstanceOf(Property\Text::class, $prop); } public function testGetClassNameForPropertyValue() { $vcal = new Component\VCalendar([], false); - $this->assertEquals('Sabre\\VObject\\Property\\Text', $vcal->getClassNameForPropertyValue('TEXT')); + $this->assertEquals(Property\Text::class, $vcal->getClassNameForPropertyValue('TEXT')); $this->assertNull($vcal->getClassNameForPropertyValue('FOO')); } @@ -64,7 +64,7 @@ public function testDestroy() $vcal = new Component\VCalendar([], false); $event = $vcal->createComponent('VEVENT'); - $this->assertInstanceOf('Sabre\VObject\Component\VEvent', $event); + $this->assertInstanceOf(Component\VEvent::class, $event); $vcal->add($event); $prop = $vcal->createProperty('X-PROP', '1234256', ['X-PARAM' => '3']); diff --git a/tests/VObject/ElementListTest.php b/tests/VObject/ElementListTest.php index 1842ca963..f3bb8f2bb 100644 --- a/tests/VObject/ElementListTest.php +++ b/tests/VObject/ElementListTest.php @@ -22,9 +22,12 @@ public function testIterate() $count = 0; foreach ($elemList as $key => $subcomponent) { ++$count; - $this->assertInstanceOf('Sabre\\VObject\\Component', $subcomponent); + $this->assertInstanceOf(Component::class, $subcomponent); + + if (3 === $count) { + $this->assertEquals(2, $key); + } } $this->assertEquals(3, $count); - $this->assertEquals(2, $key); } } diff --git a/tests/VObject/EmptyParameterTest.php b/tests/VObject/EmptyParameterTest.php index 213e69ab8..52fe878e2 100644 --- a/tests/VObject/EmptyParameterTest.php +++ b/tests/VObject/EmptyParameterTest.php @@ -20,7 +20,7 @@ public function testRead() $vcard = Reader::read($input); - $this->assertInstanceOf('Sabre\\VObject\\Component\\VCard', $vcard); + $this->assertInstanceOf(Component\VCard::class, $vcard); $vcard = $vcard->convert(\Sabre\VObject\Document::VCARD30); $vcard = $vcard->serialize(); diff --git a/tests/VObject/FreeBusyGeneratorTest.php b/tests/VObject/FreeBusyGeneratorTest.php index 323cf632b..4700a2800 100644 --- a/tests/VObject/FreeBusyGeneratorTest.php +++ b/tests/VObject/FreeBusyGeneratorTest.php @@ -22,15 +22,13 @@ public function testGeneratorBaseObject() $this->assertEquals('PUBLISH', $result->METHOD->getValue()); } - /** - * @expectedException \InvalidArgumentException - */ public function testInvalidArg() { + $this->expectException(\InvalidArgumentException::class); $gen = new FreeBusyGenerator( new \DateTime('2012-01-01'), new \DateTime('2012-12-31'), - new \StdClass() + new \stdClass() ); } diff --git a/tests/VObject/ICalendar/AttachParseTest.php b/tests/VObject/ICalendar/AttachParseTest.php index a32a2462e..e6e3d8685 100644 --- a/tests/VObject/ICalendar/AttachParseTest.php +++ b/tests/VObject/ICalendar/AttachParseTest.php @@ -3,6 +3,7 @@ namespace Sabre\VObject\ICalendar; use PHPUnit\Framework\TestCase; +use Sabre\VObject\Property\Uri; use Sabre\VObject\Reader; class AttachParseTest extends TestCase @@ -23,7 +24,7 @@ public function testParseAttach() $vcal = Reader::read($vcal); $prop = $vcal->VEVENT->ATTACH; - $this->assertInstanceOf('Sabre\\VObject\\Property\\URI', $prop); + $this->assertInstanceOf(Uri::class, $prop); $this->assertEquals('ftp://example.com/pub/reports/r-960812.ps', $prop->getValue()); } } diff --git a/tests/VObject/ITip/BrokerAttendeeReplyTest.php b/tests/VObject/ITip/BrokerAttendeeReplyTest.php index cd0c7bb66..71008c6ae 100644 --- a/tests/VObject/ITip/BrokerAttendeeReplyTest.php +++ b/tests/VObject/ITip/BrokerAttendeeReplyTest.php @@ -68,6 +68,124 @@ public function testAccepted() $this->parse($oldMessage, $newMessage, $expected); } + public function testAcceptedWithTz() + { + $oldMessage = << 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => 'One', + 'recipient' => 'mailto:strunk@example.org', + 'recipientName' => 'Strunk', + 'message' => <<parse($oldMessage, $newMessage, $expected); + } + public function testRecurringReply() { $oldMessage = <<parse(null, $message, $expected, 'mailto:strunk@example.org'); } - /** - * @expectedException \Sabre\VObject\ITip\ITipException - */ public function testBrokenEventUIDMisMatch() { + $this->expectException(ITipException::class); $message = <<parse(null, $message, [], 'mailto:strunk@example.org'); } - /** - * @expectedException \Sabre\VObject\ITip\ITipException - */ public function testBrokenEventOrganizerMisMatch() { + $this->expectException(ITipException::class); $message = <<parse(null, $message, [], 'mailto:strunk@example.org'); } - /** - * @expectedException \Sabre\VObject\ITip\ITipException - */ public function testMultipleUID() { + $this->expectException(ITipException::class); $message = <<parse(null, $message, [], 'mailto:strunk@example.org'); } - /** - * @expectedException \Sabre\VObject\ITip\SameOrganizerForAllComponentsException - */ public function testChangingOrganizers() { + $this->expectException(SameOrganizerForAllComponentsException::class); $message = <<process($itip, $old, $expected); } + public function testReplyWithTz() + { + $itip = <<process($itip, $old, $expected); + } + public function testReplyRequestStatus() { $itip = <<getComponents() as $mainComponent) { - break; + if ('VEVENT' === $mainComponent->name) { + break; + } } $message = new Message(); diff --git a/tests/VObject/Issue36WorkAroundTest.php b/tests/VObject/Issue36WorkAroundTest.php index 332aace39..1afd3d184 100644 --- a/tests/VObject/Issue36WorkAroundTest.php +++ b/tests/VObject/Issue36WorkAroundTest.php @@ -34,6 +34,6 @@ public function testWorkaround() // If this does not throw an exception, it's all good. $it = new Recur\EventIterator($obj, '1833bd44-188b-405c-9f85-1a12105318aa'); - $this->assertInstanceOf('Sabre\\VObject\\Recur\\EventIterator', $it); + $this->assertInstanceOf(Recur\EventIterator::class, $it); } } diff --git a/tests/VObject/Issue64Test.php b/tests/VObject/Issue64Test.php index 9dc2bb984..2e623baa8 100644 --- a/tests/VObject/Issue64Test.php +++ b/tests/VObject/Issue64Test.php @@ -14,6 +14,6 @@ public function testRead() $converted = Reader::read($vcard); - $this->assertInstanceOf('Sabre\\VObject\\Component\\VCard', $converted); + $this->assertInstanceOf(Component\VCard::class, $converted); } } diff --git a/tests/VObject/Issue96Test.php b/tests/VObject/Issue96Test.php index 22d1fed2f..88803a3e0 100644 --- a/tests/VObject/Issue96Test.php +++ b/tests/VObject/Issue96Test.php @@ -18,7 +18,7 @@ public function testRead() VCF; $vcard = Reader::read($input, Reader::OPTION_FORGIVING); - $this->assertInstanceOf('Sabre\\VObject\\Component\\VCard', $vcard); + $this->assertInstanceOf(Component\VCard::class, $vcard); $this->assertEquals('http://www.example.org', $vcard->URL->getValue()); } } diff --git a/tests/VObject/IssueUndefinedIndexTest.php b/tests/VObject/IssueUndefinedIndexTest.php index 5a70b0885..7ed214a19 100644 --- a/tests/VObject/IssueUndefinedIndexTest.php +++ b/tests/VObject/IssueUndefinedIndexTest.php @@ -6,11 +6,9 @@ class IssueUndefinedIndexTest extends TestCase { - /** - * @expectedException \Sabre\VObject\ParseException - */ public function testRead() { + $this->expectException(ParseException::class); $input = << 'WEEKLY', 'byday' => ['MO', 'TU'], ], ], [ - 'x-bool', new \StdClass(), 'boolean', true, + 'x-bool', new \stdClass(), 'boolean', true, ], [ - 'x-time', new \StdClass(), 'time', '08:00:00', + 'x-time', new \stdClass(), 'time', '08:00:00', ], [ - 'attach', new \StdClass(), 'binary', base64_encode('attachment'), + 'attach', new \stdClass(), 'binary', base64_encode('attachment'), ], [ 'request-status', - new \StdClass(), + new \stdClass(), 'text', ['2.0', 'Success'], ], [ 'request-status', - new \StdClass(), + new \stdClass(), 'text', ['3.7', 'Invalid Calendar User', 'ATTENDEE:mailto:jsmith@example.org'], ], [ 'dtend', - new \StdClass(), + new \stdClass(), 'date-time', '2015-01-08T13:30:00', ], diff --git a/tests/VObject/JCardTest.php b/tests/VObject/JCardTest.php index 2100a07a9..1864f666d 100644 --- a/tests/VObject/JCardTest.php +++ b/tests/VObject/JCardTest.php @@ -48,25 +48,25 @@ public function testToJCard() [ [ 'version', - new \StdClass(), + new \stdClass(), 'text', '4.0', ], [ 'prodid', - new \StdClass(), + new \stdClass(), 'text', '-//Sabre//Sabre VObject '.Version::VERSION.'//EN', ], [ 'uid', - new \StdClass(), + new \stdClass(), 'text', 'foo', ], [ 'bday', - new \StdClass(), + new \stdClass(), 'date-and-or-time', '1985-04-07', ], @@ -80,25 +80,25 @@ public function testToJCard() ], [ 'bday', - new \StdClass(), + new \stdClass(), 'date-time', '1979-12-25T02:00:00', ], [ 'rev', - new \StdClass(), + new \stdClass(), 'timestamp', '1995-10-31T22:27:10Z', ], [ 'lang', - new \StdClass(), + new \stdClass(), 'language-tag', 'nl', ], [ 'n', - new \StdClass(), + new \stdClass(), 'text', ['Last', 'First', 'Middle', '', ''], ], @@ -120,7 +120,7 @@ public function testToJCard() ], [ 'adr', - new \StdClass(), + new \stdClass(), 'text', [ '', @@ -134,55 +134,55 @@ public function testToJCard() ], [ 'x-truncated', - new \StdClass(), + new \stdClass(), 'date', '--12-25', ], [ 'x-time-local', - new \StdClass(), + new \stdClass(), 'time', '12:30:00', ], [ 'x-time-utc', - new \StdClass(), + new \stdClass(), 'time', '12:30:00Z', ], [ 'x-time-offset', - new \StdClass(), + new \stdClass(), 'time', '12:30:00-08:00', ], [ 'x-time-reduced', - new \StdClass(), + new \stdClass(), 'time', '23', ], [ 'x-time-truncated', - new \StdClass(), + new \stdClass(), 'time', '--30', ], [ 'x-karma-points', - new \StdClass(), + new \stdClass(), 'integer', 42, ], [ 'x-grade', - new \StdClass(), + new \stdClass(), 'float', 1.3, ], [ 'tz', - new \StdClass(), + new \stdClass(), 'utc-offset', '-05:00', ], diff --git a/tests/VObject/Parser/JsonTest.php b/tests/VObject/Parser/JsonTest.php index 587f55f70..e1c701489 100644 --- a/tests/VObject/Parser/JsonTest.php +++ b/tests/VObject/Parser/JsonTest.php @@ -4,6 +4,7 @@ use PHPUnit\Framework\TestCase; use Sabre\VObject; +use Sabre\VObject\ParseException; class JsonTest extends TestCase { @@ -14,25 +15,25 @@ public function testRoundTripJCard() [ [ 'version', - new \StdClass(), + new \stdClass(), 'text', '4.0', ], [ 'prodid', - new \StdClass(), + new \stdClass(), 'text', '-//Sabre//Sabre VObject '.VObject\Version::VERSION.'//EN', ], [ 'uid', - new \StdClass(), + new \stdClass(), 'text', 'foo', ], [ 'bday', - new \StdClass(), + new \stdClass(), 'date-and-or-time', '1985-04-07', ], @@ -46,25 +47,25 @@ public function testRoundTripJCard() ], [ 'bday', - new \StdClass(), + new \stdClass(), 'date-time', '1979-12-25T02:00:00', ], [ 'rev', - new \StdClass(), + new \stdClass(), 'timestamp', '1995-10-31T22:27:10Z', ], [ 'lang', - new \StdClass(), + new \stdClass(), 'language-tag', 'nl', ], [ 'n', - new \StdClass(), + new \stdClass(), 'text', ['Last', 'First', 'Middle', '', ''], ], @@ -86,7 +87,7 @@ public function testRoundTripJCard() ], [ 'adr', - new \StdClass(), + new \stdClass(), 'text', [ '', @@ -101,55 +102,55 @@ public function testRoundTripJCard() [ 'x-truncated', - new \StdClass(), + new \stdClass(), 'date', '--12-25', ], [ 'x-time-local', - new \StdClass(), + new \stdClass(), 'time', '12:30:00', ], [ 'x-time-utc', - new \StdClass(), + new \stdClass(), 'time', '12:30:00Z', ], [ 'x-time-offset', - new \StdClass(), + new \stdClass(), 'time', '12:30:00-08:00', ], [ 'x-time-reduced', - new \StdClass(), + new \stdClass(), 'time', '23', ], [ 'x-time-truncated', - new \StdClass(), + new \stdClass(), 'time', '--30', ], [ 'x-karma-points', - new \StdClass(), + new \stdClass(), 'integer', 42, ], [ 'x-grade', - new \StdClass(), + new \stdClass(), 'float', 1.3, ], [ 'tz', - new \StdClass(), + new \stdClass(), 'utc-offset', '-05:00', ], @@ -203,19 +204,19 @@ public function testRoundTripJCal() [ [ 'version', - new \StdClass(), + new \stdClass(), 'text', '2.0', ], [ 'prodid', - new \StdClass(), + new \stdClass(), 'text', '-//Sabre//Sabre VObject '.VObject\Version::VERSION.'//EN', ], [ 'calscale', - new \StdClass(), + new \stdClass(), 'text', 'GREGORIAN', ], @@ -224,25 +225,25 @@ public function testRoundTripJCal() ['vevent', [ [ - 'uid', new \StdClass(), 'text', 'foo', + 'uid', new \stdClass(), 'text', 'foo', ], [ - 'dtstart', new \StdClass(), 'date', '2013-05-26', + 'dtstart', new \stdClass(), 'date', '2013-05-26', ], [ - 'duration', new \StdClass(), 'duration', 'P1D', + 'duration', new \stdClass(), 'duration', 'P1D', ], [ - 'categories', new \StdClass(), 'text', 'home', 'testing', + 'categories', new \stdClass(), 'text', 'home', 'testing', ], [ - 'created', new \StdClass(), 'date-time', '2013-05-26T18:10:00Z', + 'created', new \stdClass(), 'date-time', '2013-05-26T18:10:00Z', ], [ - 'attach', new \StdClass(), 'binary', base64_encode('attachment'), + 'attach', new \stdClass(), 'binary', base64_encode('attachment'), ], [ - 'attendee', new \StdClass(), 'cal-address', 'mailto:armin@example.org', + 'attendee', new \stdClass(), 'cal-address', 'mailto:armin@example.org', ], [ 'attendee', @@ -254,41 +255,41 @@ public function testRoundTripJCal() 'mailto:dominik@example.org', ], [ - 'geo', new \StdClass(), 'float', [51.96668, 7.61876], + 'geo', new \stdClass(), 'float', [51.96668, 7.61876], ], [ - 'sequence', new \StdClass(), 'integer', 5, + 'sequence', new \stdClass(), 'integer', 5, ], [ - 'freebusy', new \StdClass(), 'period', ['2013-05-26T21:02:13', 'PT1H'], ['2013-06-26T12:00:00', '2013-06-26T13:00:00'], + 'freebusy', new \stdClass(), 'period', ['2013-05-26T21:02:13', 'PT1H'], ['2013-06-26T12:00:00', '2013-06-26T13:00:00'], ], [ - 'url', new \StdClass(), 'uri', 'http://example.org/', + 'url', new \stdClass(), 'uri', 'http://example.org/', ], [ - 'tzoffsetfrom', new \StdClass(), 'utc-offset', '+05:00', + 'tzoffsetfrom', new \stdClass(), 'utc-offset', '+05:00', ], [ - 'rrule', new \StdClass(), 'recur', [ + 'rrule', new \stdClass(), 'recur', [ 'freq' => 'WEEKLY', 'byday' => ['MO', 'TU'], ], ], [ - 'x-bool', new \StdClass(), 'boolean', true, + 'x-bool', new \stdClass(), 'boolean', true, ], [ - 'x-time', new \StdClass(), 'time', '08:00:00', + 'x-time', new \stdClass(), 'time', '08:00:00', ], [ 'request-status', - new \StdClass(), + new \stdClass(), 'text', ['2.0', 'Success'], ], [ 'request-status', - new \StdClass(), + new \stdClass(), 'text', ['3.7', 'Invalid Calendar User', 'ATTENDEE:mailto:jsmith@example.org'], ], @@ -297,7 +298,7 @@ public function testRoundTripJCal() ['valarm', [ [ - 'action', new \StdClass(), 'text', 'DISPLAY', + 'action', new \stdClass(), 'text', 'DISPLAY', ], ], [], @@ -358,7 +359,7 @@ public function testParseStreamArg() 'vcard', [ [ - 'FN', new \StdClass(), 'text', 'foo', + 'FN', new \stdClass(), 'text', 'foo', ], ], ]; @@ -371,17 +372,15 @@ public function testParseStreamArg() $this->assertEquals('foo', $result->FN->getValue()); } - /** - * @expectedException \Sabre\VObject\ParseException - */ public function testParseInvalidData() { + $this->expectException(ParseException::class); $json = new Json(); $input = [ 'vlist', [ [ - 'FN', new \StdClass(), 'text', 'foo', + 'FN', new \stdClass(), 'text', 'foo', ], ], ]; diff --git a/tests/VObject/Parser/MimeDirTest.php b/tests/VObject/Parser/MimeDirTest.php index 671c23064..183c9ce4c 100644 --- a/tests/VObject/Parser/MimeDirTest.php +++ b/tests/VObject/Parser/MimeDirTest.php @@ -3,6 +3,7 @@ namespace Sabre\VObject\Parser; use PHPUnit\Framework\TestCase; +use Sabre\VObject\ParseException; /** * Note that most MimeDir related tests can actually be found in the ReaderTest @@ -10,13 +11,11 @@ */ class MimeDirTest extends TestCase { - /** - * @expectedException \Sabre\VObject\ParseException - */ public function testParseError() { + $this->expectException(ParseException::class); $mimeDir = new MimeDir(); - $mimeDir->parse(fopen(__FILE__, 'a')); + $mimeDir->parse(fopen(__FILE__, 'a+')); } public function testDecodeLatin1() @@ -80,20 +79,16 @@ public function testDontDecodeLatin1() $this->assertEquals("umlaut u - \xFC", $vcard->FN->getValue()); } - /** - * @expectedException \InvalidArgumentException - */ public function testDecodeUnsupportedCharset() { + $this->expectException(\InvalidArgumentException::class); $mimeDir = new MimeDir(); $mimeDir->setCharset('foobar'); } - /** - * @expectedException \Sabre\VObject\ParseException - */ public function testDecodeUnsupportedInlineCharset() { + $this->expectException(ParseException::class); $vcard = <<assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertInstanceOf(Component::class, $result); $this->assertEquals('VCARD', $result->name); $this->assertEquals(1, count($result->children())); $this->assertEquals('Aachen', $this->getPropertyValue($result->LABEL)); @@ -24,7 +25,7 @@ public function testReadQuotedPrintableNewlineSoft() $data = "BEGIN:VCARD\r\nLABEL;ENCODING=QUOTED-PRINTABLE:Aa=\r\n ch=\r\n en\r\nEND:VCARD"; $result = Reader::read($data); - $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertInstanceOf(Component::class, $result); $this->assertEquals('VCARD', $result->name); $this->assertEquals(1, count($result->children())); $this->assertEquals('Aachen', $this->getPropertyValue($result->LABEL)); @@ -35,7 +36,7 @@ public function testReadQuotedPrintableNewlineHard() $data = "BEGIN:VCARD\r\nLABEL;ENCODING=QUOTED-PRINTABLE:Aachen=0D=0A=\r\n Germany\r\nEND:VCARD"; $result = Reader::read($data); - $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertInstanceOf(Component::class, $result); $this->assertEquals('VCARD', $result->name); $this->assertEquals(1, count($result->children())); $this->assertEquals("Aachen\r\nGermany", $this->getPropertyValue($result->LABEL)); @@ -46,7 +47,7 @@ public function testReadQuotedPrintableCompatibilityMS() $data = "BEGIN:VCARD\r\nLABEL;ENCODING=QUOTED-PRINTABLE:Aachen=0D=0A=\r\nDeutschland:okay\r\nEND:VCARD"; $result = Reader::read($data, Reader::OPTION_FORGIVING); - $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertInstanceOf(Component::class, $result); $this->assertEquals('VCARD', $result->name); $this->assertEquals(1, count($result->children())); $this->assertEquals("Aachen\r\nDeutschland:okay", $this->getPropertyValue($result->LABEL)); diff --git a/tests/VObject/Property/BinaryTest.php b/tests/VObject/Property/BinaryTest.php index a6ea701ba..b1c2dada7 100644 --- a/tests/VObject/Property/BinaryTest.php +++ b/tests/VObject/Property/BinaryTest.php @@ -7,11 +7,9 @@ class BinaryTest extends TestCase { - /** - * @expectedException \InvalidArgumentException - */ public function testMimeDir() { + $this->expectException(\InvalidArgumentException::class); $vcard = new VObject\Component\VCard(['VERSION' => '3.0']); $vcard->add('PHOTO', ['a', 'b']); } diff --git a/tests/VObject/Property/FloatTest.php b/tests/VObject/Property/FloatTest.php index c5237c47b..0ba02470e 100644 --- a/tests/VObject/Property/FloatTest.php +++ b/tests/VObject/Property/FloatTest.php @@ -14,7 +14,7 @@ public function testMimeDir() $result = $mimeDir->parse($input); - $this->assertInstanceOf('Sabre\VObject\Property\FloatValue', $result->{'X-FLOAT'}); + $this->assertInstanceOf(FloatValue::class, $result->{'X-FLOAT'}); $this->assertEquals([ 0.234, diff --git a/tests/VObject/Property/ICalendar/DateTimeTest.php b/tests/VObject/Property/ICalendar/DateTimeTest.php index 33525ff33..40c6e2ef6 100644 --- a/tests/VObject/Property/ICalendar/DateTimeTest.php +++ b/tests/VObject/Property/ICalendar/DateTimeTest.php @@ -4,12 +4,13 @@ use PHPUnit\Framework\TestCase; use Sabre\VObject\Component\VCalendar; +use Sabre\VObject\InvalidDataException; class DateTimeTest extends TestCase { protected $vcal; - public function setUp() + public function setUp(): void { $this->vcal = new VCalendar(); } @@ -256,11 +257,9 @@ public function testGetDateTimeDateLOCALTZ() $this->assertEquals('Europe/Amsterdam', $dt->getTimeZone()->getName()); } - /** - * @expectedException \Sabre\VObject\InvalidDataException - */ public function testGetDateTimeDateInvalid() { + $this->expectException(InvalidDataException::class); $elem = $this->vcal->createProperty('DTSTART', 'bla'); $dt = $elem->getDateTime(); } diff --git a/tests/VObject/Property/ICalendar/RecurTest.php b/tests/VObject/Property/ICalendar/RecurTest.php index 818840605..3902a6e13 100644 --- a/tests/VObject/Property/ICalendar/RecurTest.php +++ b/tests/VObject/Property/ICalendar/RecurTest.php @@ -16,7 +16,7 @@ public function testParts() $vcal = new VCalendar(); $recur = $vcal->add('RRULE', 'FREQ=Daily'); - $this->assertInstanceOf('Sabre\VObject\Property\ICalendar\Recur', $recur); + $this->assertInstanceOf(Recur::class, $recur); $this->assertEquals(['FREQ' => 'DAILY'], $recur->getParts()); $recur->setParts(['freq' => 'MONTHLY']); @@ -24,11 +24,9 @@ public function testParts() $this->assertEquals(['FREQ' => 'MONTHLY'], $recur->getParts()); } - /** - * @expectedException \InvalidArgumentException - */ public function testSetValueBadVal() { + $this->expectException(\InvalidArgumentException::class); $vcal = new VCalendar(); $recur = $vcal->add('RRULE', 'FREQ=Daily'); $recur->setValue(new \Exception()); diff --git a/tests/VObject/Property/UriTest.php b/tests/VObject/Property/UriTest.php index 4ab32a736..d3d0e0f9c 100644 --- a/tests/VObject/Property/UriTest.php +++ b/tests/VObject/Property/UriTest.php @@ -21,6 +21,6 @@ public function testAlwaysEncodeUriVCalendar() END:VCALENDAR ICS; $output = Reader::read($input)->serialize(); - $this->assertContains('URL;VALUE=URI:http://example.org/', $output); + $this->assertStringContainsString('URL;VALUE=URI:http://example.org/', $output); } } diff --git a/tests/VObject/Property/VCard/DateAndOrTimeTest.php b/tests/VObject/Property/VCard/DateAndOrTimeTest.php index a59411e01..f21b408a1 100644 --- a/tests/VObject/Property/VCard/DateAndOrTimeTest.php +++ b/tests/VObject/Property/VCard/DateAndOrTimeTest.php @@ -105,11 +105,9 @@ public function testSetPartsDateTimeImmutable() $this->assertEquals('20140402T183700Z', $prop->getValue()); } - /** - * @expectedException \InvalidArgumentException - */ public function testSetPartsTooMany() { + $this->expectException(\InvalidArgumentException::class); $vcard = new VObject\Component\VCard(); $prop = $vcard->createProperty('BDAY'); diff --git a/tests/VObject/Property/VCard/LanguageTagTest.php b/tests/VObject/Property/VCard/LanguageTagTest.php index ffb65f434..54106ffe8 100644 --- a/tests/VObject/Property/VCard/LanguageTagTest.php +++ b/tests/VObject/Property/VCard/LanguageTagTest.php @@ -14,7 +14,7 @@ public function testMimeDir() $result = $mimeDir->parse($input); - $this->assertInstanceOf('Sabre\VObject\Property\VCard\LanguageTag', $result->LANG); + $this->assertInstanceOf(LanguageTag::class, $result->LANG); $this->assertEquals('nl', $result->LANG->getValue()); @@ -31,7 +31,7 @@ public function testChangeAndSerialize() $result = $mimeDir->parse($input); - $this->assertInstanceOf('Sabre\VObject\Property\VCard\LanguageTag', $result->LANG); + $this->assertInstanceOf(LanguageTag::class, $result->LANG); // This replicates what the vcard converter does and triggered a bug in // the past. $result->LANG->setValue(['de']); diff --git a/tests/VObject/Property/VCard/PhoneNumberTest.php b/tests/VObject/Property/VCard/PhoneNumberTest.php new file mode 100644 index 000000000..668bc7e4c --- /dev/null +++ b/tests/VObject/Property/VCard/PhoneNumberTest.php @@ -0,0 +1,19 @@ +assertInstanceOf(PhoneNumber::class, $vCard->TEL); + $this->assertEquals('PHONE-NUMBER', $vCard->TEL->getValueType()); + $this->assertEquals($input, $vCard->serialize()); + } +} diff --git a/tests/VObject/PropertyTest.php b/tests/VObject/PropertyTest.php index 1c2dc0830..1f6e07022 100644 --- a/tests/VObject/PropertyTest.php +++ b/tests/VObject/PropertyTest.php @@ -62,7 +62,7 @@ public function testParameterGet() $property = $cal->createProperty('propname', 'propvalue'); $property['paramname'] = 'paramvalue'; - $this->assertInstanceOf('Sabre\\VObject\\Parameter', $property['paramname']); + $this->assertInstanceOf(Parameter::class, $property['paramname']); } public function testParameterNotExists() @@ -71,7 +71,7 @@ public function testParameterNotExists() $property = $cal->createProperty('propname', 'propvalue'); $property['paramname'] = 'paramvalue'; - $this->assertInternalType('null', $property['foo']); + $this->assertNull($property['foo']); } public function testParameterMultiple() @@ -81,7 +81,7 @@ public function testParameterMultiple() $property['paramname'] = 'paramvalue'; $property->add('paramname', 'paramvalue'); - $this->assertInstanceOf('Sabre\\VObject\\Parameter', $property['paramname']); + $this->assertInstanceOf(Parameter::class, $property['paramname']); $this->assertEquals(2, count($property['paramname']->getParts())); } @@ -92,7 +92,7 @@ public function testSetParameterAsString() $property['paramname'] = 'paramvalue'; $this->assertEquals(1, count($property->parameters())); - $this->assertInstanceOf('Sabre\\VObject\\Parameter', $property->parameters['PARAMNAME']); + $this->assertInstanceOf(Parameter::class, $property->parameters['PARAMNAME']); $this->assertEquals('PARAMNAME', $property->parameters['PARAMNAME']->name); $this->assertEquals('paramvalue', $property->parameters['PARAMNAME']->getValue()); } @@ -308,11 +308,10 @@ public function testGetValue() /** * ElementList should reject this. - * - * @expectedException \LogicException */ public function testArrayAccessSetInt() { + $this->expectException(\LogicException::class); $calendar = new VCalendar(); $property = $calendar->createProperty('X-PROP', null); @@ -322,11 +321,10 @@ public function testArrayAccessSetInt() /** * ElementList should reject this. - * - * @expectedException \LogicException */ public function testArrayAccessUnsetInt() { + $this->expectException(\LogicException::class); $calendar = new VCalendar(); $property = $calendar->createProperty('X-PROP', null); diff --git a/tests/VObject/ReaderTest.php b/tests/VObject/ReaderTest.php index 06310e80a..0992806be 100644 --- a/tests/VObject/ReaderTest.php +++ b/tests/VObject/ReaderTest.php @@ -12,7 +12,7 @@ public function testReadComponent() $result = Reader::read($data); - $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertInstanceOf(Component::class, $result); $this->assertEquals('VCALENDAR', $result->name); $this->assertEquals(0, count($result->children())); } @@ -27,7 +27,7 @@ public function testReadStream() $result = Reader::read($stream); - $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertInstanceOf(Component::class, $result); $this->assertEquals('VCALENDAR', $result->name); $this->assertEquals(0, count($result->children())); } @@ -38,7 +38,7 @@ public function testReadComponentUnixNewLine() $result = Reader::read($data); - $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertInstanceOf(Component::class, $result); $this->assertEquals('VCALENDAR', $result->name); $this->assertEquals(0, count($result->children())); } @@ -49,26 +49,22 @@ public function testReadComponentLineFold() $result = Reader::read($data); - $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertInstanceOf(Component::class, $result); $this->assertEquals('VCALENDAR', $result->name); $this->assertEquals(0, count($result->children())); } - /** - * @expectedException \Sabre\VObject\ParseException - */ public function testReadCorruptComponent() { + $this->expectException(ParseException::class); $data = "BEGIN:VCALENDAR\r\nEND:FOO"; $result = Reader::read($data); } - /** - * @expectedException \Sabre\VObject\ParseException - */ public function testReadCorruptSubComponent() { + $this->expectException(ParseException::class); $data = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nEND:FOO\r\nEND:VCALENDAR"; $result = Reader::read($data); @@ -80,7 +76,7 @@ public function testReadProperty() $result = Reader::read($data); $result = $result->SUMMARY; - $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertInstanceOf(Property::class, $result); $this->assertEquals('SUMMARY', $result->name); $this->assertEquals('propValue', $result->getValue()); } @@ -91,7 +87,7 @@ public function testReadPropertyWithNewLine() $result = Reader::read($data); $result = $result->SUMMARY; - $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertInstanceOf(Property::class, $result); $this->assertEquals('SUMMARY', $result->name); $this->assertEquals("Line1\nLine2\nLine3\\Not the 4th line!", $result->getValue()); } @@ -102,7 +98,7 @@ public function testReadMappedProperty() $result = Reader::read($data); $result = $result->DTSTART; - $this->assertInstanceOf('Sabre\\VObject\\Property\\ICalendar\\DateTime', $result); + $this->assertInstanceOf(Property\ICalendar\DateTime::class, $result); $this->assertEquals('DTSTART', $result->name); $this->assertEquals('20110529', $result->getValue()); } @@ -113,16 +109,14 @@ public function testReadMappedPropertyGrouped() $result = Reader::read($data); $result = $result->DTSTART; - $this->assertInstanceOf('Sabre\\VObject\\Property\\ICalendar\\DateTime', $result); + $this->assertInstanceOf(Property\ICalendar\DateTime::class, $result); $this->assertEquals('DTSTART', $result->name); $this->assertEquals('20110529', $result->getValue()); } - /** - * @expectedException \Sabre\VObject\ParseException - */ public function testReadBrokenLine() { + $this->expectException(ParseException::class); $data = "BEGIN:VCALENDAR\r\nPROPNAME;propValue"; $result = Reader::read($data); } @@ -137,10 +131,10 @@ public function testReadPropertyInComponent() $result = Reader::read(implode("\r\n", $data)); - $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertInstanceOf(Component::class, $result); $this->assertEquals('VCALENDAR', $result->name); $this->assertEquals(1, count($result->children())); - $this->assertInstanceOf('Sabre\\VObject\\Property', $result->children()[0]); + $this->assertInstanceOf(Property::class, $result->children()[0]); $this->assertEquals('PROPNAME', $result->children()[0]->name); $this->assertEquals('propValue', $result->children()[0]->getValue()); } @@ -158,13 +152,13 @@ public function testReadNestedComponent() $result = Reader::read(implode("\r\n", $data)); - $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertInstanceOf(Component::class, $result); $this->assertEquals('VCALENDAR', $result->name); $this->assertEquals(1, count($result->children())); - $this->assertInstanceOf('Sabre\\VObject\\Component', $result->children()[0]); + $this->assertInstanceOf(Component::class, $result->children()[0]); $this->assertEquals('VTIMEZONE', $result->children()[0]->name); $this->assertEquals(1, count($result->children()[0]->children())); - $this->assertInstanceOf('Sabre\\VObject\\Component', $result->children()[0]->children()[0]); + $this->assertInstanceOf(Component::class, $result->children()[0]->children()[0]); $this->assertEquals('DAYLIGHT', $result->children()[0]->children()[0]->name); } @@ -175,7 +169,7 @@ public function testReadPropertyParameter() $result = $result->PROPNAME; - $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertInstanceOf(Property::class, $result); $this->assertEquals('PROPNAME', $result->name); $this->assertEquals('propValue', $result->getValue()); $this->assertEquals(1, count($result->parameters())); @@ -190,7 +184,7 @@ public function testReadPropertyRepeatingParameter() $result = $result->PROPNAME; - $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertInstanceOf(Property::class, $result); $this->assertEquals('PROPNAME', $result->name); $this->assertEquals('propValue', $result->getValue()); $this->assertEquals(1, count($result->parameters())); @@ -206,7 +200,7 @@ public function testReadPropertyRepeatingNamelessGuessedParameter() $result = $result->PROPNAME; - $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertInstanceOf(Property::class, $result); $this->assertEquals('PROPNAME', $result->name); $this->assertEquals('propValue', $result->getValue()); $this->assertEquals(1, count($result->parameters())); @@ -222,7 +216,7 @@ public function testReadPropertyNoName() $result = $result->PROPNAME; - $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertInstanceOf(Property::class, $result); $this->assertEquals('PROPNAME', $result->name); $this->assertEquals('propValue', $result->getValue()); $this->assertEquals(1, count($result->parameters())); @@ -238,7 +232,7 @@ public function testReadPropertyParameterExtraColon() $result = $result->PROPNAME; - $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertInstanceOf(Property::class, $result); $this->assertEquals('PROPNAME', $result->name); $this->assertEquals('propValue:anotherrandomstring', $result->getValue()); $this->assertEquals(1, count($result->parameters())); @@ -253,7 +247,7 @@ public function testReadProperty2Parameters() $result = $result->PROPNAME; - $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertInstanceOf(Property::class, $result); $this->assertEquals('PROPNAME', $result->name); $this->assertEquals('propValue', $result->getValue()); $this->assertEquals(2, count($result->parameters())); @@ -270,7 +264,7 @@ public function testReadPropertyParameterQuoted() $result = $result->PROPNAME; - $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertInstanceOf(Property::class, $result); $this->assertEquals('PROPNAME', $result->name); $this->assertEquals('propValue', $result->getValue()); $this->assertEquals(1, count($result->parameters())); @@ -285,7 +279,7 @@ public function testReadPropertyParameterNewLines() $result = $result->PROPNAME; - $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertInstanceOf(Property::class, $result); $this->assertEquals('PROPNAME', $result->name); $this->assertEquals('propValue', $result->getValue()); @@ -300,7 +294,7 @@ public function testReadPropertyParameterQuotedColon() $result = Reader::read($data); $result = $result->PROPNAME; - $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertInstanceOf(Property::class, $result); $this->assertEquals('PROPNAME', $result->name); $this->assertEquals('propValue', $result->getValue()); $this->assertEquals(1, count($result->parameters())); @@ -369,11 +363,10 @@ public function testReadWithInvalidLine() /** * Reported as Issue 32. - * - * @expectedException \Sabre\VObject\ParseException */ public function testReadIncompleteFile() { + $this->expectException(ParseException::class); $input = <<expectException(\InvalidArgumentException::class); Reader::read(false); } @@ -417,7 +408,7 @@ public function testReadBOM() $data = chr(0xef).chr(0xbb).chr(0xbf)."BEGIN:VCALENDAR\r\nEND:VCALENDAR"; $result = Reader::read($data); - $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertInstanceOf(Component::class, $result); $this->assertEquals('VCALENDAR', $result->name); $this->assertEquals(0, count($result->children())); } @@ -434,7 +425,7 @@ public function testReadXMLComponent() $result = Reader::readXML($data); - $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertInstanceOf(Component::class, $result); $this->assertEquals('VCALENDAR', $result->name); $this->assertEquals(0, count($result->children())); } @@ -455,7 +446,7 @@ public function testReadXMLStream() $result = Reader::readXML($stream); - $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertInstanceOf(Component::class, $result); $this->assertEquals('VCALENDAR', $result->name); $this->assertEquals(0, count($result->children())); } diff --git a/tests/VObject/Recur/EventIterator/ByMonthInDailyTest.php b/tests/VObject/Recur/EventIterator/ByMonthInDailyTest.php index 40f09364f..bd1eeb9b6 100644 --- a/tests/VObject/Recur/EventIterator/ByMonthInDailyTest.php +++ b/tests/VObject/Recur/EventIterator/ByMonthInDailyTest.php @@ -1,9 +1,10 @@ assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + $this->assertInstanceOf(VCalendar::class, $vcal); $vcal = $vcal->expand(new DateTime('2013-09-28'), new DateTime('2014-09-11')); + $dates = []; foreach ($vcal->VEVENT as $event) { $dates[] = $event->DTSTART->getValue(); } diff --git a/tests/VObject/Recur/EventIterator/BySetPosHangTest.php b/tests/VObject/Recur/EventIterator/BySetPosHangTest.php index 82bd353e0..555f2ffd4 100644 --- a/tests/VObject/Recur/EventIterator/BySetPosHangTest.php +++ b/tests/VObject/Recur/EventIterator/BySetPosHangTest.php @@ -1,9 +1,10 @@ assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + $this->assertInstanceOf(VCalendar::class, $vcal); $vcal = $vcal->expand(new DateTime('2015-01-01'), new DateTime('2016-01-01')); + $dates = []; foreach ($vcal->VEVENT as $event) { $dates[] = $event->DTSTART->getValue(); } diff --git a/tests/VObject/Recur/EventIterator/ExpandFloatingTimesTest.php b/tests/VObject/Recur/EventIterator/ExpandFloatingTimesTest.php index 635b0a8c5..605e10dde 100644 --- a/tests/VObject/Recur/EventIterator/ExpandFloatingTimesTest.php +++ b/tests/VObject/Recur/EventIterator/ExpandFloatingTimesTest.php @@ -5,6 +5,7 @@ use DateTime; use DateTimeZone; use PHPUnit\Framework\TestCase; +use Sabre\VObject\Component\VCalendar; use Sabre\VObject\Reader; class ExpandFloatingTimesTest extends TestCase @@ -26,7 +27,7 @@ public function testExpand() ICS; $vcal = Reader::read($input); - $this->assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + $this->assertInstanceOf(VCalendar::class, $vcal); $vcal = $vcal->expand(new DateTime('2015-01-01'), new DateTime('2015-01-31')); $output = <<assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + $this->assertInstanceOf(VCalendar::class, $vcal); $vcal = $vcal->expand( new DateTime('2015-01-01'), diff --git a/tests/VObject/Recur/EventIterator/HandleRDateExpandTest.php b/tests/VObject/Recur/EventIterator/HandleRDateExpandTest.php index 698c5fe59..548820191 100644 --- a/tests/VObject/Recur/EventIterator/HandleRDateExpandTest.php +++ b/tests/VObject/Recur/EventIterator/HandleRDateExpandTest.php @@ -6,6 +6,7 @@ use DateTimeImmutable; use DateTimeZone; use PHPUnit\Framework\TestCase; +use Sabre\VObject\Component\VCalendar; use Sabre\VObject\Reader; /** @@ -36,7 +37,7 @@ public function testExpand() ICS; $vcal = Reader::read($input); - $this->assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + $this->assertInstanceOf(VCalendar::class, $vcal); $vcal = $vcal->expand(new DateTime('2015-01-01'), new DateTime('2015-12-01')); diff --git a/tests/VObject/Recur/EventIterator/IncorrectExpandTest.php b/tests/VObject/Recur/EventIterator/IncorrectExpandTest.php index afc509972..f9fcda442 100644 --- a/tests/VObject/Recur/EventIterator/IncorrectExpandTest.php +++ b/tests/VObject/Recur/EventIterator/IncorrectExpandTest.php @@ -4,6 +4,7 @@ use DateTime; use PHPUnit\Framework\TestCase; +use Sabre\VObject\Component\VCalendar; use Sabre\VObject\Reader; /** @@ -34,7 +35,7 @@ public function testExpand() ICS; $vcal = Reader::read($input); - $this->assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + $this->assertInstanceOf(VCalendar::class, $vcal); $vcal = $vcal->expand(new DateTime('2011-01-01'), new DateTime('2014-01-01')); diff --git a/tests/VObject/Recur/EventIterator/InfiniteLoopProblemTest.php b/tests/VObject/Recur/EventIterator/InfiniteLoopProblemTest.php index 2f73d8e37..5546c508d 100644 --- a/tests/VObject/Recur/EventIterator/InfiniteLoopProblemTest.php +++ b/tests/VObject/Recur/EventIterator/InfiniteLoopProblemTest.php @@ -6,11 +6,15 @@ use DateTimeZone; use PHPUnit\Framework\TestCase; use Sabre\VObject\Component\VCalendar; +use Sabre\VObject\InvalidDataException; use Sabre\VObject\Recur; class InfiniteLoopProblemTest extends TestCase { - public function setUp() + /** @var VCalendar */ + private $vcal; + + public function setUp(): void { $this->vcal = new VCalendar(); } @@ -73,11 +77,10 @@ public function testYearlyByMonthLoop() * Something, somewhere produced an ics with an interval set to 0. Because * this means we increase the current day (or week, month) by 0, this also * results in an infinite loop. - * - * @expectedException \Sabre\VObject\InvalidDataException */ public function testZeroInterval() { + $this->expectException(InvalidDataException::class); $ev = $this->vcal->createComponent('VEVENT'); $ev->UID = 'uuid'; $ev->DTSTART = '20120824T145700Z'; diff --git a/tests/VObject/Recur/EventIterator/Issue26Test.php b/tests/VObject/Recur/EventIterator/Issue26Test.php index bb4df64df..3313c3ec6 100644 --- a/tests/VObject/Recur/EventIterator/Issue26Test.php +++ b/tests/VObject/Recur/EventIterator/Issue26Test.php @@ -3,16 +3,16 @@ namespace Sabre\VObject\Recur\EventIterator; use PHPUnit\Framework\TestCase; +use Sabre\VObject\Component\VCalendar; +use Sabre\VObject\InvalidDataException; use Sabre\VObject\Reader; use Sabre\VObject\Recur\EventIterator; class Issue26Test extends TestCase { - /** - * @expectedException \Sabre\VObject\InvalidDataException - */ public function testExpand() { + $this->expectException(InvalidDataException::class); $input = <<assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + $this->assertInstanceOf(VCalendar::class, $vcal); $it = new EventIterator($vcal, 'bae5d57a98'); } diff --git a/tests/VObject/Recur/EventIterator/Issue48Test.php b/tests/VObject/Recur/EventIterator/Issue48Test.php index fe4b06755..aef592590 100644 --- a/tests/VObject/Recur/EventIterator/Issue48Test.php +++ b/tests/VObject/Recur/EventIterator/Issue48Test.php @@ -1,10 +1,13 @@ assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + $this->assertInstanceOf(VCalendar::class, $vcal); - $it = new Recur\EventIterator($vcal, 'foo'); + $it = new EventIterator($vcal, 'foo'); $result = iterator_to_array($it); diff --git a/tests/VObject/Recur/EventIterator/Issue50Test.php b/tests/VObject/Recur/EventIterator/Issue50Test.php index df9c15519..5c476e6f2 100644 --- a/tests/VObject/Recur/EventIterator/Issue50Test.php +++ b/tests/VObject/Recur/EventIterator/Issue50Test.php @@ -1,10 +1,13 @@ assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + $this->assertInstanceOf(VCalendar::class, $vcal); - $it = new Recur\EventIterator($vcal, '1aef0b27-3d92-4581-829a-11999dd36724'); + $it = new EventIterator($vcal, '1aef0b27-3d92-4581-829a-11999dd36724'); $result = []; foreach ($it as $instance) { diff --git a/tests/VObject/Recur/EventIterator/MainTest.php b/tests/VObject/Recur/EventIterator/MainTest.php index 10782a53a..1a019a57d 100644 --- a/tests/VObject/Recur/EventIterator/MainTest.php +++ b/tests/VObject/Recur/EventIterator/MainTest.php @@ -6,6 +6,7 @@ use DateTimeZone; use PHPUnit\Framework\TestCase; use Sabre\VObject\Component\VCalendar; +use Sabre\VObject\InvalidDataException; use Sabre\VObject\Recur\EventIterator; class MainTest extends TestCase @@ -29,11 +30,11 @@ public function testValues() } /** - * @expectedException \Sabre\VObject\InvalidDataException * @depends testValues */ public function testInvalidFreq() { + $this->expectException(InvalidDataException::class); $vcal = new VCalendar(); $ev = $vcal->createComponent('VEVENT'); $ev->RRULE = 'FREQ=SMONTHLY;INTERVAL=3;UNTIL=20111025T000000Z'; @@ -47,20 +48,16 @@ public function testInvalidFreq() $it = new EventIterator($vcal, (string) $ev->UID); } - /** - * @expectedException \InvalidArgumentException - */ public function testVCalendarNoUID() { + $this->expectException(\InvalidArgumentException::class); $vcal = new VCalendar(); $it = new EventIterator($vcal); } - /** - * @expectedException \InvalidArgumentException - */ public function testVCalendarInvalidUID() { + $this->expectException(\InvalidArgumentException::class); $vcal = new VCalendar(); $it = new EventIterator($vcal, 'foo'); } @@ -1386,10 +1383,10 @@ public function testRDATE() /** * @depends testValues - * @expectedException \InvalidArgumentException */ public function testNoMasterBadUID() { + $this->expectException(\InvalidArgumentException::class); $vcal = new VCalendar(); // ev2 overrides an event, and puts it on 2pm instead. $ev2 = $vcal->createComponent('VEVENT'); diff --git a/tests/VObject/Recur/EventIterator/MaxInstancesTest.php b/tests/VObject/Recur/EventIterator/MaxInstancesTest.php index d0571ee82..6314b3b5a 100644 --- a/tests/VObject/Recur/EventIterator/MaxInstancesTest.php +++ b/tests/VObject/Recur/EventIterator/MaxInstancesTest.php @@ -5,15 +5,14 @@ use DateTime; use PHPUnit\Framework\TestCase; use Sabre\VObject\Reader; +use Sabre\VObject\Recur\MaxInstancesExceededException; use Sabre\VObject\Settings; class MaxInstancesTest extends TestCase { - /** - * @expectedException \Sabre\VObject\Recur\MaxInstancesExceededException - */ public function testExceedMaxRecurrences() { + $this->expectException(MaxInstancesExceededException::class); $input = <<assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + $this->assertInstanceOf(VCalendar::class, $vcal); $vcal = $vcal->expand(new DateTime('2011-01-01'), new DateTime('2015-01-01')); diff --git a/tests/VObject/Recur/EventIterator/NoInstancesTest.php b/tests/VObject/Recur/EventIterator/NoInstancesTest.php index b3e5a11fa..d89afd197 100644 --- a/tests/VObject/Recur/EventIterator/NoInstancesTest.php +++ b/tests/VObject/Recur/EventIterator/NoInstancesTest.php @@ -1,17 +1,18 @@ expectException(NoInstancesException::class); $input = <<assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + $this->assertInstanceOf(VCalendar::class, $vcal); $it = new EventIterator($vcal, 'foo'); } diff --git a/tests/VObject/Recur/EventIterator/OverrideFirstEventTest.php b/tests/VObject/Recur/EventIterator/OverrideFirstEventTest.php index 8773c168c..150a13980 100644 --- a/tests/VObject/Recur/EventIterator/OverrideFirstEventTest.php +++ b/tests/VObject/Recur/EventIterator/OverrideFirstEventTest.php @@ -1,6 +1,6 @@ expectException(InvalidDataException::class); $this->parse( 'FREQ=YEARLY;COUNT=6;BYMONTHDAY=24;BYMONTH=0', '2011-04-07 00:00:00', @@ -466,11 +464,9 @@ public function testYearlyByMonthInvalidValue1() ); } - /** - * @expectedException \Sabre\VObject\InvalidDataException - */ public function testYearlyByMonthInvalidValue2() { + $this->expectException(InvalidDataException::class); $this->parse( 'FREQ=YEARLY;COUNT=6;BYMONTHDAY=24;BYMONTH=bla', '2011-04-07 00:00:00', @@ -478,11 +474,9 @@ public function testYearlyByMonthInvalidValue2() ); } - /** - * @expectedException \Sabre\VObject\InvalidDataException - */ public function testYearlyByMonthManyInvalidValues() { + $this->expectException(InvalidDataException::class); $this->parse( 'FREQ=YEARLY;COUNT=6;BYMONTHDAY=24;BYMONTH=0,bla', '2011-04-07 00:00:00', @@ -490,11 +484,9 @@ public function testYearlyByMonthManyInvalidValues() ); } - /** - * @expectedException \Sabre\VObject\InvalidDataException - */ public function testYearlyByMonthEmptyValue() { + $this->expectException(InvalidDataException::class); $this->parse( 'FREQ=YEARLY;COUNT=6;BYMONTHDAY=24;BYMONTH=', '2011-04-07 00:00:00', @@ -675,11 +667,9 @@ public function testFirstFourthSundayEveryOtherMonthAt830and930() ); } - /** - * @expectedException \Sabre\VObject\InvalidDataException - */ public function testYearlyByYearDayInvalid390() { + $this->expectException(InvalidDataException::class); $this->parse( 'FREQ=YEARLY;COUNT=8;INTERVAL=4;BYYEARDAY=390', '2011-04-07 00:00:00', @@ -688,11 +678,9 @@ public function testYearlyByYearDayInvalid390() ); } - /** - * @expectedException \Sabre\VObject\InvalidDataException - */ public function testYearlyByYearDayInvalid0() { + $this->expectException(InvalidDataException::class); $this->parse( 'FREQ=YEARLY;COUNT=8;INTERVAL=4;BYYEARDAY=0', '2011-04-07 00:00:00', @@ -877,11 +865,9 @@ public function testMultipleValidByWeekNo() ); } - /** - * @expectedException \Sabre\VObject\InvalidDataException - */ public function testInvalidByWeekNo() { + $this->expectException(InvalidDataException::class); $this->parse( 'FREQ=YEARLY;BYWEEKNO=54', '2011-05-16 00:00:00', @@ -910,11 +896,10 @@ public function testYearlyByMonthLoop() * Something, somewhere produced an ics with an interval set to 0. Because * this means we increase the current day (or week, month) by 0, this also * results in an infinite loop. - * - * @expectedException \Sabre\VObject\InvalidDataException */ public function testZeroInterval() { + $this->expectException(InvalidDataException::class); $this->parse( 'FREQ=YEARLY;INTERVAL=0', '2012-08-24 14:57:00', @@ -924,11 +909,9 @@ public function testZeroInterval() ); } - /** - * @expectedException \Sabre\VObject\InvalidDataException - */ public function testInvalidFreq() { + $this->expectException(InvalidDataException::class); $this->parse( 'FREQ=SMONTHLY;INTERVAL=3;UNTIL=20111025T000000Z', '2011-10-07', @@ -946,11 +929,9 @@ public function testInvalidMissingFreq() ); } - /** - * @expectedException \Sabre\VObject\InvalidDataException - */ public function testByDayBadOffset() { + $this->expectException(InvalidDataException::class); $this->parse( 'FREQ=WEEKLY;INTERVAL=1;COUNT=4;BYDAY=0MO;WKST=SA', '2014-08-01 00:00:00', @@ -1036,11 +1017,9 @@ public function testNeverEnding() ); } - /** - * @expectedException \Sabre\VObject\InvalidDataException - */ public function testUnsupportedPart() { + $this->expectException(InvalidDataException::class); $this->parse( 'FREQ=DAILY;BYWODAN=1', '2014-08-02 00:15:00', diff --git a/tests/VObject/Splitter/ICalendarTest.php b/tests/VObject/Splitter/ICalendarTest.php index 5addf9f6d..2788b96d6 100644 --- a/tests/VObject/Splitter/ICalendarTest.php +++ b/tests/VObject/Splitter/ICalendarTest.php @@ -4,12 +4,13 @@ use PHPUnit\Framework\TestCase; use Sabre\VObject; +use Sabre\VObject\ParseException; class ICalendarTest extends TestCase { protected $version; - public function setUp() + public function setUp(): void { $this->version = VObject\Version::VERSION; } @@ -45,11 +46,9 @@ public function testICalendarImportValidEvent() $this->assertEquals([], VObject\Reader::read($return)->validate()); } - /** - * @expectedException \Sabre\VObject\ParseException - */ public function testICalendarImportWrongType() { + $this->expectException(ParseException::class); $data = <<assertNull($object = $objects->getNext()); } - /** - * @expectedException \Sabre\VObject\ParseException - */ public function testICalendarImportInvalidEvent() { + $this->expectException(ParseException::class); $data = <<createStream($data); diff --git a/tests/VObject/Splitter/VCardTest.php b/tests/VObject/Splitter/VCardTest.php index a7e4ea3d2..1402b5094 100644 --- a/tests/VObject/Splitter/VCardTest.php +++ b/tests/VObject/Splitter/VCardTest.php @@ -3,6 +3,7 @@ namespace Sabre\VObject\Splitter; use PHPUnit\Framework\TestCase; +use Sabre\VObject\ParseException; class VCardTest extends TestCase { @@ -33,11 +34,9 @@ public function testVCardImportValidVCard() $this->assertEquals(1, $count); } - /** - * @expectedException \Sabre\VObject\ParseException - */ public function testVCardImportWrongType() { + $this->expectException(ParseException::class); $event[] = <<assertEquals(4, $count); } - /** - * @expectedException \Sabre\VObject\ParseException - */ public function testVCardImportVCardNoComponent() { + $this->expectException(ParseException::class); $data = <<expectException(\Sabre\VObject\ParseException::class); + $this->expectException(ParseException::class); $this->expectExceptionMessage('Invalid MimeDir file. Unexpected component: "BEGIN:VCARD" in document type VCARD'); while ($object = $splitter->getNext()) { } @@ -161,11 +158,9 @@ public function testVCardImportEndOfData() $this->assertNull($objects->getNext()); } - /** - * @expectedException \Sabre\VObject\ParseException - */ public function testVCardImportCheckInvalidArgumentException() { + $this->expectException(ParseException::class); $data = <<assertEquals($ex->getName(), $tz->getName()); } - /** - * @expectedException \InvalidArgumentException - */ public function testTimezoneFail() { + $this->expectException(\InvalidArgumentException::class); $tz = TimeZoneUtil::getTimeZone('FooBar', null, true); } diff --git a/tests/VObject/VCardConverterTest.php b/tests/VObject/VCardConverterTest.php index a5be3cc8c..ae2b10bd5 100644 --- a/tests/VObject/VCardConverterTest.php +++ b/tests/VObject/VCardConverterTest.php @@ -294,11 +294,9 @@ public function testBDAYConversion() ); } - /** - * @expectedException \InvalidArgumentException - */ public function testUnknownSourceVCardVersion() { + $this->expectException(\InvalidArgumentException::class); $input = <<convert(Document::VCARD40); } - /** - * @expectedException \InvalidArgumentException - */ public function testUnknownTargetVCardVersion() { + $this->expectException(\InvalidArgumentException::class); $input = <<assertInstanceOf('Sabre\\VObject\\Component\\VCard', $vcard); + $this->assertInstanceOf(Component\VCard::class, $vcard); $vcard = $vcard->convert(Document::VCARD40); $vcard = $vcard->serialize(); @@ -518,4 +514,34 @@ public function testNoLabel() $this->assertEquals($expected, str_replace("\r", '', $vcard)); } + + public function testPhoneNumberValueTypeGetsRemoved() + { + $input = <<convert(Document::VCARD40); + + $this->assertVObjectEqualsVObject( + $output, + $vcard + ); + } } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 46e9014cb..2496aa4ff 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -13,13 +13,3 @@ break; } } - -$autoLoader->addPsr4('Sabre\\VObject\\', __DIR__.'/VObject'); - -if (!defined('SABRE_TEMPDIR')) { - define('SABRE_TEMPDIR', __DIR__.'/temp/'); -} - -if (!file_exists(SABRE_TEMPDIR)) { - mkdir(SABRE_TEMPDIR); -} diff --git a/tests/phpunit.xml b/tests/phpunit.xml index 46dad6a3d..c0588d460 100644 --- a/tests/phpunit.xml +++ b/tests/phpunit.xml @@ -6,18 +6,16 @@ convertWarningsToExceptions="true" beStrictAboutTestsThatDoNotTestAnything="true" beStrictAboutOutputDuringTests="true" - beStrictAboutTestSize="true" > - - VObject/ - + + + . + + ../lib/ - - ../lib/Sabre/VObject/includes.php - From f2903601f81e40f2c8c1839af106e83708ba4a61 Mon Sep 17 00:00:00 2001 From: Valentin BONNEAUD Date: Wed, 18 Nov 2020 22:49:26 +0100 Subject: [PATCH 18/92] Merge upstream changes up to 4.3.3 (#18) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix Bad file descriptor (7.4) (#469) * travis: allow failure for phpstan for now (#470) * travis: add php 7.4snapshot build (#471) * reduce phpstan level back to 0, as level 1 is failing right now (#472) * reduce phpstan level back to 0, as level 1 is failing right now * travis: no longer allow failures for phpstan * Fixed typo in vobject CLI help (#477) * Release 4.2.1 (#479) * Release 4.2.1 * Update CHANGELOG.md * Prepare next iteration (#480) * Run phpstan on PHP >= 7.1 (#483) * Add TZ in iTip REPLY messages * Added phpstan for tests folder (#485) * Add PHONE-NUMBER value type (used for TEL in vCard 3.0) (#486) Signed-off-by: Christian Kraus * use latest phpstan 0.12.5 in CI * Add PHPstorm .idea to .gitignore * Fix various typos * Release 4.2.2 (#490) * Release 4.2.2 * Update CHANGELOG.md * Prepare next iteration * Update dependencies and code style tools * Remove unneeded 'bootstrap' line from phpstan.neon * Apply php-cs-fixer code style changes * run php-cs-fixer in CI * php-cs-fixer must be at least 2.16.1 for PHP 7.4 * Use phpunit8 where possible * Fixed phpstan level 1 errors * Make sure there is no logic change * Prevent setting foreach key beforehand * Refactored fqcn strings to ::class to allow checking with phpstan (#495) * Release 4.3.0 (#497) * Update CHANGELOG.md * Update Version.php * Added phpstan to dev dependencies * Reset bin-dir config * Added convenient development commands * Cleaned up .gitignore These entries should be in the developer's global .gitignore * cs-fixer: don't check only the lib folder * Decoupled cs-fixer command from chosen tool * Standardize CI * Use phpunit 9 where possible * Only upload coverage when it has been collected * Replace assertRegExp with assertMatchesRegularExpression in unit test * fix an incomplete phpdoc type annotation * Release 4.3.1 * Adjust boolean vars in .travis.yml to prepare for PHP8.0 * Run unit tests on PHP8 * Fixup calendar parameter to Broker parseEvent * Release 4.3.2 * Remove Pacific-New obsolete timezone * Do composer remove --no-update in Travis * Use min php-cs-fixer 2.16.7 * Release 4.3.3 Co-authored-by: Remi Collet Co-authored-by: Markus Staab Co-authored-by: Dominik Co-authored-by: Jeroen van Oort Co-authored-by: Renaud BOYER Co-authored-by: Christian Kraus Co-authored-by: Thomas Müller Co-authored-by: Phil Davis Co-authored-by: Michael Stilkerich --- .gitignore | 1 - .travis.yml | 15 +++++++++++++-- composer.json | 4 ++-- lib/ITip/Broker.php | 2 +- lib/Version.php | 2 +- lib/timezonedata/php-bc.php | 1 - 6 files changed, 17 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index b00b0fca1..b952f0309 100644 --- a/.gitignore +++ b/.gitignore @@ -16,7 +16,6 @@ bin/sabre-cs-fixer bin/hoa # Development stuff -testdata/ .php_cs.cache .idea diff --git a/.travis.yml b/.travis.yml index d1feb60de..4f50e97fd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,8 @@ php: env: global: - MEMCACHED_SERVER=127.0.0.1 + - RUN_PHPCSFIXER="TRUE" + - RUN_PHPUNIT="TRUE" - RUN_PHPSTAN="FALSE" matrix: - PREFER_LOWEST="" REPORT_COVERAGE="TRUE" WITH_COVERAGE="--coverage-clover=coverage.xml" @@ -15,19 +17,28 @@ env: matrix: include: + - name: 'PHP8' + dist: focal + php: nightly + env: + - RUN_PHPCSFIXER="FALSE" + - REPORT_COVERAGE="FALSE" - name: 'PHPStan' php: 7.4 env: + - RUN_PHPCSFIXER="FALSE" + - RUN_PHPUNIT="FALSE" - RUN_PHPSTAN="TRUE" - REPORT_COVERAGE="FALSE" fast_finish: true before_script: + - if [ $RUN_PHPCSFIXER == "FALSE" ]; then composer remove --no-update --dev friendsofphp/php-cs-fixer; fi - composer update $PREFER_LOWEST script: - - if [ $RUN_PHPSTAN == "FALSE" ]; then php vendor/bin/php-cs-fixer fix --dry-run --diff; fi - - if [ $RUN_PHPSTAN == "FALSE" ]; then php vendor/bin/phpunit --configuration tests/phpunit.xml $WITH_COVERAGE; fi + - if [ $RUN_PHPCSFIXER == "TRUE" ]; then php vendor/bin/php-cs-fixer fix --dry-run --diff; fi + - if [ $RUN_PHPUNIT == "TRUE" ]; then php vendor/bin/phpunit --configuration tests/phpunit.xml $WITH_COVERAGE; fi - if [ $RUN_PHPSTAN == "TRUE" ]; then composer phpstan; fi after_success: diff --git a/composer.json b/composer.json index 2187f77fe..e4a0b4389 100644 --- a/composer.json +++ b/composer.json @@ -32,12 +32,12 @@ "homepage" : "http://sabre.io/vobject/", "license" : "BSD-3-Clause", "require" : { - "php" : "^7.1", + "php" : "^7.1 || ^8.0", "ext-mbstring" : "*", "sabre/xml" : "^2.1" }, "require-dev" : { - "friendsofphp/php-cs-fixer": "~2.16.1", + "friendsofphp/php-cs-fixer": "~2.16.7", "phpunit/phpunit" : "^7.5 || ^8.5 || ^9.0", "phpstan/phpstan": "^0.12" }, diff --git a/lib/ITip/Broker.php b/lib/ITip/Broker.php index c09cdf3be..4e0368e13 100644 --- a/lib/ITip/Broker.php +++ b/lib/ITip/Broker.php @@ -162,7 +162,7 @@ public function processMessage(Message $itipMessage, VCalendar $existingObject = * * @return array */ - public function parseEvent($calendar = null, $userHref, $oldCalendar = null) + public function parseEvent($calendar, $userHref, $oldCalendar = null) { if ($oldCalendar) { if (is_string($oldCalendar)) { diff --git a/lib/Version.php b/lib/Version.php index 83c82a573..63452400f 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -14,5 +14,5 @@ class Version /** * Full version number. */ - const VERSION = '4.3.4'; + const VERSION = '4.3.5'; } diff --git a/lib/timezonedata/php-bc.php b/lib/timezonedata/php-bc.php index 83f38f507..3116c6868 100644 --- a/lib/timezonedata/php-bc.php +++ b/lib/timezonedata/php-bc.php @@ -147,7 +147,6 @@ 'US/Michigan', 'US/Mountain', 'US/Pacific', - 'US/Pacific-New', 'US/Samoa', 'WET', ]; From e125596a23f4c9869cf17e4ac60d15a55584ddbe Mon Sep 17 00:00:00 2001 From: liurxliu <31659588+liurxliu@users.noreply.github.com> Date: Tue, 24 Nov 2020 00:31:16 +0800 Subject: [PATCH 19/92] Throw exception when getting invalid timezone (#17) Co-authored-by: Ren Xie Liu --- lib/Property/ICalendar/DateTime.php | 2 +- tests/VObject/Property/ICalendar/DateTimeTest.php | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/Property/ICalendar/DateTime.php b/lib/Property/ICalendar/DateTime.php index f2dbdeba3..9342bc46e 100644 --- a/lib/Property/ICalendar/DateTime.php +++ b/lib/Property/ICalendar/DateTime.php @@ -159,7 +159,7 @@ public function getDateTimes(DateTimeZone $timeZone = null) $tzid = $this['TZID']; if ($tzid) { - $timeZone = TimeZoneUtil::getTimeZone((string) $tzid, $this->root); + $timeZone = TimeZoneUtil::getTimeZone((string) $tzid, $this->root, true); } $dts = []; diff --git a/tests/VObject/Property/ICalendar/DateTimeTest.php b/tests/VObject/Property/ICalendar/DateTimeTest.php index 40c6e2ef6..aa584dba5 100644 --- a/tests/VObject/Property/ICalendar/DateTimeTest.php +++ b/tests/VObject/Property/ICalendar/DateTimeTest.php @@ -2,6 +2,7 @@ namespace Sabre\VObject\Property\ICalendar; +use InvalidArgumentException; use PHPUnit\Framework\TestCase; use Sabre\VObject\Component\VCalendar; use Sabre\VObject\InvalidDataException; @@ -304,12 +305,10 @@ public function testGetDateTimeBadTimeZone() $this->vcal->add($event); $this->vcal->add($timezone); - $dt = $elem->getDateTime(); - - $this->assertInstanceOf('DateTimeImmutable', $dt); - $this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s')); - $this->assertEquals('Canada/Eastern', $dt->getTimeZone()->getName()); date_default_timezone_set($default); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('We were unable to determine the correct PHP timezone for tzid: Moon'); + $elem->getDateTime(); } public function testUpdateValueParameter() From 6507b3d76cbe953c32f5ab2585f514c61ac0d929 Mon Sep 17 00:00:00 2001 From: Valentin BONNEAUD Date: Mon, 23 Nov 2020 22:35:11 +0100 Subject: [PATCH 20/92] Update changelog for release 4.4.0 (#19) --- CHANGELOG.md | 8 +++++++- lib/Version.php | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff4480d92..86a53c8e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,16 @@ ChangeLog ========= +4.4.0 (2020-11-23) +------------------ + +* #18 Merge upstream changes from sabre-io/vobject:4.3.3 into protonlabs/vobject +* #17 Throw exception when getting invalid timezone + 4.3.4 (2020-07-27) ------------------ -* # 16 Merge upstream changes from sabre-io/vobject:4.3.1 into protonlabs/vobject +* #16 Merge upstream changes from sabre-io/vobject:4.3.1 into protonlabs/vobject 4.3.3 (2020-07-22) ------------------ diff --git a/lib/Version.php b/lib/Version.php index 63452400f..e8e73360b 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -14,5 +14,5 @@ class Version /** * Full version number. */ - const VERSION = '4.3.5'; + const VERSION = '4.4.0'; } From bee705ac3ade2beffdc631211f92753cbc291966 Mon Sep 17 00:00:00 2001 From: liurxliu <31659588+liurxliu@users.noreply.github.com> Date: Mon, 18 Jan 2021 17:28:54 +0800 Subject: [PATCH 21/92] Validate count and until property (#20) Co-authored-by: Ren Xie Liu --- lib/Recur/RRuleIterator.php | 4 ++++ tests/VObject/Recur/RRuleIteratorTest.php | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/lib/Recur/RRuleIterator.php b/lib/Recur/RRuleIterator.php index c916c9c3c..1607183b1 100644 --- a/lib/Recur/RRuleIterator.php +++ b/lib/Recur/RRuleIterator.php @@ -1002,6 +1002,10 @@ protected function parseRRule($rrule) if (!isset($this->frequency)) { throw new InvalidDataException('Unknown value for FREQ'); } + + if (isset($this->count) && isset($this->until)) { + throw new InvalidDataException('Can not have both UNTIL and COUNT property at the same time'); + } } /** diff --git a/tests/VObject/Recur/RRuleIteratorTest.php b/tests/VObject/Recur/RRuleIteratorTest.php index ed8fedc5c..5f56e6849 100644 --- a/tests/VObject/Recur/RRuleIteratorTest.php +++ b/tests/VObject/Recur/RRuleIteratorTest.php @@ -974,6 +974,18 @@ public function testUntilBeforeDtStart() ); } + public function testUntilAndCount() + { + $this->expectException(InvalidDataException::class); + $this->expectExceptionMessage('Can not have both UNTIL and COUNT property at the same time'); + + $this->parse( + 'FREQ=DAILY;COUNT=5;UNTIL=20201108T225959Z', + '2021-01-18 00:15:00', + [] + ); + } + public function testIgnoredStuff() { $this->parse( From 5e572e24747bbe6db8ae694cd7dbd73cb8784d21 Mon Sep 17 00:00:00 2001 From: Valentin BONNEAUD Date: Mon, 18 Jan 2021 10:38:16 +0100 Subject: [PATCH 22/92] Prepare changelog for 4.4.1 (#21) --- CHANGELOG.md | 4 ++++ lib/Version.php | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 86a53c8e2..44fb5b81c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ ChangeLog ========= +4.4.1 (2021-01-18) +------------------ +* #19 Validate count and until property + 4.4.0 (2020-11-23) ------------------ diff --git a/lib/Version.php b/lib/Version.php index e8e73360b..64938bf0b 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -14,5 +14,5 @@ class Version /** * Full version number. */ - const VERSION = '4.4.0'; + const VERSION = '4.4.1'; } From 57b05a4b6314be58e140e062ff6bd6b40028db37 Mon Sep 17 00:00:00 2001 From: liurxliu <31659588+liurxliu@users.noreply.github.com> Date: Thu, 15 Jul 2021 18:09:40 +0800 Subject: [PATCH 23/92] Add microsoft timezone map (#23) Co-authored-by: Ren Xie Liu --- lib/timezonedata/extrazones.php | 1 + tests/VObject/TimeZoneUtilTest.php | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/lib/timezonedata/extrazones.php b/lib/timezonedata/extrazones.php index d855ff831..e5681496e 100644 --- a/lib/timezonedata/extrazones.php +++ b/lib/timezonedata/extrazones.php @@ -193,4 +193,5 @@ 'UTC-11' => 'Pacific/Niue', 'UTC+12' => 'Pacific/Auckland', 'US Eastern Standard Time' => 'America/New_York', + 'tzone://Microsoft/Utc' => 'UTC', ]; diff --git a/tests/VObject/TimeZoneUtilTest.php b/tests/VObject/TimeZoneUtilTest.php index 619abc19a..0c60f934c 100644 --- a/tests/VObject/TimeZoneUtilTest.php +++ b/tests/VObject/TimeZoneUtilTest.php @@ -354,4 +354,11 @@ public function testPrefixedOffsetExchangeIdentifier() $ex = new \DateTimeZone('America/New_York'); $this->assertEquals($ex->getName(), $tz->getName()); } + + public function testMicrosoftMap() + { + $tz = TimeZoneUtil::getTimeZone('tzone://Microsoft/Utc', null, true); + $ex = new \DateTimeZone('UTC'); + $this->assertEquals($ex->getName(), $tz->getName()); + } } From 038054613e7422df4d6dcfd7733de1650815dfcf Mon Sep 17 00:00:00 2001 From: Valentin BONNEAUD Date: Thu, 15 Jul 2021 12:15:25 +0200 Subject: [PATCH 24/92] Prepare 4.4.2 release (#24) --- CHANGELOG.md | 4 ++++ lib/Version.php | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44fb5b81c..5181d624d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ ChangeLog ========= +4.4.2 (2021-07-15) +------------------ +* #23 Add microsoft timezone map + 4.4.1 (2021-01-18) ------------------ * #19 Validate count and until property diff --git a/lib/Version.php b/lib/Version.php index 64938bf0b..ff8cf49c2 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -14,5 +14,5 @@ class Version /** * Full version number. */ - const VERSION = '4.4.1'; + const VERSION = '4.4.2'; } From f9055e07f91a0ad2e46c2d05a19375bdb9f0de5a Mon Sep 17 00:00:00 2001 From: liurxliu <31659588+liurxliu@users.noreply.github.com> Date: Tue, 5 Oct 2021 01:12:52 +0800 Subject: [PATCH 25/92] [Calendar] Fix duplicate value (#25) Co-authored-by: Ren Xie Liu --- lib/Parser/MimeDir.php | 2 +- tests/VObject/ReaderTest.php | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/Parser/MimeDir.php b/lib/Parser/MimeDir.php index ea5ac0326..9b36df6a9 100644 --- a/lib/Parser/MimeDir.php +++ b/lib/Parser/MimeDir.php @@ -378,7 +378,7 @@ protected function readProperty($line) $property['parameters'][$lastParam] = $value; } elseif (is_array($property['parameters'][$lastParam])) { $property['parameters'][$lastParam][] = $value; - } else { + } elseif ($property['parameters'][$lastParam] !== $value) { $property['parameters'][$lastParam] = [ $property['parameters'][$lastParam], $value, diff --git a/tests/VObject/ReaderTest.php b/tests/VObject/ReaderTest.php index 0992806be..63d1dccf2 100644 --- a/tests/VObject/ReaderTest.php +++ b/tests/VObject/ReaderTest.php @@ -450,4 +450,28 @@ public function testReadXMLStream() $this->assertEquals('VCALENDAR', $result->name); $this->assertEquals(0, count($result->children())); } + + public function testReadDuplicateValue() + { + $input = <<assertSame($expected, $result->VEVENT->DTSTART->serialize()); + } } From b87d00718c38662c2aba63eb49039e59d1e77014 Mon Sep 17 00:00:00 2001 From: Ren Xie Liu Date: Mon, 27 Sep 2021 13:18:26 +0800 Subject: [PATCH 26/92] [Calendar] Add php unsupport timezone --- lib/timezonedata/extrazones.php | 5 ++++ tests/VObject/TimeZoneUtilTest.php | 38 ++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/lib/timezonedata/extrazones.php b/lib/timezonedata/extrazones.php index e5681496e..3f78c638c 100644 --- a/lib/timezonedata/extrazones.php +++ b/lib/timezonedata/extrazones.php @@ -194,4 +194,9 @@ 'UTC+12' => 'Pacific/Auckland', 'US Eastern Standard Time' => 'America/New_York', 'tzone://Microsoft/Utc' => 'UTC', + 'America/Santa_Isabel' => 'America/Tijuana', + 'Asia/Chongqing' => 'Asia/Shanghai', + 'Asia/Harbin' => 'Asia/Shanghai', + 'Asia/Kashgar' => 'Asia/Urumqi', + 'Pacific/Johnston' => 'Pacific/Honolulu', ]; diff --git a/tests/VObject/TimeZoneUtilTest.php b/tests/VObject/TimeZoneUtilTest.php index 0c60f934c..bfa1fdb4a 100644 --- a/tests/VObject/TimeZoneUtilTest.php +++ b/tests/VObject/TimeZoneUtilTest.php @@ -361,4 +361,42 @@ public function testMicrosoftMap() $ex = new \DateTimeZone('UTC'); $this->assertEquals($ex->getName(), $tz->getName()); } + + /** + * @dataProvider unSupportTimezoneProvider + */ + public function testPHPUnSupportTimeZone(string $origin, string $expected) + { + $tz = TimeZoneUtil::getTimeZone($origin, null, true); + $ex = new \DateTimeZone($expected); + $this->assertEquals($ex->getName(), $tz->getName()); + } + + public function unSupportTimezoneProvider(): iterable + { + yield 'America/Santa_Isabel' => [ + 'origin' => 'America/Santa_Isabel', + 'expected' => 'America/Tijuana', + ]; + + yield 'Asia/Chongqing' => [ + 'origin' => 'Asia/Chongqing', + 'expected' => 'Asia/Shanghai', + ]; + + yield 'Asia/Harbin' => [ + 'origin' => 'Asia/Harbin', + 'expected' => 'Asia/Shanghai', + ]; + + yield 'Asia/Kashgar' => [ + 'origin' => 'Asia/Kashgar', + 'expected' => 'Asia/Urumqi', + ]; + + yield 'Pacific/Johnston' => [ + 'origin' => 'Pacific/Johnston', + 'expected' => 'Pacific/Honolulu', + ]; + } } From fb68d5a0caa84b08b6d77a11d71a506bdda7fcc6 Mon Sep 17 00:00:00 2001 From: Valentin Bonneaud Date: Mon, 4 Oct 2021 19:16:53 +0200 Subject: [PATCH 27/92] Update changelog for 4.5.1 --- CHANGELOG.md | 5 +++++ lib/Version.php | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5181d624d..4a938b175 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ ChangeLog ========= +4.5.1 (2021-10-11) +------------------ +* #25 Fix duplicate value +* #26 Add php unsupport timezone + 4.4.2 (2021-07-15) ------------------ * #23 Add microsoft timezone map diff --git a/lib/Version.php b/lib/Version.php index ff8cf49c2..92882f2f1 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -14,5 +14,5 @@ class Version /** * Full version number. */ - const VERSION = '4.4.2'; + const VERSION = '4.5.1'; } From 79a8073535f6019f599ce0d27e7c13a6bba7c070 Mon Sep 17 00:00:00 2001 From: Valentin Bonneaud Date: Mon, 25 Oct 2021 19:25:31 +0200 Subject: [PATCH 28/92] Add FR Europe localized timezone --- lib/timezonedata/exchangezones.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/timezonedata/exchangezones.php b/lib/timezonedata/exchangezones.php index 89bddc27c..586e2615f 100644 --- a/lib/timezonedata/exchangezones.php +++ b/lib/timezonedata/exchangezones.php @@ -91,4 +91,7 @@ 'Hawaii' => 'Pacific/Honolulu', 'Midway Island, Samoa' => 'Pacific/Midway', 'Eniwetok, Kwajalein, Dateline Time' => 'Pacific/Kwajalein', + + // Localized timezones + 'Amsterdam, Berlin, Berne, Rome, Stockholm, Vienne' => 'Europe/Berlin', ]; From 1b109b0aafc78654ed22e6c3ef4258e8377d6bc2 Mon Sep 17 00:00:00 2001 From: Giuseppe Arcuti Date: Mon, 1 Nov 2021 16:38:52 +0100 Subject: [PATCH 29/92] Fix timezones starting with slash --- lib/TimeZoneUtil.php | 8 ++++++++ tests/VObject/TimeZoneUtilTest.php | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/lib/TimeZoneUtil.php b/lib/TimeZoneUtil.php index 4113eed12..d1f6c344a 100644 --- a/lib/TimeZoneUtil.php +++ b/lib/TimeZoneUtil.php @@ -135,7 +135,12 @@ public static function getTimeZone($tzid, Component $vcalendar = null, $failIfUn // Since PHP 5.5.10, the first bit will be used as the timezone and // this method will return just GMT+01:00. This is wrong, because it // doesn't take DST into account. + $originalTzid = $tzid; if ('(' !== $tzid[0]) { + // If the timezone is prefixed with a slash we remove the slash for lookup in the maps. + if ('/' === $tzid[0]) { + $tzid = substr($tzid, 1); + } // PHP has a bug that logs PHP warnings even it shouldn't: // https://bugs.php.net/bug.php?id=67881 // @@ -163,6 +168,9 @@ public static function getTimeZone($tzid, Component $vcalendar = null, $failIfUn return new \DateTimeZone(self::$map[$tzid]); } + // If we removed the slash we add it back + $tzid = $originalTzid; + // Some Microsoft products prefix the offset first, so let's strip that off // and see if it is our tzid map. We don't want to check for this first just // in case there are overrides in our tzid map. diff --git a/tests/VObject/TimeZoneUtilTest.php b/tests/VObject/TimeZoneUtilTest.php index bfa1fdb4a..5a9548ab0 100644 --- a/tests/VObject/TimeZoneUtilTest.php +++ b/tests/VObject/TimeZoneUtilTest.php @@ -42,6 +42,16 @@ function ($value) { ); } + /** + * @dataProvider getMapping + */ + public function testSlashTZ($timezonename) { + $slashTimezone = '/' . $timezonename; + $expected = TimeZoneUtil::getTimeZone($timezonename)->getName(); + $actual = TimeZoneUtil::getTimeZone($slashTimezone)->getName(); + $this->assertEquals($expected, $actual); + } + public function testExchangeMap() { $vobj = << Date: Mon, 1 Nov 2021 17:03:33 +0100 Subject: [PATCH 30/92] Fix EDT timezone --- lib/timezonedata/extrazones.php | 1 + tests/VObject/TimeZoneUtilTest.php | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/lib/timezonedata/extrazones.php b/lib/timezonedata/extrazones.php index 3f78c638c..69a009cd1 100644 --- a/lib/timezonedata/extrazones.php +++ b/lib/timezonedata/extrazones.php @@ -199,4 +199,5 @@ 'Asia/Harbin' => 'Asia/Shanghai', 'Asia/Kashgar' => 'Asia/Urumqi', 'Pacific/Johnston' => 'Pacific/Honolulu', + 'EDT' => 'America/New_York', ]; diff --git a/tests/VObject/TimeZoneUtilTest.php b/tests/VObject/TimeZoneUtilTest.php index 5a9548ab0..792eae3cf 100644 --- a/tests/VObject/TimeZoneUtilTest.php +++ b/tests/VObject/TimeZoneUtilTest.php @@ -408,5 +408,10 @@ public function unSupportTimezoneProvider(): iterable 'origin' => 'Pacific/Johnston', 'expected' => 'Pacific/Honolulu', ]; + + yield 'EDT' => [ + 'origin' => 'EDT', + 'expected' => 'America/New_York', + ]; } } From fe6cf8e31fd5dae1003ec9d6eb8de85fbe8a956e Mon Sep 17 00:00:00 2001 From: Valentin Bonneaud Date: Thu, 4 Nov 2021 12:18:33 +0100 Subject: [PATCH 31/92] Use Manaus tz --- lib/timezonedata/extrazones.php | 2 +- tests/VObject/TimeZoneUtilTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/timezonedata/extrazones.php b/lib/timezonedata/extrazones.php index 69a009cd1..134c3cb6d 100644 --- a/lib/timezonedata/extrazones.php +++ b/lib/timezonedata/extrazones.php @@ -199,5 +199,5 @@ 'Asia/Harbin' => 'Asia/Shanghai', 'Asia/Kashgar' => 'Asia/Urumqi', 'Pacific/Johnston' => 'Pacific/Honolulu', - 'EDT' => 'America/New_York', + 'EDT' => 'America/Manaus', ]; diff --git a/tests/VObject/TimeZoneUtilTest.php b/tests/VObject/TimeZoneUtilTest.php index 792eae3cf..8b6103f68 100644 --- a/tests/VObject/TimeZoneUtilTest.php +++ b/tests/VObject/TimeZoneUtilTest.php @@ -411,7 +411,7 @@ public function unSupportTimezoneProvider(): iterable yield 'EDT' => [ 'origin' => 'EDT', - 'expected' => 'America/New_York', + 'expected' => 'America/Manaus', ]; } } From 3e3131cf52866598fb66124b93e01e9cbb8ee113 Mon Sep 17 00:00:00 2001 From: Valentin Bonneaud Date: Thu, 4 Nov 2021 12:22:30 +0100 Subject: [PATCH 32/92] Release of 4.6.1 --- CHANGELOG.md | 5 +++++ lib/Version.php | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a938b175..58921b307 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ ChangeLog ========= +4.6.1 (2021-11-04) +------------------ +* #29 Fix timezone name prefixed with / +* #30 Missing EDT TZID conversion + 4.5.1 (2021-10-11) ------------------ * #25 Fix duplicate value diff --git a/lib/Version.php b/lib/Version.php index 92882f2f1..d0d1337fd 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -14,5 +14,5 @@ class Version /** * Full version number. */ - const VERSION = '4.5.1'; + const VERSION = '4.6.1'; } From ea0bcee653c56f68362354ca6db66531ceda4aa1 Mon Sep 17 00:00:00 2001 From: Valentin Bonneaud Date: Thu, 4 Nov 2021 20:03:31 +0100 Subject: [PATCH 33/92] Add github actions --- .github/workflows/tests.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 000000000..1e87e8cd8 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,35 @@ +name: Tests + +on: [push] + +jobs: + build: + strategy: + matrix: + operating-system: [ubuntu-latest] + php-versions: ['7.4'] + runs-on: ${{ matrix.operating-system }} + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 #https://github.com/shivammathur/setup-php + with: + php-version: ${{ matrix.php-versions }} + extensions: mbstring, simplexml, dom + coverage: xdebug #optional + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + - name: Cache composer dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + # Use composer.json for key, if composer.lock is not committed. + # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + - name: Install dependencies + run: composer install --no-progress --prefer-dist --optimize-autoloader + - name: Test with phpunit + run: vendor/bin/phpunit --configuration ./tests/phpunit.xml \ No newline at end of file From a1a3a76a6b667144404e7c9627b2a1c2d77770ef Mon Sep 17 00:00:00 2001 From: Valentin Bonneaud Date: Thu, 4 Nov 2021 20:27:23 +0100 Subject: [PATCH 34/92] Add coverage --- .github/workflows/{tests.yml => actions.yml} | 27 ++++++++++++++++---- README.md | 4 +-- codecov.yml | 19 ++++++++++++++ 3 files changed, 43 insertions(+), 7 deletions(-) rename .github/workflows/{tests.yml => actions.yml} (71%) create mode 100644 codecov.yml diff --git a/.github/workflows/tests.yml b/.github/workflows/actions.yml similarity index 71% rename from .github/workflows/tests.yml rename to .github/workflows/actions.yml index 1e87e8cd8..55ad1fb4b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/actions.yml @@ -3,24 +3,29 @@ name: Tests on: [push] jobs: - build: + phpunit: strategy: matrix: operating-system: [ubuntu-latest] php-versions: ['7.4'] + runs-on: ${{ matrix.operating-system }} + steps: - name: Checkout uses: actions/checkout@v2 + - name: Setup PHP, with composer and extensions - uses: shivammathur/setup-php@v2 #https://github.com/shivammathur/setup-php + uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-versions }} - extensions: mbstring, simplexml, dom - coverage: xdebug #optional + extensions: mbstring + coverage: pcov + - name: Get composer cache directory id: composer-cache run: echo "::set-output name=dir::$(composer config cache-files-dir)" + - name: Cache composer dependencies uses: actions/cache@v2 with: @@ -29,7 +34,19 @@ jobs: # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: ${{ runner.os }}-composer- + - name: Install dependencies run: composer install --no-progress --prefer-dist --optimize-autoloader + - name: Test with phpunit - run: vendor/bin/phpunit --configuration ./tests/phpunit.xml \ No newline at end of file + run: vendor/bin/phpunit --configuration ./tests/phpunit.xml --coverage-text --coverage-clover=coverage.xml + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} + file: ./coverage.xml + flags: tests + name: codecov-umbrella + yml: ./codecov.yml + fail_ci_if_error: true \ No newline at end of file diff --git a/README.md b/README.md index 53fa93ebc..0499f3a25 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ sabre/vobject ============= -[![Build Status](https://img.shields.io/travis/ProtonMail/vobject?style=flat-square)](https://travis-ci.org/ProtonMail/vobject) -[![Coverage](https://img.shields.io/codecov/c/github/ProtonMail/vobject?style=flat-square)](https://codecov.io/gh/ProtonMail/vobject) +![Build Status](https://github.com/ProtonMail/vobject/actions/workflows/tests.yml/badge.svg) +[![codecov](https://codecov.io/gh/ProtonMail/vobject/branch/master/graph/badge.svg?token=ARcwkxCKZn)](https://codecov.io/gh/ProtonMail/vobject) The VObject library allows you to easily parse and manipulate [iCalendar](https://tools.ietf.org/html/rfc5545) diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 000000000..03a09f7d8 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,19 @@ +comment: + layout: "reach, diff, flags, files" + behavior: default + require_changes: false + +coverage: + range: "70..100" + round: down + precision: 2 + status: + project: + default: + # basic + target: 95% + threshold: 0% + # advanced settings + if_ci_failed: error + informational: false + only_pulls: false \ No newline at end of file From c26e2d40db82dab5468e622dafa8b213d6161d87 Mon Sep 17 00:00:00 2001 From: Valentin BONNEAUD Date: Thu, 4 Nov 2021 21:11:45 +0100 Subject: [PATCH 35/92] Fix tests badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0499f3a25..968bc32b9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ sabre/vobject ============= -![Build Status](https://github.com/ProtonMail/vobject/actions/workflows/tests.yml/badge.svg) +![Build Status](https://github.com/ProtonMail/vobject/actions/workflows/actions.yml/badge.svg) [![codecov](https://codecov.io/gh/ProtonMail/vobject/branch/master/graph/badge.svg?token=ARcwkxCKZn)](https://codecov.io/gh/ProtonMail/vobject) From 1424b3875bcd5b98bb51d3192614e04c23a8119c Mon Sep 17 00:00:00 2001 From: giuseppe-arcuti <93533561+giuseppe-arcuti@users.noreply.github.com> Date: Wed, 15 Dec 2021 11:48:57 +0100 Subject: [PATCH 36/92] Merge upstream commits (#34) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix Bad file descriptor (7.4) (#469) * travis: allow failure for phpstan for now (#470) * travis: add php 7.4snapshot build (#471) * reduce phpstan level back to 0, as level 1 is failing right now (#472) * reduce phpstan level back to 0, as level 1 is failing right now * travis: no longer allow failures for phpstan * Fixed typo in vobject CLI help (#477) * Release 4.2.1 (#479) * Release 4.2.1 * Update CHANGELOG.md * Prepare next iteration (#480) * Run phpstan on PHP >= 7.1 (#483) * Add TZ in iTip REPLY messages * Added phpstan for tests folder (#485) * Add PHONE-NUMBER value type (used for TEL in vCard 3.0) (#486) Signed-off-by: Christian Kraus * use latest phpstan 0.12.5 in CI * Add PHPstorm .idea to .gitignore * Fix various typos * Release 4.2.2 (#490) * Release 4.2.2 * Update CHANGELOG.md * Prepare next iteration * Update dependencies and code style tools * Remove unneeded 'bootstrap' line from phpstan.neon * Apply php-cs-fixer code style changes * run php-cs-fixer in CI * php-cs-fixer must be at least 2.16.1 for PHP 7.4 * Use phpunit8 where possible * Fixed phpstan level 1 errors * Make sure there is no logic change * Prevent setting foreach key beforehand * Refactored fqcn strings to ::class to allow checking with phpstan (#495) * Release 4.3.0 (#497) * Update CHANGELOG.md * Update Version.php * Added phpstan to dev dependencies * Reset bin-dir config * Added convenient development commands * Cleaned up .gitignore These entries should be in the developer's global .gitignore * cs-fixer: don't check only the lib folder * Decoupled cs-fixer command from chosen tool * Standardize CI * Use phpunit 9 where possible * Only upload coverage when it has been collected * Replace assertRegExp with assertMatchesRegularExpression in unit test * fix an incomplete phpdoc type annotation * Release 4.3.1 * Adjust boolean vars in .travis.yml to prepare for PHP8.0 * Run unit tests on PHP8 * Fixup calendar parameter to Broker parseEvent * Release 4.3.2 * Remove Pacific-New obsolete timezone * Do composer remove --no-update in Travis * Use min php-cs-fixer 2.16.7 * Release 4.3.3 * Add .gitattributes * Explicitly select PHP 8.0 in CI * Use latest php-cs-fixer 2.17.1 * Update windowszones timezone data to 2020-12-13 * Fix typos * Reassign modified date in yearly rrule * Add test * Code style * Add test for calendar expand * adjust unit test settings for time limits Some tests were testing a bug that caused an infinite loop. Annotate those tests with large, small annotations. Turn on enforceTimeLimit, failOnWarning and failOnRisky so that the annotations are enforced when unit tests are run. Add phpunit/php-invoker to the composer require-dev becaause this is required to make the enforceTimeLimit setting effective. * Release 4.3.4 * Make use of until parameter in nextMonthly function * CS FIX * CS FIX * Fix breaking tests * create testMonthlyByDayUntil * create testMonthlyByDayUntilWithImpossibleNextOccurrence * Fix setting properties with group assignment * Unit test for adding properties with group for a VCard * Adapt style * Release 4.3.5 * tests: migrate from Travis to gh-actions * Removed travis config * Minor edit to README * Run phpunit with coverage in CI * Fix deprecated usages and return types on PHP 8.1 * sync ci.yml to match other repos * Changes that should have happened for 4.3.6 * changelog and VERSION bump for 4.3.7 * EventIterator returns wrong endTime (#534) * Reordering of the attendees should not be a signitifcant change (sabre-io#540) * Reordering of vevent should not be a significant change (#542) * Prepare release 4.3.8 * Allow easier extension of the timezone guessing This will ease customization of timezone-guessing as it is now gets easier to extend that process with own implementations (as long as they implement the appropriate interface) This is espechially necessary when wanting to actually guess a timezone via the rules defined in the VTIMEZONE-entry (which is currently not done) * testEmptyTimeZone * Changelog for 4.4.0 * Fix Changelog * Merge github actions Co-authored-by: Remi Collet Co-authored-by: Markus Staab Co-authored-by: Dominik Co-authored-by: Jeroen van Oort Co-authored-by: Renaud BOYER Co-authored-by: Christian Kraus Co-authored-by: Thomas Müller Co-authored-by: Phil Davis Co-authored-by: Michael Stilkerich Co-authored-by: Stéphane Co-authored-by: Allon Moritz Co-authored-by: Jair Cueva Junior Co-authored-by: Parajuli Kiran Co-authored-by: Cédric Anne Co-authored-by: Holger Floerke Co-authored-by: Andreas Heigl --- .gitattributes | 7 + .github/workflows/actions.yml | 12 +- .gitignore | 4 + CHANGELOG.md | 12 +- README.md | 2 +- bin/bench_freebusygenerator.php | 2 +- bin/bench_manipulatevcard.php | 2 +- bin/fetch_windows_zones.php | 5 +- bin/generateicalendardata.php | 4 +- bin/mergeduplicates.php | 4 +- bin/rrulebench.php | 2 +- composer.json | 3 +- lib/Cli.php | 11 +- lib/Component.php | 5 +- lib/Component/VCalendar.php | 2 +- lib/Component/VCard.php | 3 +- lib/ElementList.php | 2 + lib/FreeBusyData.php | 8 +- lib/FreeBusyGenerator.php | 3 +- lib/ITip/Broker.php | 24 +++- lib/Node.php | 7 + lib/Parameter.php | 10 +- lib/Parser/MimeDir.php | 4 +- lib/Property.php | 5 + lib/Property/Boolean.php | 3 +- lib/Property/ICalendar/CalAddress.php | 3 +- lib/Property/ICalendar/DateTime.php | 3 +- lib/Property/IntegerValue.php | 3 +- lib/Property/VCard/LanguageTag.php | 3 +- lib/Recur/EventIterator.php | 10 +- lib/Recur/RRuleIterator.php | 12 +- lib/TimeZoneUtil.php | 129 +++++++++++++++++- lib/TimezoneGuesser/FindFromOffset.php | 31 +++++ .../FindFromTimezoneIdentifier.php | 71 ++++++++++ lib/TimezoneGuesser/FindFromTimezoneMap.php | 78 +++++++++++ lib/TimezoneGuesser/GuessFromLicEntry.php | 33 +++++ lib/TimezoneGuesser/GuessFromMsTzId.php | 119 ++++++++++++++++ lib/TimezoneGuesser/TimezoneFinder.php | 10 ++ lib/TimezoneGuesser/TimezoneGuesser.php | 11 ++ lib/timezonedata/windowszones.php | 17 ++- tests/VObject/Component/VAvailabilityTest.php | 14 +- tests/VObject/Component/VCalendarTest.php | 27 ++++ tests/VObject/Component/VCardTest.php | 4 +- tests/VObject/ComponentTest.php | 17 +++ tests/VObject/DateTimeParserTest.php | 40 +++--- tests/VObject/EmptyValueIssueTest.php | 4 +- .../VObject/ITip/BrokerAttendeeReplyTest.php | 2 +- tests/VObject/ITip/BrokerProcessReplyTest.php | 2 +- .../ITip/BrokerSignificantChangesTest.php | 108 +++++++++++++++ tests/VObject/Parser/XmlTest.php | 112 +++++++-------- tests/VObject/ReaderTest.php | 2 +- .../VObject/Recur/EventIterator/MainTest.php | 12 +- .../EventIterator/OverrideDurationTest.php | 52 +++++++ tests/VObject/Recur/RRuleIteratorTest.php | 54 +++++++- tests/VObject/TimeZoneUtilTest.php | 26 +++- 55 files changed, 979 insertions(+), 176 deletions(-) create mode 100644 .gitattributes create mode 100644 lib/TimezoneGuesser/FindFromOffset.php create mode 100644 lib/TimezoneGuesser/FindFromTimezoneIdentifier.php create mode 100644 lib/TimezoneGuesser/FindFromTimezoneMap.php create mode 100644 lib/TimezoneGuesser/GuessFromLicEntry.php create mode 100644 lib/TimezoneGuesser/GuessFromMsTzId.php create mode 100644 lib/TimezoneGuesser/TimezoneFinder.php create mode 100644 lib/TimezoneGuesser/TimezoneGuesser.php create mode 100644 tests/VObject/Recur/EventIterator/OverrideDurationTest.php diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..f95a950f7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +/tests export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore +/.php_cs.dist export-ignore +/.travis.yml export-ignore +/CHANGELOG.md export-ignore +/phpstan.neon export-ignore diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 55ad1fb4b..7d143be0b 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -35,9 +35,17 @@ jobs: key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: ${{ runner.os }}-composer- - - name: Install dependencies + - name: Install composer dependencies run: composer install --no-progress --prefer-dist --optimize-autoloader + - name: Code Analysis (PHP CS-Fixer) + if: matrix.code-analysis == 'yes' + run: php vendor/bin/php-cs-fixer fix --dry-run --diff + + - name: Code Analysis (PHPStan) + if: matrix.code-analysis == 'yes' + run: composer phpstan + - name: Test with phpunit run: vendor/bin/phpunit --configuration ./tests/phpunit.xml --coverage-text --coverage-clover=coverage.xml @@ -49,4 +57,4 @@ jobs: flags: tests name: codecov-umbrella yml: ./codecov.yml - fail_ci_if_error: true \ No newline at end of file + fail_ci_if_error: true diff --git a/.gitignore b/.gitignore index b952f0309..4b72e7ad6 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,7 @@ bin/hoa # OS X .DS_Store +======= + +# Development stuff +.php_cs.cache diff --git a/CHANGELOG.md b/CHANGELOG.md index 58921b307..b01f32d2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ ChangeLog ========= +4.7.0 (2021-12-15) +------------------ +* #34 Merge upstream changes from sabre-io/vobject:4.4.0 into protonlabs/vobject + 4.6.1 (2021-11-04) ------------------ * #29 Fix timezone name prefixed with / @@ -138,7 +142,7 @@ ChangeLog * #306: iTip REPLYs to the first instance of a recurring event was not handled correctly. * Slightly better error message during validation of `N` and `ADR` properties. -* #312: Correctly extracing timezone in the iTip broker, even when we don't +* #312: Correctly extracting timezone in the iTip broker, even when we don't have a master event. (@vkomrakov-sugar). * When validating a component's property that must appear once and which could automatically be repaired, make sure we report the change as 'repaired'. @@ -460,7 +464,7 @@ ChangeLog * #114: VTIMEZONE is retained when generating new REQUEST objects. * #114: Support for 'MAILTO:' style email addresses (in uppercase) in the iTip broker. This improves evolution support. -* #115: Using REQUEST-STATUS from REPLY messages and now propegating that into +* #115: Using REQUEST-STATUS from REPLY messages and now propagating that into SCHEDULE-STATUS. @@ -697,7 +701,7 @@ ChangeLog 3.0.0-alpha2 (2013-05-22) ------------------------- -* Fixed: vCard URL properties were referencing a non-existant class. +* Fixed: vCard URL properties were referencing a non-existent class. 3.0.0-alpha1 (2013-05-21) @@ -855,7 +859,7 @@ ChangeLog properties such as N, ADR, ORG and CATEGORIES. * Added: Splitter classes, that can split up large objects (such as exports) into individual objects (thanks @DominikTo and @armin-hackmann). -* Added: VFREEBUSY component, which allows easily checking wether timeslots are +* Added: VFREEBUSY component, which allows easily checking whether timeslots are available. * Added: The Reader class now has a 'FORGIVING' option, which allows it to parse properties with incorrect characters in the name (at this time, it just allows diff --git a/README.md b/README.md index 968bc32b9..b5b1cc96c 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ sabre/vobject The VObject library allows you to easily parse and manipulate [iCalendar](https://tools.ietf.org/html/rfc5545) and [vCard](https://tools.ietf.org/html/rfc6350) objects using PHP. -The goal of the VObject library is to create a very complete library, with an easy to use API. +The goal of the VObject library is to create a very complete library, with an easy-to-use API. Installation diff --git a/bin/bench_freebusygenerator.php b/bin/bench_freebusygenerator.php index 1299c14fb..963623d18 100644 --- a/bin/bench_freebusygenerator.php +++ b/bin/bench_freebusygenerator.php @@ -11,7 +11,7 @@ echo "The process will be repeated 100 times to get accurate stats\n"; echo "\n"; echo 'Usage: '.$argv[0]." inputfile.ics\n"; - die(); + exit(); } list(, $inputFile) = $argv; diff --git a/bin/bench_manipulatevcard.php b/bin/bench_manipulatevcard.php index f229091db..df6d9f23d 100644 --- a/bin/bench_manipulatevcard.php +++ b/bin/bench_manipulatevcard.php @@ -10,7 +10,7 @@ echo 'system.'; echo "\n"; echo 'Usage: '.$argv[0]." inputfile.vcf\n"; - die(); + exit(); } list(, $inputFile) = $argv; diff --git a/bin/fetch_windows_zones.php b/bin/fetch_windows_zones.php index 9c4e51abd..2361dc309 100755 --- a/bin/fetch_windows_zones.php +++ b/bin/fetch_windows_zones.php @@ -1,13 +1,12 @@ #!/usr/bin/env php inputFormat = 'mimedir'; break; @@ -211,7 +206,7 @@ public function main(array $argv) } if (!in_array($positional[0], ['validate', 'repair', 'convert', 'color'])) { - throw new InvalidArgumentException('Uknown command: '.$positional[0]); + throw new InvalidArgumentException('Unknown command: '.$positional[0]); } } catch (InvalidArgumentException $e) { $this->showHelp(); @@ -458,8 +453,6 @@ protected function convert($vObj) * Colorizes a file. * * @param Component $vObj - * - * @return int */ protected function color($vObj) { diff --git a/lib/Component.php b/lib/Component.php index da45eb29f..f33b628a7 100644 --- a/lib/Component.php +++ b/lib/Component.php @@ -160,9 +160,9 @@ public function remove($item) return; } } - } - throw new \InvalidArgumentException('The item you passed to remove() was not a child of this component'); + throw new \InvalidArgumentException('The item you passed to remove() was not a child of this component'); + } } /** @@ -339,6 +339,7 @@ function ($a, $b) use ($sortScore, $tmp) { * * @return array */ + #[\ReturnTypeWillChange] public function jsonSerialize() { $components = []; diff --git a/lib/Component/VCalendar.php b/lib/Component/VCalendar.php index 40e09a1c0..4db318135 100644 --- a/lib/Component/VCalendar.php +++ b/lib/Component/VCalendar.php @@ -309,7 +309,7 @@ public function expand(DateTimeInterface $start, DateTimeInterface $end, DateTim foreach ($this->children() as $child) { if ($child instanceof Property && 'PRODID' !== $child->name) { - // We explictly want to ignore PRODID, because we want to + // We explicitly want to ignore PRODID, because we want to // overwrite it with our own. $newChildren[] = clone $child; } elseif ($child instanceof Component && 'VTIMEZONE' !== $child->name) { diff --git a/lib/Component/VCard.php b/lib/Component/VCard.php index 51321949f..eac789842 100644 --- a/lib/Component/VCard.php +++ b/lib/Component/VCard.php @@ -373,7 +373,7 @@ public function getValidationRules() /** * Returns a preferred field. * - * VCards can indicate wether a field such as ADR, TEL or EMAIL is + * VCards can indicate whether a field such as ADR, TEL or EMAIL is * preferred by specifying TYPE=PREF (vcard 2.1, 3) or PREF=x (vcard 4, x * being a number between 1 and 100). * @@ -445,6 +445,7 @@ protected function getDefaults() * * @return array */ + #[\ReturnTypeWillChange] public function jsonSerialize() { // A vcard does not have sub-components, so we're overriding this diff --git a/lib/ElementList.php b/lib/ElementList.php index 56058cbd5..860512649 100644 --- a/lib/ElementList.php +++ b/lib/ElementList.php @@ -25,6 +25,7 @@ class ElementList extends ArrayIterator * @param int $offset * @param mixed $value */ + #[\ReturnTypeWillChange] public function offsetSet($offset, $value) { throw new LogicException('You can not add new objects to an ElementList'); @@ -37,6 +38,7 @@ public function offsetSet($offset, $value) * * @param int $offset */ + #[\ReturnTypeWillChange] public function offsetUnset($offset) { throw new LogicException('You can not remove objects from an ElementList'); diff --git a/lib/FreeBusyData.php b/lib/FreeBusyData.php index d05dfc799..4d9f441ce 100644 --- a/lib/FreeBusyData.php +++ b/lib/FreeBusyData.php @@ -84,7 +84,7 @@ public function add($start, $end, $type) 'type' => $type, ]; - $preceedingItem = $this->data[$insertStartIndex - 1]; + $precedingItem = $this->data[$insertStartIndex - 1]; if ($this->data[$insertStartIndex - 1]['start'] === $start) { // The old item starts at the exact same point as the new item. --$insertStartIndex; @@ -122,11 +122,11 @@ public function add($start, $end, $type) // between. if (-1 === $itemsToDelete) { $itemsToDelete = 0; - if ($newItem['end'] < $preceedingItem['end']) { + if ($newItem['end'] < $precedingItem['end']) { $newItems[] = [ 'start' => $newItem['end'] + 1, - 'end' => $preceedingItem['end'], - 'type' => $preceedingItem['type'], + 'end' => $precedingItem['end'], + 'type' => $precedingItem['type'], ]; } } diff --git a/lib/FreeBusyGenerator.php b/lib/FreeBusyGenerator.php index a1c24044c..81b8126d5 100644 --- a/lib/FreeBusyGenerator.php +++ b/lib/FreeBusyGenerator.php @@ -126,7 +126,7 @@ public function setVAvailability(Document $vcalendar) /** * Sets the input objects. * - * You must either specify a valendar object as a string, or as the parse + * You must either specify a vcalendar object as a string, or as the parse * Component. * It's also possible to specify multiple objects as an array. * @@ -362,7 +362,6 @@ protected function calculateBusy(FreeBusyData $fbData, array $objects) foreach ($object->getBaseComponents() as $component) { switch ($component->name) { case 'VEVENT': - $FBTYPE = 'BUSY'; if (isset($component->TRANSP) && ('TRANSPARENT' === strtoupper($component->TRANSP))) { break; diff --git a/lib/ITip/Broker.php b/lib/ITip/Broker.php index 4e0368e13..b66a59f54 100644 --- a/lib/ITip/Broker.php +++ b/lib/ITip/Broker.php @@ -547,9 +547,13 @@ protected function parseEventForOrganizer(VCalendar $calendar, array $eventInfo, // properties changed in the event, or simply if there's a // difference in instances that the attendee is invited to. + $oldAttendeeInstances = array_keys($attendee['oldInstances']); + $newAttendeeInstances = array_keys($attendee['newInstances']); + $message->significantChange = 'REQUEST' === $attendee['forceSend'] || - array_keys($attendee['oldInstances']) != array_keys($attendee['newInstances']) || + count($oldAttendeeInstances) != count($newAttendeeInstances) || + count(array_diff($oldAttendeeInstances, $newAttendeeInstances)) > 0 || $oldEventInfo['significantChangeHash'] !== $eventInfo['significantChangeHash']; foreach ($attendee['newInstances'] as $instanceId => $instanceInfo) { @@ -816,7 +820,10 @@ protected function parseEventInfo(VCalendar $calendar = null) $instances = []; $exdate = []; + $significantChangeEventProperties = []; + foreach ($calendar->VEVENT as $vevent) { + $eventSignificantChangeHash = ''; $rrule = []; if (is_null($uid)) { @@ -930,19 +937,26 @@ protected function parseEventInfo(VCalendar $calendar = null) if (isset($vevent->$prop)) { $propertyValues = $vevent->select($prop); - $significantChangeHash .= $prop.':'; + $eventSignificantChangeHash .= $prop.':'; if ('EXDATE' === $prop) { - $significantChangeHash .= implode(',', $exdate).';'; + $eventSignificantChangeHash .= implode(',', $exdate).';'; } elseif ('RRULE' === $prop) { - $significantChangeHash .= implode(',', $rrule).';'; + $eventSignificantChangeHash .= implode(',', $rrule).';'; } else { foreach ($propertyValues as $val) { - $significantChangeHash .= $val->getValue().';'; + $eventSignificantChangeHash .= $val->getValue().';'; } } } } + $significantChangeEventProperties[] = $eventSignificantChangeHash; + } + + asort($significantChangeEventProperties); + + foreach ($significantChangeEventProperties as $eventSignificantChangeHash) { + $significantChangeHash .= $eventSignificantChangeHash; } $significantChangeHash = md5($significantChangeHash); diff --git a/lib/Node.php b/lib/Node.php index 4c0c04f72..2041b2ac7 100644 --- a/lib/Node.php +++ b/lib/Node.php @@ -73,6 +73,7 @@ abstract public function serialize(); * * @return array */ + #[\ReturnTypeWillChange] abstract public function jsonSerialize(); /** @@ -102,6 +103,7 @@ public function destroy() * * @return ElementList */ + #[\ReturnTypeWillChange] public function getIterator() { if (!is_null($this->iterator)) { @@ -157,6 +159,7 @@ public function validate($options = 0) * * @return int */ + #[\ReturnTypeWillChange] public function count() { $it = $this->getIterator(); @@ -177,6 +180,7 @@ public function count() * * @return bool */ + #[\ReturnTypeWillChange] public function offsetExists($offset) { $iterator = $this->getIterator(); @@ -193,6 +197,7 @@ public function offsetExists($offset) * * @return mixed */ + #[\ReturnTypeWillChange] public function offsetGet($offset) { $iterator = $this->getIterator(); @@ -208,6 +213,7 @@ public function offsetGet($offset) * @param int $offset * @param mixed $value */ + #[\ReturnTypeWillChange] public function offsetSet($offset, $value) { $iterator = $this->getIterator(); @@ -228,6 +234,7 @@ public function offsetSet($offset, $value) * * @param int $offset */ + #[\ReturnTypeWillChange] public function offsetUnset($offset) { $iterator = $this->getIterator(); diff --git a/lib/Parameter.php b/lib/Parameter.php index e39d320a1..7e4d55743 100644 --- a/lib/Parameter.php +++ b/lib/Parameter.php @@ -95,13 +95,11 @@ public static function guessParameterNameByValue($value) case 'WORK': case 'HOME': case 'PREF': - // Delivery Label Type case 'DOM': case 'INTL': case 'POSTAL': case 'PARCEL': - // Telephone types case 'VOICE': case 'FAX': @@ -113,7 +111,6 @@ public static function guessParameterNameByValue($value) case 'CAR': case 'ISDN': case 'VIDEO': - // EMAIL types (lol) case 'AOL': case 'APPLELINK': @@ -127,7 +124,6 @@ public static function guessParameterNameByValue($value) case 'PRODIGY': case 'TLX': case 'X400': - // Photo / Logo format types case 'GIF': case 'CGM': @@ -143,12 +139,10 @@ public static function guessParameterNameByValue($value) case 'MPEG2': case 'AVI': case 'QTIME': - // Sound Digital Audio Type case 'WAVE': case 'PCM': case 'AIFF': - // Key types case 'X509': case 'PGP': @@ -299,7 +293,7 @@ function ($out, $item) { // https://tools.ietf.org/html/rfc6868 // // But we've found that iCal (7.0, shipped with OSX 10.9) - // severaly trips on + characters not being quoted, so we + // severely trips on + characters not being quoted, so we // added + as well. if (!preg_match('#(?: [\n":;\^,\+] )#x', $item)) { return $out.$item; @@ -327,6 +321,7 @@ function ($out, $item) { * * @return array */ + #[\ReturnTypeWillChange] public function jsonSerialize() { return $this->value; @@ -360,6 +355,7 @@ public function __toString() * * @return ElementList */ + #[\ReturnTypeWillChange] public function getIterator() { if (!is_null($this->iterator)) { diff --git a/lib/Parser/MimeDir.php b/lib/Parser/MimeDir.php index 9b36df6a9..ecf1944fa 100644 --- a/lib/Parser/MimeDir.php +++ b/lib/Parser/MimeDir.php @@ -343,7 +343,7 @@ protected function readProperty($line) ) (?=[;:,]) /xi"; - //echo $regex, "\n"; die(); + //echo $regex, "\n"; exit(); preg_match_all($regex, $line, $matches, PREG_SET_ORDER); $property = [ @@ -439,7 +439,7 @@ protected function readProperty($line) $propObj->add(null, $namelessParameter); } - if ('QUOTED-PRINTABLE' === strtoupper($propObj['ENCODING'])) { + if (isset($propObj['ENCODING']) && 'QUOTED-PRINTABLE' === strtoupper($propObj['ENCODING'])) { $propObj->setQuotedPrintableValue($this->extractQuotedPrintableValue()); } else { $charset = $this->charset; diff --git a/lib/Property.php b/lib/Property.php index f9cf8e38e..6219c9b67 100644 --- a/lib/Property.php +++ b/lib/Property.php @@ -276,6 +276,7 @@ public function setJsonValue(array $value) * * @return array */ + #[\ReturnTypeWillChange] public function jsonSerialize() { $parameters = []; @@ -387,6 +388,7 @@ public function __toString() * * @return bool */ + #[\ReturnTypeWillChange] public function offsetExists($name) { if (is_int($name)) { @@ -413,6 +415,7 @@ public function offsetExists($name) * * @return Node */ + #[\ReturnTypeWillChange] public function offsetGet($name) { if (is_int($name)) { @@ -433,6 +436,7 @@ public function offsetGet($name) * @param string $name * @param mixed $value */ + #[\ReturnTypeWillChange] public function offsetSet($name, $value) { if (is_int($name)) { @@ -453,6 +457,7 @@ public function offsetSet($name, $value) * * @param string $name */ + #[\ReturnTypeWillChange] public function offsetUnset($name) { if (is_int($name)) { diff --git a/lib/Property/Boolean.php b/lib/Property/Boolean.php index 9fb2bce35..4bd6ffdfe 100644 --- a/lib/Property/Boolean.php +++ b/lib/Property/Boolean.php @@ -2,8 +2,7 @@ namespace Sabre\VObject\Property; -use - Sabre\VObject\Property; +use Sabre\VObject\Property; /** * Boolean property. diff --git a/lib/Property/ICalendar/CalAddress.php b/lib/Property/ICalendar/CalAddress.php index e89bb31f9..86be66c15 100644 --- a/lib/Property/ICalendar/CalAddress.php +++ b/lib/Property/ICalendar/CalAddress.php @@ -2,8 +2,7 @@ namespace Sabre\VObject\Property\ICalendar; -use - Sabre\VObject\Property\Text; +use Sabre\VObject\Property\Text; /** * CalAddress property. diff --git a/lib/Property/ICalendar/DateTime.php b/lib/Property/ICalendar/DateTime.php index 9342bc46e..cbafc16ba 100644 --- a/lib/Property/ICalendar/DateTime.php +++ b/lib/Property/ICalendar/DateTime.php @@ -184,7 +184,7 @@ public function setDateTime(DateTimeInterface $dt, $isFloating = false) * Sets the property as multiple date-time objects. * * The first value will be used as a reference for the timezones, and all - * the otehr values will be adjusted for that timezone + * the other values will be adjusted for that timezone * * @param DateTimeInterface[] $dt * @param bool isFloating If set to true, timezones will be ignored @@ -300,6 +300,7 @@ function ($item) { * @param string $name * @param mixed $value */ + #[\ReturnTypeWillChange] public function offsetSet($name, $value) { parent::offsetSet($name, $value); diff --git a/lib/Property/IntegerValue.php b/lib/Property/IntegerValue.php index 6f709bfff..3ae775214 100644 --- a/lib/Property/IntegerValue.php +++ b/lib/Property/IntegerValue.php @@ -2,8 +2,7 @@ namespace Sabre\VObject\Property; -use - Sabre\VObject\Property; +use Sabre\VObject\Property; /** * Integer property. diff --git a/lib/Property/VCard/LanguageTag.php b/lib/Property/VCard/LanguageTag.php index 697273989..318ea0231 100644 --- a/lib/Property/VCard/LanguageTag.php +++ b/lib/Property/VCard/LanguageTag.php @@ -2,8 +2,7 @@ namespace Sabre\VObject\Property\VCard; -use - Sabre\VObject\Property; +use Sabre\VObject\Property; /** * LanguageTag property. diff --git a/lib/Recur/EventIterator.php b/lib/Recur/EventIterator.php index fd904b383..310bebe41 100644 --- a/lib/Recur/EventIterator.php +++ b/lib/Recur/EventIterator.php @@ -83,7 +83,7 @@ class EventIterator implements \Iterator * 2. You can pass an array of VEVENTs (all UIDS should match). * 3. You can pass a single VEVENT component. * - * Only the second method is recomended. The other 1 and 3 will be removed + * Only the second method is recommended. The other 1 and 3 will be removed * at some point in the future. * * The $uid parameter is only required for the first method. @@ -229,9 +229,13 @@ public function getDtEnd() if (!$this->valid()) { return; } - $end = clone $this->currentDate; + if ($this->currentOverriddenEvent && $this->currentOverriddenEvent->DTEND) { + return $this->currentOverriddenEvent->DTEND->getDateTime($this->timeZone); + } else { + $end = clone $this->currentDate; - return $end->modify('+'.$this->eventDuration.' seconds'); + return $end->modify('+'.$this->eventDuration.' seconds'); + } } /** diff --git a/lib/Recur/RRuleIterator.php b/lib/Recur/RRuleIterator.php index 1607183b1..3dd483429 100644 --- a/lib/Recur/RRuleIterator.php +++ b/lib/Recur/RRuleIterator.php @@ -644,6 +644,13 @@ protected function nextMonthly($amount = 1) $currentMinuteOfMonth = 0; $currentSecondOfMonth = 0; + // For some reason the "until" parameter was not being used here, + // that's why the workaround of the 10000 year bug was needed at all + // let's stop it before the "until" parameter date + if ($this->until && $this->currentDate->getTimestamp() >= $this->until->getTimestamp()) { + return; + } + // To prevent running this forever (better: until we hit the max date of DateTimeImmutable) we simply // stop at 9999-12-31. Looks like the year 10000 problem is not solved in php .... if ($this->currentDate->getTimestamp() > 253402300799) { @@ -715,7 +722,7 @@ protected function nextYearly($amount = 1) foreach ($this->byWeekNo as $byWeekNo) { foreach ($dayOffsets as $dayOffset) { $date = clone $this->currentDate; - $date->setISODate($currentYear, $byWeekNo, $dayOffset); + $date = $date->setISODate($currentYear, $byWeekNo, $dayOffset); if ($date > $this->currentDate) { $checkDates[] = $date; @@ -913,7 +920,6 @@ protected function parseRRule($rrule) break; case 'INTERVAL': - case 'COUNT': $val = (int) $value; if ($val < 1) { @@ -1092,7 +1098,7 @@ protected function getMonthlyOccurrences() foreach ($this->byMonthDay as $monthDay) { // Removing values that are out of range for this month if ($monthDay > $startDate->format('t') || - $monthDay < 0 - $startDate->format('t')) { + $monthDay < 0 - $startDate->format('t')) { continue; } if ($monthDay > 0) { diff --git a/lib/TimeZoneUtil.php b/lib/TimeZoneUtil.php index d1f6c344a..9e382c655 100644 --- a/lib/TimeZoneUtil.php +++ b/lib/TimeZoneUtil.php @@ -2,6 +2,16 @@ namespace Sabre\VObject; +use DateTimeZone; +use InvalidArgumentException; +use Sabre\VObject\TimezoneGuesser\FindFromOffset; +use Sabre\VObject\TimezoneGuesser\FindFromTimezoneIdentifier; +use Sabre\VObject\TimezoneGuesser\FindFromTimezoneMap; +use Sabre\VObject\TimezoneGuesser\GuessFromLicEntry; +use Sabre\VObject\TimezoneGuesser\GuessFromMsTzId; +use Sabre\VObject\TimezoneGuesser\TimezoneFinder; +use Sabre\VObject\TimezoneGuesser\TimezoneGuesser; + /** * Time zone name translation. * @@ -14,17 +24,125 @@ */ class TimeZoneUtil { + /** @var self */ + private static $instance = null; + + /** @var TimezoneGuesser[] */ + private $timezoneGuessers = []; + + /** @var TimezoneFinder[] */ + private $timezoneFinders = []; + + // Keeping things for backwards compatibility + /** + * @var array|null + * + * @deprecated + */ public static $map = null; + private function __construct() + { + $this->addGuesser('lic', new GuessFromLicEntry()); + $this->addGuesser('msTzId', new GuessFromMsTzId()); + $this->addFinder('tzid', new FindFromTimezoneIdentifier()); + $this->addFinder('tzmap', new FindFromTimezoneMap()); + $this->addFinder('offset', new FindFromOffset()); + } + + private static function getInstance(): self + { + if (null === self::$instance) { + self::$instance = new self(); + } + + return self::$instance; + } + + private function addGuesser(string $key, TimezoneGuesser $guesser): void + { + $this->timezoneGuessers[$key] = $guesser; + } + + private function addFinder(string $key, TimezoneFinder $finder): void + { + $this->timezoneFinders[$key] = $finder; + } + + /** + * This method will try to find out the correct timezone for an iCalendar + * date-time value. + * + * You must pass the contents of the TZID parameter, as well as the full + * calendar. + * + * If the lookup fails, this method will return the default PHP timezone + * (as configured using date_default_timezone_set, or the date.timezone ini + * setting). + * + * Alternatively, if $failIfUncertain is set to true, it will throw an + * exception if we cannot accurately determine the timezone. + */ + private function findTimeZone(string $tzid, Component $vcalendar = null, bool $failIfUncertain = false): DateTimeZone + { + foreach ($this->timezoneFinders as $timezoneFinder) { + $timezone = $timezoneFinder->find($tzid, $failIfUncertain); + if (!$timezone instanceof DateTimeZone) { + continue; + } + + return $timezone; + } + + if ($vcalendar) { + // If that didn't work, we will scan VTIMEZONE objects + foreach ($vcalendar->select('VTIMEZONE') as $vtimezone) { + if ((string) $vtimezone->TZID === $tzid) { + foreach ($this->timezoneGuessers as $timezoneGuesser) { + $timezone = $timezoneGuesser->guess($vtimezone, $failIfUncertain); + if (!$timezone instanceof DateTimeZone) { + continue; + } + + return $timezone; + } + } + } + } + + if ($failIfUncertain) { + throw new InvalidArgumentException('We were unable to determine the correct PHP timezone for tzid: '.$tzid); + } + + // If we got all the way here, we default to whatever has been set as the PHP default timezone. + return new DateTimeZone(date_default_timezone_get()); + } + + public static function addTimezoneGuesser(string $key, TimezoneGuesser $guesser): void + { + self::getInstance()->addGuesser($key, $guesser); + } + + public static function addTimezoneFinder(string $key, TimezoneFinder $finder): void + { + self::getInstance()->addFinder($key, $finder); + } + + public static function clean(): void + { + self::$instance = null; + } + /** * List of microsoft exchange timezone ids. * * Source: http://msdn.microsoft.com/en-us/library/aa563018(loband).aspx + * + * @deprecated */ public static $microsoftExchangeMap = [ 0 => 'UTC', 31 => 'Africa/Casablanca', - // Insanely, id #2 is used for both Europe/Lisbon, and Europe/Sarajevo. // I'm not even kidding.. We handle this special case in the // getTimeZone method. @@ -104,6 +222,7 @@ class TimeZoneUtil ]; /** +<<<<<<< HEAD * This method will try to find out the correct timezone for an iCalendar * date-time value. * @@ -136,7 +255,7 @@ public static function getTimeZone($tzid, Component $vcalendar = null, $failIfUn // this method will return just GMT+01:00. This is wrong, because it // doesn't take DST into account. $originalTzid = $tzid; - if ('(' !== $tzid[0]) { + if ($tzid && '(' !== $tzid[0]) { // If the timezone is prefixed with a slash we remove the slash for lookup in the maps. if ('/' === $tzid[0]) { $tzid = substr($tzid, 1); @@ -238,8 +357,12 @@ public static function getTimeZone($tzid, Component $vcalendar = null, $failIfUn } /** +======= +>>>>>>> upstream/master * This method will load in all the tz mapping information, if it's not yet * done. + * + * @deprecated */ public static function loadTzMaps() { @@ -266,6 +389,8 @@ public static function loadTzMaps() * (See timezonedata/php-bc.php and timezonedata php-workaround.php) * * @return array + * + * @deprecated */ public static function getIdentifiersBC() { diff --git a/lib/TimezoneGuesser/FindFromOffset.php b/lib/TimezoneGuesser/FindFromOffset.php new file mode 100644 index 000000000..990ac9692 --- /dev/null +++ b/lib/TimezoneGuesser/FindFromOffset.php @@ -0,0 +1,31 @@ +getIdentifiersBC())) + ) { + return new DateTimeZone($tzid); + } + } catch (Exception $e) { + } + + return null; + } + + /** + * This method returns an array of timezone identifiers, that are supported + * by DateTimeZone(), but not returned by DateTimeZone::listIdentifiers(). + * + * We're not using DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC) because: + * - It's not supported by some PHP versions as well as HHVM. + * - It also returns identifiers, that are invalid values for new DateTimeZone() on some PHP versions. + * (See timezonedata/php-bc.php and timezonedata php-workaround.php) + * + * @return array + */ + private function getIdentifiersBC() + { + return include __DIR__.'/../timezonedata/php-bc.php'; + } +} diff --git a/lib/TimezoneGuesser/FindFromTimezoneMap.php b/lib/TimezoneGuesser/FindFromTimezoneMap.php new file mode 100644 index 000000000..b52ba6a19 --- /dev/null +++ b/lib/TimezoneGuesser/FindFromTimezoneMap.php @@ -0,0 +1,78 @@ +hasTzInMap($tzid)) { + return new DateTimeZone($this->getTzFromMap($tzid)); + } + + // Some Microsoft products prefix the offset first, so let's strip that off + // and see if it is our tzid map. We don't want to check for this first just + // in case there are overrides in our tzid map. + foreach ($this->patterns as $pattern) { + if (!preg_match($pattern, $tzid, $matches)) { + continue; + } + $tzidAlternate = $matches[3]; + if ($this->hasTzInMap($tzidAlternate)) { + return new DateTimeZone($this->getTzFromMap($tzidAlternate)); + } + } + + return null; + } + + /** + * This method returns an array of timezone identifiers, that are supported + * by DateTimeZone(), but not returned by DateTimeZone::listIdentifiers(). + * + * We're not using DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC) because: + * - It's not supported by some PHP versions as well as HHVM. + * - It also returns identifiers, that are invalid values for new DateTimeZone() on some PHP versions. + * (See timezonedata/php-bc.php and timezonedata php-workaround.php) + * + * @return array + */ + private function getTzMaps() + { + if ([] === $this->map) { + $this->map = array_merge( + include __DIR__.'/../timezonedata/windowszones.php', + include __DIR__.'/../timezonedata/lotuszones.php', + include __DIR__.'/../timezonedata/exchangezones.php', + include __DIR__.'/../timezonedata/php-workaround.php' + ); + } + + return $this->map; + } + + private function getTzFromMap(string $tzid): string + { + return $this->getTzMaps()[$tzid]; + } + + private function hasTzInMap(string $tzid): bool + { + return isset($this->getTzMaps()[$tzid]); + } +} diff --git a/lib/TimezoneGuesser/GuessFromLicEntry.php b/lib/TimezoneGuesser/GuessFromLicEntry.php new file mode 100644 index 000000000..f340a3962 --- /dev/null +++ b/lib/TimezoneGuesser/GuessFromLicEntry.php @@ -0,0 +1,33 @@ +{'X-LIC-LOCATION'})) { + return null; + } + + $lic = (string) $vtimezone->{'X-LIC-LOCATION'}; + + // Libical generators may specify strings like + // "SystemV/EST5EDT". For those we must remove the + // SystemV part. + if ('SystemV/' === substr($lic, 0, 8)) { + $lic = substr($lic, 8); + } + + return TimeZoneUtil::getTimeZone($lic, null, $failIfUncertain); + } +} diff --git a/lib/TimezoneGuesser/GuessFromMsTzId.php b/lib/TimezoneGuesser/GuessFromMsTzId.php new file mode 100644 index 000000000..b11ce1832 --- /dev/null +++ b/lib/TimezoneGuesser/GuessFromMsTzId.php @@ -0,0 +1,119 @@ + 'UTC', + 31 => 'Africa/Casablanca', + + // Insanely, id #2 is used for both Europe/Lisbon, and Europe/Sarajevo. + // I'm not even kidding.. We handle this special case in the + // getTimeZone method. + 2 => 'Europe/Lisbon', + 1 => 'Europe/London', + 4 => 'Europe/Berlin', + 6 => 'Europe/Prague', + 3 => 'Europe/Paris', + 69 => 'Africa/Luanda', // This was a best guess + 7 => 'Europe/Athens', + 5 => 'Europe/Bucharest', + 49 => 'Africa/Cairo', + 50 => 'Africa/Harare', + 59 => 'Europe/Helsinki', + 27 => 'Asia/Jerusalem', + 26 => 'Asia/Baghdad', + 74 => 'Asia/Kuwait', + 51 => 'Europe/Moscow', + 56 => 'Africa/Nairobi', + 25 => 'Asia/Tehran', + 24 => 'Asia/Muscat', // Best guess + 54 => 'Asia/Baku', + 48 => 'Asia/Kabul', + 58 => 'Asia/Yekaterinburg', + 47 => 'Asia/Karachi', + 23 => 'Asia/Calcutta', + 62 => 'Asia/Kathmandu', + 46 => 'Asia/Almaty', + 71 => 'Asia/Dhaka', + 66 => 'Asia/Colombo', + 61 => 'Asia/Rangoon', + 22 => 'Asia/Bangkok', + 64 => 'Asia/Krasnoyarsk', + 45 => 'Asia/Shanghai', + 63 => 'Asia/Irkutsk', + 21 => 'Asia/Singapore', + 73 => 'Australia/Perth', + 75 => 'Asia/Taipei', + 20 => 'Asia/Tokyo', + 72 => 'Asia/Seoul', + 70 => 'Asia/Yakutsk', + 19 => 'Australia/Adelaide', + 44 => 'Australia/Darwin', + 18 => 'Australia/Brisbane', + 76 => 'Australia/Sydney', + 43 => 'Pacific/Guam', + 42 => 'Australia/Hobart', + 68 => 'Asia/Vladivostok', + 41 => 'Asia/Magadan', + 17 => 'Pacific/Auckland', + 40 => 'Pacific/Fiji', + 67 => 'Pacific/Tongatapu', + 29 => 'Atlantic/Azores', + 53 => 'Atlantic/Cape_Verde', + 30 => 'America/Noronha', + 8 => 'America/Sao_Paulo', // Best guess + 32 => 'America/Argentina/Buenos_Aires', + 60 => 'America/Godthab', + 28 => 'America/St_Johns', + 9 => 'America/Halifax', + 33 => 'America/Caracas', + 65 => 'America/Santiago', + 35 => 'America/Bogota', + 10 => 'America/New_York', + 34 => 'America/Indiana/Indianapolis', + 55 => 'America/Guatemala', + 11 => 'America/Chicago', + 37 => 'America/Mexico_City', + 36 => 'America/Edmonton', + 38 => 'America/Phoenix', + 12 => 'America/Denver', // Best guess + 13 => 'America/Los_Angeles', // Best guess + 14 => 'America/Anchorage', + 15 => 'Pacific/Honolulu', + 16 => 'Pacific/Midway', + 39 => 'Pacific/Kwajalein', + ]; + + public function guess(VTimeZone $vtimezone, bool $throwIfUnsure = false): ?DateTimeZone + { + // Microsoft may add a magic number, which we also have an + // answer for. + if (!isset($vtimezone->{'X-MICROSOFT-CDO-TZID'})) { + return null; + } + $cdoId = (int) $vtimezone->{'X-MICROSOFT-CDO-TZID'}->getValue(); + + // 2 can mean both Europe/Lisbon and Europe/Sarajevo. + if (2 === $cdoId && false !== strpos((string) $vtimezone->TZID, 'Sarajevo')) { + return new DateTimeZone('Europe/Sarajevo'); + } + + if (isset(self::$microsoftExchangeMap[$cdoId])) { + return new DateTimeZone(self::$microsoftExchangeMap[$cdoId]); + } + + return null; + } +} diff --git a/lib/TimezoneGuesser/TimezoneFinder.php b/lib/TimezoneGuesser/TimezoneFinder.php new file mode 100644 index 000000000..5aa880a1c --- /dev/null +++ b/lib/TimezoneGuesser/TimezoneFinder.php @@ -0,0 +1,10 @@ + 'Australia/Darwin', 'AUS Eastern Standard Time' => 'Australia/Sydney', 'Afghanistan Standard Time' => 'Asia/Kabul', @@ -74,6 +74,7 @@ 'Line Islands Standard Time' => 'Pacific/Kiritimati', 'Lord Howe Standard Time' => 'Australia/Lord_Howe', 'Magadan Standard Time' => 'Asia/Magadan', + 'Magallanes Standard Time' => 'America/Punta_Arenas', 'Marquesas Standard Time' => 'Pacific/Marquesas', 'Mauritius Standard Time' => 'Indian/Mauritius', 'Middle East Standard Time' => 'Asia/Beirut', @@ -91,11 +92,13 @@ 'North Asia East Standard Time' => 'Asia/Irkutsk', 'North Asia Standard Time' => 'Asia/Krasnoyarsk', 'North Korea Standard Time' => 'Asia/Pyongyang', + 'Omsk Standard Time' => 'Asia/Omsk', 'Pacific SA Standard Time' => 'America/Santiago', 'Pacific Standard Time' => 'America/Los_Angeles', 'Pacific Standard Time (Mexico)' => 'America/Tijuana', 'Pakistan Standard Time' => 'Asia/Karachi', 'Paraguay Standard Time' => 'America/Asuncion', + 'Qyzylorda Standard Time' => 'Asia/Qyzylorda', 'Romance Standard Time' => 'Europe/Paris', 'Russia Time Zone 10' => 'Asia/Srednekolymsk', 'Russia Time Zone 11' => 'Asia/Kamchatka', @@ -108,9 +111,12 @@ 'Saint Pierre Standard Time' => 'America/Miquelon', 'Sakhalin Standard Time' => 'Asia/Sakhalin', 'Samoa Standard Time' => 'Pacific/Apia', + 'Sao Tome Standard Time' => 'Africa/Sao_Tome', + 'Saratov Standard Time' => 'Europe/Saratov', 'Singapore Standard Time' => 'Asia/Singapore', 'South Africa Standard Time' => 'Africa/Johannesburg', 'Sri Lanka Standard Time' => 'Asia/Colombo', + 'Sudan Standard Time' => 'Africa/Khartoum', 'Syria Standard Time' => 'Asia/Damascus', 'Taipei Standard Time' => 'Asia/Taipei', 'Tasmania Standard Time' => 'Australia/Hobart', @@ -125,6 +131,7 @@ 'US Mountain Standard Time' => 'America/Phoenix', 'UTC' => 'Etc/GMT', 'UTC+12' => 'Etc/GMT-12', + 'UTC+13' => 'Etc/GMT-13', 'UTC-02' => 'Etc/GMT+2', 'UTC-08' => 'Etc/GMT+8', 'UTC-09' => 'Etc/GMT+9', @@ -132,6 +139,7 @@ 'Ulaanbaatar Standard Time' => 'Asia/Ulaanbaatar', 'Venezuela Standard Time' => 'America/Caracas', 'Vladivostok Standard Time' => 'Asia/Vladivostok', + 'Volgograd Standard Time' => 'Europe/Volgograd', 'W. Australia Standard Time' => 'Australia/Perth', 'W. Central Africa Standard Time' => 'Africa/Lagos', 'W. Europe Standard Time' => 'Europe/Berlin', @@ -140,4 +148,5 @@ 'West Bank Standard Time' => 'Asia/Hebron', 'West Pacific Standard Time' => 'Pacific/Port_Moresby', 'Yakutsk Standard Time' => 'Asia/Yakutsk', + 'Yukon Standard Time' => 'America/Whitehorse', ]; diff --git a/tests/VObject/Component/VAvailabilityTest.php b/tests/VObject/Component/VAvailabilityTest.php index 2fd9c0dde..edd06b02b 100644 --- a/tests/VObject/Component/VAvailabilityTest.php +++ b/tests/VObject/Component/VAvailabilityTest.php @@ -122,7 +122,7 @@ public function testIsInTimeRangeOutside() ); } - public function testRFCxxxSection3_1_availabilityprop_required() + public function testRFCxxxSection3Part1AvailabilitypropRequired() { // UID and DTSTAMP are present. $this->assertIsValid(Reader::read( @@ -177,7 +177,7 @@ public function testRFCxxxSection3_1_availabilityprop_required() )); } - public function testRFCxxxSection3_1_availabilityprop_optional_once() + public function testRFCxxxSection3Part1AvailabilitypropOptionalOnce() { $properties = [ 'BUSYTYPE:BUSY', @@ -205,7 +205,7 @@ public function testRFCxxxSection3_1_availabilityprop_optional_once() } } - public function testRFCxxxSection3_1_availabilityprop_dtend_duration() + public function testRFCxxxSection3Part1AvailabilitypropDtendDuration() { // Only DTEND. $this->assertIsValid(Reader::read($this->template([ @@ -239,7 +239,7 @@ public function testAvailableSubComponent() $this->assertInstanceOf(Available::class, $document->VAVAILABILITY->AVAILABLE); } - public function testRFCxxxSection3_1_availableprop_required() + public function testRFCxxxSection3Part1AvailablepropRequired() { // UID, DTSTAMP and DTSTART are present. $this->assertIsValid(Reader::read( @@ -331,7 +331,7 @@ public function testRFCxxxSection3_1_availableprop_required() )); } - public function testRFCxxxSection3_1_available_dtend_duration() + public function testRFCxxxSection3Part1AvailableDtendDuration() { // Only DTEND. $this->assertIsValid(Reader::read($this->templateAvailable([ @@ -350,7 +350,7 @@ public function testRFCxxxSection3_1_available_dtend_duration() ]))); } - public function testRFCxxxSection3_1_available_optional_once() + public function testRFCxxxSection3Part1AvailableOptionalOnce() { $properties = [ 'CREATED:20111005T135125Z', @@ -373,7 +373,7 @@ public function testRFCxxxSection3_1_available_optional_once() } } - public function testRFCxxxSection3_2() + public function testRFCxxxSection3Part2() { $this->assertEquals( 'BUSY', diff --git a/tests/VObject/Component/VCalendarTest.php b/tests/VObject/Component/VCalendarTest.php index c2f0ce978..d34e12d2b 100644 --- a/tests/VObject/Component/VCalendarTest.php +++ b/tests/VObject/Component/VCalendarTest.php @@ -350,6 +350,33 @@ public function testBrokenEventExpand() ); } + /** + * This test used to induce an infinite loop. + * The "medium" annotation means that phpunit will fail the + * test if it takes longer than a default of 10 seconds. + * + * @medium + */ + public function testEventExpandYearly() + { + $input = 'BEGIN:VCALENDAR +BEGIN:VEVENT +UID:1a093f1012086078fdd3d9df5ff4d7d0 +DTSTART;TZID=UTC:20210203T130000 +DTEND;TZID=UTC:20210203T140000 +RRULE:FREQ=YEARLY;COUNT=7;WKST=MO;BYDAY=MO;BYWEEKNO=13,15,50 +END:VEVENT +END:VCALENDAR +'; + $vcal = VObject\Reader::read($input); + $events = $vcal->expand( + new \DateTime('2021-01-01'), + new \DateTime('2023-01-01') + ); + + $this->assertCount(7, $events->VEVENT); + } + public function testGetDocumentType() { $vcard = new VCalendar(); diff --git a/tests/VObject/Component/VCardTest.php b/tests/VObject/Component/VCardTest.php index 3124fec84..d8e6110b6 100644 --- a/tests/VObject/Component/VCardTest.php +++ b/tests/VObject/Component/VCardTest.php @@ -135,8 +135,8 @@ public function testGetByType() $vcard = VObject\Reader::read($vcard); $this->assertEquals('1@example.org', $vcard->getByType('EMAIL', 'home')->getValue()); $this->assertEquals('2@example.org', $vcard->getByType('EMAIL', 'work')->getValue()); - $this->assertNull($vcard->getByType('EMAIL', 'non-existant')); - $this->assertNull($vcard->getByType('ADR', 'non-existant')); + $this->assertNull($vcard->getByType('EMAIL', 'non-existent')); + $this->assertNull($vcard->getByType('ADR', 'non-existent')); } public function testPreferredNoPref() diff --git a/tests/VObject/ComponentTest.php b/tests/VObject/ComponentTest.php index f56d55531..cf3e196dc 100644 --- a/tests/VObject/ComponentTest.php +++ b/tests/VObject/ComponentTest.php @@ -72,6 +72,23 @@ public function testMagicGetGroups() $this->assertEquals(null, $email3[0]->group); } + public function testAddGroupProperties() + { + $comp = new VCard([ + 'VERSION' => '3.0', + 'item2.X-ABLabel' => 'item2-Foo', + ]); + + $comp->{'ITEM1.X-ABLabel'} = 'ITEM1-Foo'; + + foreach (['item2', 'ITEM1'] as $group) { + $prop = $comp->{"$group.X-ABLabel"}; + $this->assertInstanceOf(Property::class, $prop); + $this->assertSame("$group-Foo", (string) $prop); + $this->assertSame($group, $prop->group); + } + } + public function testMagicIsset() { $comp = new VCalendar(); diff --git a/tests/VObject/DateTimeParserTest.php b/tests/VObject/DateTimeParserTest.php index b20a43217..ede81e321 100644 --- a/tests/VObject/DateTimeParserTest.php +++ b/tests/VObject/DateTimeParserTest.php @@ -408,7 +408,7 @@ public function vcardDates() ]; } - public function testDateAndOrTime_DateWithYearMonthDay() + public function testDateAndOrTimeDateWithYearMonthDay() { $this->assertDateAndOrTimeEqualsTo( '20150128', @@ -420,7 +420,7 @@ public function testDateAndOrTime_DateWithYearMonthDay() ); } - public function testDateAndOrTime_DateWithYearMonth() + public function testDateAndOrTimeDateWithYearMonth() { $this->assertDateAndOrTimeEqualsTo( '2015-01', @@ -431,7 +431,7 @@ public function testDateAndOrTime_DateWithYearMonth() ); } - public function testDateAndOrTime_DateWithMonth() + public function testDateAndOrTimeDateWithMonth() { $this->assertDateAndOrTimeEqualsTo( '--01', @@ -441,7 +441,7 @@ public function testDateAndOrTime_DateWithMonth() ); } - public function testDateAndOrTime_DateWithMonthDay() + public function testDateAndOrTimeDateWithMonthDay() { $this->assertDateAndOrTimeEqualsTo( '--0128', @@ -452,7 +452,7 @@ public function testDateAndOrTime_DateWithMonthDay() ); } - public function testDateAndOrTime_DateWithDay() + public function testDateAndOrTimeDateWithDay() { $this->assertDateAndOrTimeEqualsTo( '---28', @@ -462,7 +462,7 @@ public function testDateAndOrTime_DateWithDay() ); } - public function testDateAndOrTime_TimeWithHour() + public function testDateAndOrTimeTimeWithHour() { $this->assertDateAndOrTimeEqualsTo( '13', @@ -472,7 +472,7 @@ public function testDateAndOrTime_TimeWithHour() ); } - public function testDateAndOrTime_TimeWithHourMinute() + public function testDateAndOrTimeTimeWithHourMinute() { $this->assertDateAndOrTimeEqualsTo( '1353', @@ -483,7 +483,7 @@ public function testDateAndOrTime_TimeWithHourMinute() ); } - public function testDateAndOrTime_TimeWithHourSecond() + public function testDateAndOrTimeTimeWithHourSecond() { $this->assertDateAndOrTimeEqualsTo( '135301', @@ -495,7 +495,7 @@ public function testDateAndOrTime_TimeWithHourSecond() ); } - public function testDateAndOrTime_TimeWithMinute() + public function testDateAndOrTimeTimeWithMinute() { $this->assertDateAndOrTimeEqualsTo( '-53', @@ -505,7 +505,7 @@ public function testDateAndOrTime_TimeWithMinute() ); } - public function testDateAndOrTime_TimeWithMinuteSecond() + public function testDateAndOrTimeTimeWithMinuteSecond() { $this->assertDateAndOrTimeEqualsTo( '-5301', @@ -516,7 +516,7 @@ public function testDateAndOrTime_TimeWithMinuteSecond() ); } - public function testDateAndOrTime_TimeWithSecond() + public function testDateAndOrTimeTimeWithSecond() { $this->assertTrue(true); @@ -526,7 +526,7 @@ public function testDateAndOrTime_TimeWithSecond() */ } - public function testDateAndOrTime_TimeWithSecondZ() + public function testDateAndOrTimeTimeWithSecondZ() { $this->assertDateAndOrTimeEqualsTo( '--01Z', @@ -537,7 +537,7 @@ public function testDateAndOrTime_TimeWithSecondZ() ); } - public function testDateAndOrTime_TimeWithSecondTZ() + public function testDateAndOrTimeTimeWithSecondTZ() { $this->assertDateAndOrTimeEqualsTo( '--01+1234', @@ -548,7 +548,7 @@ public function testDateAndOrTime_TimeWithSecondTZ() ); } - public function testDateAndOrTime_DateTimeWithYearMonthDayHour() + public function testDateAndOrTimeDateTimeWithYearMonthDayHour() { $this->assertDateAndOrTimeEqualsTo( '20150128T13', @@ -561,7 +561,7 @@ public function testDateAndOrTime_DateTimeWithYearMonthDayHour() ); } - public function testDateAndOrTime_DateTimeWithMonthDayHour() + public function testDateAndOrTimeDateTimeWithMonthDayHour() { $this->assertDateAndOrTimeEqualsTo( '--0128T13', @@ -573,7 +573,7 @@ public function testDateAndOrTime_DateTimeWithMonthDayHour() ); } - public function testDateAndOrTime_DateTimeWithDayHour() + public function testDateAndOrTimeDateTimeWithDayHour() { $this->assertDateAndOrTimeEqualsTo( '---28T13', @@ -584,7 +584,7 @@ public function testDateAndOrTime_DateTimeWithDayHour() ); } - public function testDateAndOrTime_DateTimeWithDayHourMinute() + public function testDateAndOrTimeDateTimeWithDayHourMinute() { $this->assertDateAndOrTimeEqualsTo( '---28T1353', @@ -596,7 +596,7 @@ public function testDateAndOrTime_DateTimeWithDayHourMinute() ); } - public function testDateAndOrTime_DateTimeWithDayHourMinuteSecond() + public function testDateAndOrTimeDateTimeWithDayHourMinuteSecond() { $this->assertDateAndOrTimeEqualsTo( '---28T135301', @@ -609,7 +609,7 @@ public function testDateAndOrTime_DateTimeWithDayHourMinuteSecond() ); } - public function testDateAndOrTime_DateTimeWithDayHourZ() + public function testDateAndOrTimeDateTimeWithDayHourZ() { $this->assertDateAndOrTimeEqualsTo( '---28T13Z', @@ -621,7 +621,7 @@ public function testDateAndOrTime_DateTimeWithDayHourZ() ); } - public function testDateAndOrTime_DateTimeWithDayHourTZ() + public function testDateAndOrTimeDateTimeWithDayHourTZ() { $this->assertDateAndOrTimeEqualsTo( '---28T13+1234', diff --git a/tests/VObject/EmptyValueIssueTest.php b/tests/VObject/EmptyValueIssueTest.php index 91a4d84f6..0798d9c4a 100644 --- a/tests/VObject/EmptyValueIssueTest.php +++ b/tests/VObject/EmptyValueIssueTest.php @@ -17,7 +17,7 @@ public function testDecodeValue() BEGIN:VCALENDAR VERSION:2.0 BEGIN:VEVENT -DESCRIPTION:This is a descpription\\nwith a linebreak and a \\; \\, and : +DESCRIPTION:This is a description\\nwith a linebreak and a \\; \\, and : END:VEVENT END:VCALENDAR ICS; @@ -25,6 +25,6 @@ public function testDecodeValue() $vobj = Reader::read($input); // Before this bug was fixed, getValue() would return nothing. - $this->assertEquals("This is a descpription\nwith a linebreak and a ; , and :", $vobj->VEVENT->DESCRIPTION->getValue()); + $this->assertEquals("This is a description\nwith a linebreak and a ; , and :", $vobj->VEVENT->DESCRIPTION->getValue()); } } diff --git a/tests/VObject/ITip/BrokerAttendeeReplyTest.php b/tests/VObject/ITip/BrokerAttendeeReplyTest.php index 71008c6ae..284075adf 100644 --- a/tests/VObject/ITip/BrokerAttendeeReplyTest.php +++ b/tests/VObject/ITip/BrokerAttendeeReplyTest.php @@ -907,7 +907,7 @@ public function testDeclinedCancelledEvent() * Except in this case, there was already an overridden event, and the * overridden event was marked as cancelled by the attendee. * - * For any other attendence status, the new status would have been + * For any other attendance status, the new status would have been * declined, but for this, no message should we sent. */ public function testDontCreateReplyWhenEventWasDeclined() diff --git a/tests/VObject/ITip/BrokerProcessReplyTest.php b/tests/VObject/ITip/BrokerProcessReplyTest.php index 1cb685096..bbfd6c419 100644 --- a/tests/VObject/ITip/BrokerProcessReplyTest.php +++ b/tests/VObject/ITip/BrokerProcessReplyTest.php @@ -373,7 +373,7 @@ public function testReplyNewExceptionTz() $result = $this->process($itip, $old, $expected); } - public function testReplyPartyCrashCreateExcepton() + public function testReplyPartyCrashCreateException() { // IN this test there's a recurring event that has an exception. The // exception is missing the attendee. diff --git a/tests/VObject/ITip/BrokerSignificantChangesTest.php b/tests/VObject/ITip/BrokerSignificantChangesTest.php index a225cb98c..a20d55025 100644 --- a/tests/VObject/ITip/BrokerSignificantChangesTest.php +++ b/tests/VObject/ITip/BrokerSignificantChangesTest.php @@ -105,4 +105,112 @@ public function testSignificantChangesRRuleOrderNoChange() $this->parse($old, $new, $expected, 'mailto:martin@fruux.com'); } + + /** + * Check significant changes detection (no change). + * Reordering of the attendees should not be a significant change (#540) + * https://github.com/sabre-io/vobject/issues/540. + */ + public function testSignificantChangesAttendeesOrderNoChange() + { + $old = << false]; + $expected[] = ['significantChange' => false]; + + $this->parse($old, $new, $expected, 'mailto:martin@fruux.com'); + } + + /** + * Check significant changes detection (no change). + * Reordering of vevent in a recurring event with exceptions should + * not be a significant change + * https://github.com/sabre-io/vobject/issues/542. + */ + public function testSignificantChangesVeventOrderNoChange() + { + $vevent1 = << false]]; + + $this->parse($old, $new, $expected, 'mailto:martin@fruux.com'); + } } diff --git a/tests/VObject/Parser/XmlTest.php b/tests/VObject/Parser/XmlTest.php index e520185ba..46ee30ce2 100644 --- a/tests/VObject/Parser/XmlTest.php +++ b/tests/VObject/Parser/XmlTest.php @@ -262,7 +262,7 @@ public function testRFC6321Example2() /** * iCalendar Stream. */ - public function testRFC6321Section3_2() + public function testRFC6321Section3Part2() { $this->assertXMLReflexivelyEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( @@ -466,7 +466,7 @@ public function testRFC6321Section3_4_1_3() /** * Values, Binary. */ - public function testRFC6321Section3_6_1() + public function testRFC6321Section3Part6Part1() { $this->assertXMLEqualsToMimeDir( <<assertXMLEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( << with a positive and a non-negative numbers. - $this->testRFC6321Section3_4_1_2(); + $this->testRFC6321Section3Part4Part1Part2(); } /** * Values, Integer. */ - public function testRFC6321Section3_6_8() + public function testRFC6321Section3Part6Part8() { $this->assertXMLEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( @@ -1259,7 +1259,7 @@ public function testRFC6351Section5Group() /** * Extensibility. */ - public function testRFC6351Section5_1_NoNamespace() + public function testRFC6351Section5Part1NoNamespace() { $this->assertXMLEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<assertXMLReflexivelyEqualsToMimeDir( <<createComponent('VEVENT'); @@ -1318,12 +1318,12 @@ public function testOverridenEventNoValuesExpected() $summaries = []; // The reported problem was specifically related to the VCALENDAR - // expansion. In this parcitular case, we had to forward to the 28th of + // expansion. In this particular case, we had to forward to the 28th of // january. $it->fastForward(new DateTimeImmutable('2012-01-28 23:00:00')); - // We stop the loop when it hits the 6th of februari. Normally this - // iterator would hit 24, 25 (overriden from 31) and 7 feb but because + // We stop the loop when it hits the 6th of February. Normally this + // iterator would hit 24, 25 (overridden from 31) and 7 feb but because // we 'filter' from the 28th till the 6th, we should get 0 results. while ($it->valid() && $it->getDTStart() < new DateTimeImmutable('2012-02-06 23:00:00')) { $dates[] = $it->getDTStart(); diff --git a/tests/VObject/Recur/EventIterator/OverrideDurationTest.php b/tests/VObject/Recur/EventIterator/OverrideDurationTest.php new file mode 100644 index 000000000..f25ef13d9 --- /dev/null +++ b/tests/VObject/Recur/EventIterator/OverrideDurationTest.php @@ -0,0 +1,52 @@ +getComponents()); + + $this->assertEquals($eventIterator->current()->format('Y-m-d H:i:s'), '2021-05-17 09:00:00', 'recur event start time'); + $this->assertEquals($eventIterator->getDtEnd()->format('Y-m-d H:i:s'), '2021-05-17 10:00:00', 'recur event end time'); + + $eventIterator->next(); + $this->assertEquals($eventIterator->current()->format('Y-m-d H:i:s'), '2021-05-18 09:00:00', 'recur event start time'); + $this->assertEquals($eventIterator->getDtEnd()->format('Y-m-d H:i:s'), '2021-05-18 10:00:00', 'recur event end time'); + + $eventIterator->next(); + $this->assertEquals($eventIterator->current()->format('Y-m-d H:i:s'), '2021-05-19 09:00:00', 'overridden event start time'); + $this->assertEquals($eventIterator->getDtEnd()->format('Y-m-d H:i:s'), '2021-05-19 12:00:00', 'overridden event end time'); + + $eventIterator->next(); + $this->assertEquals($eventIterator->current()->format('Y-m-d H:i:s'), '2021-05-20 09:00:00', 'recur event start time'); + $this->assertEquals($eventIterator->getDtEnd()->format('Y-m-d H:i:s'), '2021-05-20 10:00:00', 'recur event end time'); + } +} diff --git a/tests/VObject/Recur/RRuleIteratorTest.php b/tests/VObject/Recur/RRuleIteratorTest.php index 5f56e6849..cd4c3f8d4 100644 --- a/tests/VObject/Recur/RRuleIteratorTest.php +++ b/tests/VObject/Recur/RRuleIteratorTest.php @@ -278,7 +278,7 @@ public function testMonthly() ); } - public function testMonlthyEndOfMonth() + public function testMonthlyEndOfMonth() { $this->parse( 'FREQ=MONTHLY;INTERVAL=2;COUNT=12', @@ -358,6 +358,35 @@ public function testMonthlyByDay() ); } + public function testMonthlyByDayUntil() + { + $this->parse( + 'FREQ=MONTHLY;INTERVAL=1;BYDAY=WE;WKST=WE;UNTIL=20210317T000000Z', + '2021-02-10 00:00:00', + [ + '2021-02-10 00:00:00', + '2021-02-17 00:00:00', + '2021-02-24 00:00:00', + '2021-03-03 00:00:00', + '2021-03-10 00:00:00', + '2021-03-17 00:00:00', + ], + 'monthly', null, 1, new DateTime('2021-03-17') + ); + } + + public function testMonthlyByDayUntilWithImpossibleNextOccurrence() + { + $this->parse( + 'FREQ=MONTHLY;INTERVAL=1;BYDAY=2WE;BYMONTHDAY=2;WKST=WE;UNTIL=20210317T000000Z', + '2021-02-10 00:00:00', + [ + '2021-02-10 00:00:00', + ], + 'monthly', null, 1, new DateTime('2021-03-17') + ); + } + public function testMonthlyByDayByMonthDay() { $this->parse( @@ -689,6 +718,20 @@ public function testYearlyByYearDayInvalid0() ); } + public function testYearlyByDayByWeekNo() + { + $this->parse( + 'FREQ=YEARLY;COUNT=3;BYDAY=MO;BYWEEKNO=13,15,50', + '2021-01-01 00:00:00', + [ + '2021-01-01 00:00:00', + '2021-03-29 00:00:00', + '2021-04-12 00:00:00', + ], + 'yearly', 3, 1 + ); + } + public function testFastForward() { // The idea is that we're fast-forwarding too far in the future, so @@ -728,7 +771,7 @@ public function testFifthTuesdayProblem() * This bug came from a Fruux customer. This would result in a never-ending * request. */ - public function testFastFowardTooFar() + public function testFastForwardTooFar() { $this->parse( 'FREQ=WEEKLY;BYDAY=MO;UNTIL=20090704T205959Z;INTERVAL=1', @@ -1014,6 +1057,13 @@ public function testMinusFifthThursday() ); } + /** + * This test can take some seconds to complete. + * The "large" annotation means phpunit will let it run for + * up to 60 seconds by default. + * + * @large + */ public function testNeverEnding() { $this->parse( diff --git a/tests/VObject/TimeZoneUtilTest.php b/tests/VObject/TimeZoneUtilTest.php index 8b6103f68..33a1eff98 100644 --- a/tests/VObject/TimeZoneUtilTest.php +++ b/tests/VObject/TimeZoneUtilTest.php @@ -10,6 +10,7 @@ public function setUp(): void { // clearning the tz cache TimeZoneUtil::$map = null; + TimeZoneUtil::clean(); } /** @@ -31,22 +32,28 @@ public function testCorrectTZ($timezoneName) public function getMapping() { - TimeZoneUtil::loadTzMaps(); + $map = array_merge( + include __DIR__.'/../../lib/timezonedata/windowszones.php', + include __DIR__.'/../../lib/timezonedata/lotuszones.php', + include __DIR__.'/../../lib/timezonedata/exchangezones.php', + include __DIR__.'/../../lib/timezonedata/php-workaround.php' + ); // PHPUNit requires an array of arrays return array_map( function ($value) { return [$value]; }, - TimeZoneUtil::$map + $map ); } /** * @dataProvider getMapping */ - public function testSlashTZ($timezonename) { - $slashTimezone = '/' . $timezonename; + public function testSlashTZ($timezonename) + { + $slashTimezone = '/'.$timezonename; $expected = TimeZoneUtil::getTimeZone($timezonename)->getName(); $actual = TimeZoneUtil::getTimeZone($slashTimezone)->getName(); $this->assertEquals($expected, $actual); @@ -92,7 +99,7 @@ public function testExchangeMap() $this->assertEquals($ex->getName(), $tz->getName()); } - public function testWetherMicrosoftIsStillInsane() + public function testWhetherMicrosoftIsStillInsane() { $vobj = <<assertEquals($ex->getName(), $tz->getName()); } + public function testEmptyTimeZone() + { + $tz = TimeZoneUtil::getTimeZone(''); + $ex = new \DateTimeZone('UTC'); + $this->assertEquals($ex->getName(), $tz->getName()); + } + public function testWindowsTimeZone() { $tz = TimeZoneUtil::getTimeZone('Eastern Standard Time'); @@ -204,7 +218,7 @@ public function getPHPTimeZoneBCIdentifiers() function ($value) { return [$value]; }, - TimeZoneUtil::getIdentifiersBC() + include __DIR__.'/../../lib/timezonedata/php-bc.php' ); } From 0251b1f8e6b667c9071a7fdefaf382b802bf1776 Mon Sep 17 00:00:00 2001 From: Valentin Bonneaud Date: Wed, 15 Dec 2021 13:31:19 +0100 Subject: [PATCH 37/92] Upgrade action codecov --- .github/workflows/actions.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 7d143be0b..3178186b4 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -50,11 +50,10 @@ jobs: run: vendor/bin/phpunit --configuration ./tests/phpunit.xml --coverage-text --coverage-clover=coverage.xml - name: Upload coverage to Codecov - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v2 with: token: ${{ secrets.CODECOV_TOKEN }} file: ./coverage.xml flags: tests name: codecov-umbrella - yml: ./codecov.yml fail_ci_if_error: true From 4d357a65c00b81d980048e17dd8aef81261c2c21 Mon Sep 17 00:00:00 2001 From: Valentin Bonneaud Date: Wed, 15 Dec 2021 11:52:19 +0100 Subject: [PATCH 38/92] Add php8.0 in tests --- .github/workflows/actions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 3178186b4..ac66d2129 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -7,7 +7,7 @@ jobs: strategy: matrix: operating-system: [ubuntu-latest] - php-versions: ['7.4'] + php-versions: ['7.4', '8.0'] runs-on: ${{ matrix.operating-system }} From 814f4b85dcdd58dc19615ccece024542564d9bc2 Mon Sep 17 00:00:00 2001 From: Valentin BONNEAUD Date: Wed, 15 Dec 2021 16:41:11 +0100 Subject: [PATCH 39/92] Merge upstream commits (#36) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix Bad file descriptor (7.4) (#469) * travis: allow failure for phpstan for now (#470) * travis: add php 7.4snapshot build (#471) * reduce phpstan level back to 0, as level 1 is failing right now (#472) * reduce phpstan level back to 0, as level 1 is failing right now * travis: no longer allow failures for phpstan * Fixed typo in vobject CLI help (#477) * Release 4.2.1 (#479) * Release 4.2.1 * Update CHANGELOG.md * Prepare next iteration (#480) * Run phpstan on PHP >= 7.1 (#483) * Add TZ in iTip REPLY messages * Added phpstan for tests folder (#485) * Add PHONE-NUMBER value type (used for TEL in vCard 3.0) (#486) Signed-off-by: Christian Kraus * use latest phpstan 0.12.5 in CI * Add PHPstorm .idea to .gitignore * Fix various typos * Release 4.2.2 (#490) * Release 4.2.2 * Update CHANGELOG.md * Prepare next iteration * Update dependencies and code style tools * Remove unneeded 'bootstrap' line from phpstan.neon * Apply php-cs-fixer code style changes * run php-cs-fixer in CI * php-cs-fixer must be at least 2.16.1 for PHP 7.4 * Use phpunit8 where possible * Fixed phpstan level 1 errors * Make sure there is no logic change * Prevent setting foreach key beforehand * Refactored fqcn strings to ::class to allow checking with phpstan (#495) * Release 4.3.0 (#497) * Update CHANGELOG.md * Update Version.php * Added phpstan to dev dependencies * Reset bin-dir config * Added convenient development commands * Cleaned up .gitignore These entries should be in the developer's global .gitignore * cs-fixer: don't check only the lib folder * Decoupled cs-fixer command from chosen tool * Standardize CI * Use phpunit 9 where possible * Only upload coverage when it has been collected * Replace assertRegExp with assertMatchesRegularExpression in unit test * fix an incomplete phpdoc type annotation * Release 4.3.1 * Adjust boolean vars in .travis.yml to prepare for PHP8.0 * Run unit tests on PHP8 * Fixup calendar parameter to Broker parseEvent * Release 4.3.2 * Remove Pacific-New obsolete timezone * Do composer remove --no-update in Travis * Use min php-cs-fixer 2.16.7 * Release 4.3.3 * Add .gitattributes * Explicitly select PHP 8.0 in CI * Use latest php-cs-fixer 2.17.1 * Update windowszones timezone data to 2020-12-13 * Fix typos * Reassign modified date in yearly rrule * Add test * Code style * Add test for calendar expand * adjust unit test settings for time limits Some tests were testing a bug that caused an infinite loop. Annotate those tests with large, small annotations. Turn on enforceTimeLimit, failOnWarning and failOnRisky so that the annotations are enforced when unit tests are run. Add phpunit/php-invoker to the composer require-dev becaause this is required to make the enforceTimeLimit setting effective. * Release 4.3.4 * Make use of until parameter in nextMonthly function * CS FIX * CS FIX * Fix breaking tests * create testMonthlyByDayUntil * create testMonthlyByDayUntilWithImpossibleNextOccurrence * Fix setting properties with group assignment * Unit test for adding properties with group for a VCard * Adapt style * Release 4.3.5 * tests: migrate from Travis to gh-actions * Removed travis config * Minor edit to README * Run phpunit with coverage in CI * Fix deprecated usages and return types on PHP 8.1 * sync ci.yml to match other repos * Changes that should have happened for 4.3.6 * changelog and VERSION bump for 4.3.7 * EventIterator returns wrong endTime (#534) * Reordering of the attendees should not be a signitifcant change (sabre-io#540) * Reordering of vevent should not be a significant change (#542) * Prepare release 4.3.8 * Allow easier extension of the timezone guessing This will ease customization of timezone-guessing as it is now gets easier to extend that process with own implementations (as long as they implement the appropriate interface) This is espechially necessary when wanting to actually guess a timezone via the rules defined in the VTIMEZONE-entry (which is currently not done) * testEmptyTimeZone * Changelog for 4.4.0 * Fix Iterator method signatures This avoids warnings under PHP>=8.1 Real return types should be added before PHP 9 Signed-off-by: Côme Chilliet * Avoid passing null as separator for implode Signed-off-by: Côme Chilliet * Prepare release 4.4.1 * Rebase fix * Add PHP8.1 CI * Remove PHPUnit update * Fix PHP8.1 tests Co-authored-by: Remi Collet Co-authored-by: Markus Staab Co-authored-by: Dominik Co-authored-by: Jeroen van Oort Co-authored-by: Renaud BOYER Co-authored-by: Christian Kraus Co-authored-by: Thomas Müller Co-authored-by: Phil Davis Co-authored-by: Michael Stilkerich Co-authored-by: Stéphane Co-authored-by: Allon Moritz Co-authored-by: Jair Cueva Junior Co-authored-by: Parajuli Kiran Co-authored-by: Cédric Anne Co-authored-by: Holger Floerke Co-authored-by: Andreas Heigl Co-authored-by: Côme Chilliet --- .github/workflows/actions.yml | 2 +- .gitignore | 18 -- .php_cs.dist | 3 +- .travis.yml | 49 ----- lib/Parser/MimeDir.php | 2 +- lib/Property.php | 2 +- lib/Property/Binary.php | 4 +- lib/Property/FloatValue.php | 2 +- lib/Property/ICalendar/CalAddress.php | 4 +- lib/Property/ICalendar/Duration.php | 2 +- lib/Property/ICalendar/Period.php | 2 +- lib/Property/Time.php | 4 +- lib/Property/Uri.php | 4 +- lib/Property/UtcOffset.php | 4 +- lib/Property/VCard/DateAndOrTime.php | 4 +- lib/Property/VCard/TimeStamp.php | 4 +- lib/Recur/EventIterator.php | 9 + lib/Recur/RDateIterator.php | 9 + lib/Recur/RRuleIterator.php | 17 +- lib/TimeZoneUtil.php | 174 +++--------------- .../FindFromTimezoneIdentifier.php | 6 + lib/TimezoneGuesser/FindFromTimezoneMap.php | 3 +- tests/VObject/Recur/FastForwardBeforeTest.php | 53 ++++-- tests/VObject/Recur/FastForwardTest.php | 51 +++-- tests/VObject/Recur/FastForwardToEndTest.php | 20 +- tests/VObject/TimeZoneUtilTest.php | 5 +- 26 files changed, 185 insertions(+), 272 deletions(-) delete mode 100644 .travis.yml diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index ac66d2129..d1c7c97b2 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -7,7 +7,7 @@ jobs: strategy: matrix: operating-system: [ubuntu-latest] - php-versions: ['7.4', '8.0'] + php-versions: ['7.4', '8.0', '8.1'] runs-on: ${{ matrix.operating-system }} diff --git a/.gitignore b/.gitignore index 4b72e7ad6..82b7dad3f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,23 +5,5 @@ tests/cov/ tests/temp tests/.phpunit.result.cache -#vim -.*.swp - -#binaries -bin/phpunit -bin/phpcs -bin/php-cs-fixer -bin/sabre-cs-fixer -bin/hoa - -# Development stuff -.php_cs.cache -.idea - -# OS X -.DS_Store -======= - # Development stuff .php_cs.cache diff --git a/.php_cs.dist b/.php_cs.dist index 7c6cf6749..4aaf1d90f 100644 --- a/.php_cs.dist +++ b/.php_cs.dist @@ -6,8 +6,7 @@ $config->getFinder() ->in(__DIR__); $config->setRules([ '@PSR1' => true, - '@Symfony' => true, - 'phpdoc_summary' => false + '@Symfony' => true ]); return $config; diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 4f50e97fd..000000000 --- a/.travis.yml +++ /dev/null @@ -1,49 +0,0 @@ -language: php -php: - - 7.1 - - 7.2 - - 7.3 - - 7.4 - -env: - global: - - MEMCACHED_SERVER=127.0.0.1 - - RUN_PHPCSFIXER="TRUE" - - RUN_PHPUNIT="TRUE" - - RUN_PHPSTAN="FALSE" - matrix: - - PREFER_LOWEST="" REPORT_COVERAGE="TRUE" WITH_COVERAGE="--coverage-clover=coverage.xml" - - PREFER_LOWEST="--prefer-lowest" REPORT_COVERAGE="FALSE" WITH_COVERAGE="" - -matrix: - include: - - name: 'PHP8' - dist: focal - php: nightly - env: - - RUN_PHPCSFIXER="FALSE" - - REPORT_COVERAGE="FALSE" - - name: 'PHPStan' - php: 7.4 - env: - - RUN_PHPCSFIXER="FALSE" - - RUN_PHPUNIT="FALSE" - - RUN_PHPSTAN="TRUE" - - REPORT_COVERAGE="FALSE" - fast_finish: true - -before_script: - - if [ $RUN_PHPCSFIXER == "FALSE" ]; then composer remove --no-update --dev friendsofphp/php-cs-fixer; fi - - composer update $PREFER_LOWEST - -script: - - if [ $RUN_PHPCSFIXER == "TRUE" ]; then php vendor/bin/php-cs-fixer fix --dry-run --diff; fi - - if [ $RUN_PHPUNIT == "TRUE" ]; then php vendor/bin/phpunit --configuration tests/phpunit.xml $WITH_COVERAGE; fi - - if [ $RUN_PHPSTAN == "TRUE" ]; then composer phpstan; fi - -after_success: - - if [ $REPORT_COVERAGE == "TRUE" ]; then bash <(curl -s https://codecov.io/bash); fi - -cache: - directories: - - $HOME/.composer/cache diff --git a/lib/Parser/MimeDir.php b/lib/Parser/MimeDir.php index ecf1944fa..9256af6f6 100644 --- a/lib/Parser/MimeDir.php +++ b/lib/Parser/MimeDir.php @@ -518,7 +518,7 @@ protected function readProperty($line) * * Now for the parameters * - * If delimiter is not set (null) this method will just return a string. + * If delimiter is not set (empty string) this method will just return a string. * If it's a comma or a semi-colon the string will be split on those * characters, and always return an array. * diff --git a/lib/Property.php b/lib/Property.php index 6219c9b67..50cda9684 100644 --- a/lib/Property.php +++ b/lib/Property.php @@ -52,7 +52,7 @@ abstract class Property extends Node * In case this is a multi-value property. This string will be used as a * delimiter. * - * @var string|null + * @var string */ public $delimiter = ';'; diff --git a/lib/Property/Binary.php b/lib/Property/Binary.php index ec6713fdd..1262dd054 100644 --- a/lib/Property/Binary.php +++ b/lib/Property/Binary.php @@ -24,9 +24,9 @@ class Binary extends Property * In case this is a multi-value property. This string will be used as a * delimiter. * - * @var string|null + * @var string */ - public $delimiter = null; + public $delimiter = ''; /** * Updates the current value. diff --git a/lib/Property/FloatValue.php b/lib/Property/FloatValue.php index 0d0346968..e780ae6c1 100644 --- a/lib/Property/FloatValue.php +++ b/lib/Property/FloatValue.php @@ -21,7 +21,7 @@ class FloatValue extends Property * In case this is a multi-value property. This string will be used as a * delimiter. * - * @var string|null + * @var string */ public $delimiter = ';'; diff --git a/lib/Property/ICalendar/CalAddress.php b/lib/Property/ICalendar/CalAddress.php index 86be66c15..2dbbc6eaf 100644 --- a/lib/Property/ICalendar/CalAddress.php +++ b/lib/Property/ICalendar/CalAddress.php @@ -19,9 +19,9 @@ class CalAddress extends Text * In case this is a multi-value property. This string will be used as a * delimiter. * - * @var string|null + * @var string */ - public $delimiter = null; + public $delimiter = ''; /** * Returns the type of value. diff --git a/lib/Property/ICalendar/Duration.php b/lib/Property/ICalendar/Duration.php index 87f008160..e18fe191e 100644 --- a/lib/Property/ICalendar/Duration.php +++ b/lib/Property/ICalendar/Duration.php @@ -22,7 +22,7 @@ class Duration extends Property * In case this is a multi-value property. This string will be used as a * delimiter. * - * @var string|null + * @var string */ public $delimiter = ','; diff --git a/lib/Property/ICalendar/Period.php b/lib/Property/ICalendar/Period.php index eb3752770..ae8a78911 100644 --- a/lib/Property/ICalendar/Period.php +++ b/lib/Property/ICalendar/Period.php @@ -23,7 +23,7 @@ class Period extends Property * In case this is a multi-value property. This string will be used as a * delimiter. * - * @var string|null + * @var string */ public $delimiter = ','; diff --git a/lib/Property/Time.php b/lib/Property/Time.php index 544b5ced3..1b81609aa 100644 --- a/lib/Property/Time.php +++ b/lib/Property/Time.php @@ -19,9 +19,9 @@ class Time extends Text * In case this is a multi-value property. This string will be used as a * delimiter. * - * @var string|null + * @var string */ - public $delimiter = null; + public $delimiter = ''; /** * Returns the type of value. diff --git a/lib/Property/Uri.php b/lib/Property/Uri.php index 830cd3f18..1ad1fb199 100644 --- a/lib/Property/Uri.php +++ b/lib/Property/Uri.php @@ -20,9 +20,9 @@ class Uri extends Text * In case this is a multi-value property. This string will be used as a * delimiter. * - * @var string|null + * @var string */ - public $delimiter = null; + public $delimiter = ''; /** * Returns the type of value. diff --git a/lib/Property/UtcOffset.php b/lib/Property/UtcOffset.php index 248ed40ea..04b88447f 100644 --- a/lib/Property/UtcOffset.php +++ b/lib/Property/UtcOffset.php @@ -17,9 +17,9 @@ class UtcOffset extends Text * In case this is a multi-value property. This string will be used as a * delimiter. * - * @var string|null + * @var string */ - public $delimiter = null; + public $delimiter = ''; /** * Returns the type of value. diff --git a/lib/Property/VCard/DateAndOrTime.php b/lib/Property/VCard/DateAndOrTime.php index 09918b31a..7bf79c48c 100644 --- a/lib/Property/VCard/DateAndOrTime.php +++ b/lib/Property/VCard/DateAndOrTime.php @@ -24,9 +24,9 @@ class DateAndOrTime extends Property /** * Field separator. * - * @var string|null + * @var string */ - public $delimiter = null; + public $delimiter = ''; /** * Returns the type of value. diff --git a/lib/Property/VCard/TimeStamp.php b/lib/Property/VCard/TimeStamp.php index fccf2d600..da6ea3d44 100644 --- a/lib/Property/VCard/TimeStamp.php +++ b/lib/Property/VCard/TimeStamp.php @@ -21,9 +21,9 @@ class TimeStamp extends Text * In case this is a multi-value property. This string will be used as a * delimiter. * - * @var string|null + * @var string */ - public $delimiter = null; + public $delimiter = ''; /** * Returns the type of value. diff --git a/lib/Recur/EventIterator.php b/lib/Recur/EventIterator.php index 310bebe41..61f05d7de 100644 --- a/lib/Recur/EventIterator.php +++ b/lib/Recur/EventIterator.php @@ -198,6 +198,7 @@ public function __construct($input, $uid = null, DateTimeZone $timeZone = null) * * @return DateTimeImmutable */ + #[\ReturnTypeWillChange] public function current() { if ($this->currentDate) { @@ -285,6 +286,7 @@ public function getEventObject() * * @return int */ + #[\ReturnTypeWillChange] public function key() { // The counter is always 1 ahead. @@ -297,6 +299,7 @@ public function key() * * @return bool */ + #[\ReturnTypeWillChange] public function valid() { if ($this->counter > Settings::$maxRecurrences && -1 !== Settings::$maxRecurrences) { @@ -308,7 +311,10 @@ public function valid() /** * Sets the iterator back to the starting point. + * + * @return void */ + #[\ReturnTypeWillChange] public function rewind() { $this->recurIterator->rewind(); @@ -331,7 +337,10 @@ public function rewind() /** * Advances the iterator with one step. + * + * @return void */ + #[\ReturnTypeWillChange] public function next() { $this->currentOverriddenEvent = null; diff --git a/lib/Recur/RDateIterator.php b/lib/Recur/RDateIterator.php index d117e152c..5d56657fa 100644 --- a/lib/Recur/RDateIterator.php +++ b/lib/Recur/RDateIterator.php @@ -35,6 +35,7 @@ public function __construct($rrule, DateTimeInterface $start) /* Implementation of the Iterator interface {{{ */ + #[\ReturnTypeWillChange] public function current() { if (!$this->valid()) { @@ -49,6 +50,7 @@ public function current() * * @return int */ + #[\ReturnTypeWillChange] public function key() { return $this->counter; @@ -60,6 +62,7 @@ public function key() * * @return bool */ + #[\ReturnTypeWillChange] public function valid() { return $this->counter <= count($this->dates); @@ -67,7 +70,10 @@ public function valid() /** * Resets the iterator. + * + * @return void */ + #[\ReturnTypeWillChange] public function rewind() { $this->currentDate = clone $this->startDate; @@ -76,7 +82,10 @@ public function rewind() /** * Goes on to the next iteration. + * + * @return void */ + #[\ReturnTypeWillChange] public function next() { ++$this->counter; diff --git a/lib/Recur/RRuleIterator.php b/lib/Recur/RRuleIterator.php index 3dd483429..a1b97ebee 100644 --- a/lib/Recur/RRuleIterator.php +++ b/lib/Recur/RRuleIterator.php @@ -37,6 +37,7 @@ public function __construct($rrule, DateTimeInterface $start) /* Implementation of the Iterator interface {{{ */ + #[\ReturnTypeWillChange] public function current() { if (!$this->valid()) { @@ -51,6 +52,7 @@ public function current() * * @return int */ + #[\ReturnTypeWillChange] public function key() { return $this->counter; @@ -63,6 +65,7 @@ public function key() * * @return bool */ + #[\ReturnTypeWillChange] public function valid() { if (null === $this->currentDate) { @@ -77,7 +80,10 @@ public function valid() /** * Resets the iterator. + * + * @return void */ + #[\ReturnTypeWillChange] public function rewind() { $this->currentDate = clone $this->startDate; @@ -88,8 +94,10 @@ public function rewind() * Goes on to the next iteration. * * @param int $amount + * @return void */ - public function next($amount = 1) + #[\ReturnTypeWillChange] + public function next(int $amount = 1) { // Otherwise, we find the next event in the normal RRULE // sequence. @@ -640,6 +648,9 @@ protected function nextMonthly($amount = 1) // This goes to 0 because we need to start counting at the // beginning. $currentDayOfMonth = 0; + + // For some reason the "until" parameter was not being used here, + // that's why the workaround of the 10000 year bug was needed at all $currentHourOfMonth = 0; $currentMinuteOfMonth = 0; $currentSecondOfMonth = 0; @@ -650,6 +661,10 @@ protected function nextMonthly($amount = 1) if ($this->until && $this->currentDate->getTimestamp() >= $this->until->getTimestamp()) { return; } + // let's stop it before the "until" parameter date + if ($this->until && $this->currentDate->getTimestamp() >= $this->until->getTimestamp()) { + return; + } // To prevent running this forever (better: until we hit the max date of DateTimeImmutable) we simply // stop at 9999-12-31. Looks like the year 10000 problem is not solved in php .... diff --git a/lib/TimeZoneUtil.php b/lib/TimeZoneUtil.php index 9e382c655..65f2e6c8c 100644 --- a/lib/TimeZoneUtil.php +++ b/lib/TimeZoneUtil.php @@ -33,14 +33,6 @@ class TimeZoneUtil /** @var TimezoneFinder[] */ private $timezoneFinders = []; - // Keeping things for backwards compatibility - /** - * @var array|null - * - * @deprecated - */ - public static $map = null; - private function __construct() { $this->addGuesser('lic', new GuessFromLicEntry()); @@ -128,11 +120,30 @@ public static function addTimezoneFinder(string $key, TimezoneFinder $finder): v self::getInstance()->addFinder($key, $finder); } + /** + * @param string $tzid + * @param false $failIfUncertain + * + * @return DateTimeZone + */ + public static function getTimeZone($tzid, Component $vcalendar = null, $failIfUncertain = false) + { + return self::getInstance()->findTimeZone($tzid, $vcalendar, $failIfUncertain); + } + public static function clean(): void { self::$instance = null; } + // Keeping things for backwards compatibility + /** + * @var array|null + * + * @deprecated + */ + public static $map = null; + /** * List of microsoft exchange timezone ids. * @@ -168,12 +179,12 @@ public static function clean(): void 48 => 'Asia/Kabul', 58 => 'Asia/Yekaterinburg', 47 => 'Asia/Karachi', - 23 => 'Asia/Kolkata', + 23 => 'Asia/Calcutta', 62 => 'Asia/Kathmandu', 46 => 'Asia/Almaty', 71 => 'Asia/Dhaka', 66 => 'Asia/Colombo', - 61 => 'Asia/Yangon', + 61 => 'Asia/Rangoon', 22 => 'Asia/Bangkok', 64 => 'Asia/Krasnoyarsk', 45 => 'Asia/Shanghai', @@ -198,11 +209,11 @@ public static function clean(): void 29 => 'Atlantic/Azores', 53 => 'Atlantic/Cape_Verde', 30 => 'America/Noronha', - 8 => 'America/Sao_Paulo', // Best guess + 8 => 'America/Sao_Paulo', // Best guess 32 => 'America/Argentina/Buenos_Aires', 60 => 'America/Godthab', 28 => 'America/St_Johns', - 9 => 'America/Halifax', + 9 => 'America/Halifax', 33 => 'America/Caracas', 65 => 'America/Santiago', 35 => 'America/Bogota', @@ -222,143 +233,6 @@ public static function clean(): void ]; /** -<<<<<<< HEAD - * This method will try to find out the correct timezone for an iCalendar - * date-time value. - * - * You must pass the contents of the TZID parameter, as well as the full - * calendar. - * - * If the lookup fails, this method will return the default PHP timezone - * (as configured using date_default_timezone_set, or the date.timezone ini - * setting). - * - * Alternatively, if $failIfUncertain is set to true, it will throw an - * exception if we cannot accurately determine the timezone. - * - * @param string $tzid - * @param Sabre\VObject\Component $vcalendar - * - * @return \DateTimeZone - */ - public static function getTimeZone($tzid, Component $vcalendar = null, $failIfUncertain = false) - { - // First we will just see if the tzid is a support timezone identifier. - // - // The only exception is if the timezone starts with (. This is to - // handle cases where certain microsoft products generate timezone - // identifiers that for instance look like: - // - // (GMT+01.00) Sarajevo/Warsaw/Zagreb - // - // Since PHP 5.5.10, the first bit will be used as the timezone and - // this method will return just GMT+01:00. This is wrong, because it - // doesn't take DST into account. - $originalTzid = $tzid; - if ($tzid && '(' !== $tzid[0]) { - // If the timezone is prefixed with a slash we remove the slash for lookup in the maps. - if ('/' === $tzid[0]) { - $tzid = substr($tzid, 1); - } - // PHP has a bug that logs PHP warnings even it shouldn't: - // https://bugs.php.net/bug.php?id=67881 - // - // That's why we're checking if we'll be able to successfully instantiate - // \DateTimeZone() before doing so. Otherwise we could simply instantiate - // and catch the exception. - $tzIdentifiers = \DateTimeZone::listIdentifiers(); - - try { - if ( - (in_array($tzid, $tzIdentifiers)) || - (preg_match('/^GMT(\+|-)([0-9]{4})$/', $tzid, $matches)) || - (in_array($tzid, self::getIdentifiersBC())) - ) { - return new \DateTimeZone($tzid); - } - } catch (\Exception $e) { - } - } - - self::loadTzMaps(); - - // Next, we check if the tzid is somewhere in our tzid map. - if (isset(self::$map[$tzid])) { - return new \DateTimeZone(self::$map[$tzid]); - } - - // If we removed the slash we add it back - $tzid = $originalTzid; - - // Some Microsoft products prefix the offset first, so let's strip that off - // and see if it is our tzid map. We don't want to check for this first just - // in case there are overrides in our tzid map. - if (preg_match('/^\((UTC|GMT)(\+|\-)[\d]{2}\:[\d]{2}\) (.*)/', $tzid, $matches)) { - $tzidAlternate = $matches[3]; - if (isset(self::$map[$tzidAlternate])) { - return new \DateTimeZone(self::$map[$tzidAlternate]); - } - } - - // Maybe the author was hyper-lazy and just included an offset. We - // support it, but we aren't happy about it. - if (preg_match('/^GMT(\+|-)([0-9]{4})$/', $tzid, $matches)) { - // Note that the path in the source will never be taken from PHP 5.5.10 - // onwards. PHP 5.5.10 supports the "GMT+0100" style of format, so it - // already gets returned early in this function. Once we drop support - // for versions under PHP 5.5.10, this bit can be taken out of the - // source. - // @codeCoverageIgnoreStart - return new \DateTimeZone('Etc/GMT'.$matches[1].ltrim(substr($matches[2], 0, 2), '0')); - // @codeCoverageIgnoreEnd - } - - if ($vcalendar) { - // If that didn't work, we will scan VTIMEZONE objects - foreach ($vcalendar->select('VTIMEZONE') as $vtimezone) { - if ((string) $vtimezone->TZID === $tzid) { - // Some clients add 'X-LIC-LOCATION' with the olson name. - if (isset($vtimezone->{'X-LIC-LOCATION'})) { - $lic = (string) $vtimezone->{'X-LIC-LOCATION'}; - - // Libical generators may specify strings like - // "SystemV/EST5EDT". For those we must remove the - // SystemV part. - if ('SystemV/' === substr($lic, 0, 8)) { - $lic = substr($lic, 8); - } - - return self::getTimeZone($lic, null, $failIfUncertain); - } - // Microsoft may add a magic number, which we also have an - // answer for. - if (isset($vtimezone->{'X-MICROSOFT-CDO-TZID'})) { - $cdoId = (int) $vtimezone->{'X-MICROSOFT-CDO-TZID'}->getValue(); - - // 2 can mean both Europe/Lisbon and Europe/Sarajevo. - if (2 === $cdoId && false !== strpos((string) $vtimezone->TZID, 'Sarajevo')) { - return new \DateTimeZone('Europe/Sarajevo'); - } - - if (isset(self::$microsoftExchangeMap[$cdoId])) { - return new \DateTimeZone(self::$microsoftExchangeMap[$cdoId]); - } - } - } - } - } - - if ($failIfUncertain) { - throw new \InvalidArgumentException('We were unable to determine the correct PHP timezone for tzid: '.$tzid); - } - - // If we got all the way here, we default to UTC. - return new \DateTimeZone(date_default_timezone_get()); - } - - /** -======= ->>>>>>> upstream/master * This method will load in all the tz mapping information, if it's not yet * done. * @@ -396,4 +270,4 @@ public static function getIdentifiersBC() { return include __DIR__.'/timezonedata/php-bc.php'; } -} +} \ No newline at end of file diff --git a/lib/TimezoneGuesser/FindFromTimezoneIdentifier.php b/lib/TimezoneGuesser/FindFromTimezoneIdentifier.php index d8c6d5d3b..b5c58f715 100644 --- a/lib/TimezoneGuesser/FindFromTimezoneIdentifier.php +++ b/lib/TimezoneGuesser/FindFromTimezoneIdentifier.php @@ -31,6 +31,12 @@ public function find(string $tzid, bool $failIfUncertain = false): ?DateTimeZone if ('(' === $tzid[0]) { return null; } + + // If the timezone is prefixed with a slash we remove the slash for lookup in the maps. + if ('/' === $tzid[0]) { + $tzid = substr($tzid, 1); + } + // PHP has a bug that logs PHP warnings even it shouldn't: // https://bugs.php.net/bug.php?id=67881 // diff --git a/lib/TimezoneGuesser/FindFromTimezoneMap.php b/lib/TimezoneGuesser/FindFromTimezoneMap.php index b52ba6a19..0a6b608b4 100644 --- a/lib/TimezoneGuesser/FindFromTimezoneMap.php +++ b/lib/TimezoneGuesser/FindFromTimezoneMap.php @@ -59,7 +59,8 @@ private function getTzMaps() include __DIR__.'/../timezonedata/windowszones.php', include __DIR__.'/../timezonedata/lotuszones.php', include __DIR__.'/../timezonedata/exchangezones.php', - include __DIR__.'/../timezonedata/php-workaround.php' + include __DIR__.'/../timezonedata/php-workaround.php', + include __DIR__.'/../timezonedata/extrazones.php' ); } diff --git a/tests/VObject/Recur/FastForwardBeforeTest.php b/tests/VObject/Recur/FastForwardBeforeTest.php index ba71d23dd..3461bd6a5 100644 --- a/tests/VObject/Recur/FastForwardBeforeTest.php +++ b/tests/VObject/Recur/FastForwardBeforeTest.php @@ -282,39 +282,51 @@ public function testFastForwardBeforeMonthly31thDay() $this->fastForward($rrule, $ffDate); - $expected = (new DateTime('midnight', new DateTimeZone('America/New_York'))) + $expected = (new \DateTime('midnight', new DateTimeZone($timezone))) ->setDate(18000, 1, 31) ->getTimestamp(); $this->assertEquals($expected, $rrule->current()->getTimestamp()); // march $rrule->next(); - $expected += (29 + 31) * 24 * 60 * 60; + $expected = (new \DateTime('1970-01-31 00:00:00', new DateTimeZone($timezone))) + ->setDate(18000, 3, 31) + ->getTimestamp(); $this->assertEquals($expected, $rrule->current()->getTimestamp()); // may $rrule->next(); - $expected += (30 + 31) * 24 * 60 * 60; + $expected = (new \DateTime('1970-01-31 00:00:00', new DateTimeZone($timezone))) + ->setDate(18000, 5, 31) + ->getTimestamp(); $this->assertEquals($expected, $rrule->current()->getTimestamp()); // july $rrule->next(); - $expected += (30 + 31) * 24 * 60 * 60; + $expected = (new \DateTime('1970-01-31 00:00:00', new DateTimeZone($timezone))) + ->setDate(18000, 7, 31) + ->getTimestamp(); $this->assertEquals($expected, $rrule->current()->getTimestamp()); // august $rrule->next(); - $expected += 31 * 24 * 60 * 60; + $expected = (new \DateTime('1970-01-31 00:00:00', new DateTimeZone($timezone))) + ->setDate(18000, 8, 31) + ->getTimestamp(); $this->assertEquals($expected, $rrule->current()->getTimestamp()); // october $rrule->next(); - $expected += (30 + 31) * 24 * 60 * 60; + $expected = (new \DateTime('1970-01-31 00:00:00', new DateTimeZone($timezone))) + ->setDate(18000, 10, 31) + ->getTimestamp(); $this->assertEquals($expected, $rrule->current()->getTimestamp()); // december $rrule->next(); - $expected += (30 + 31) * 24 * 60 * 60; + $expected = (new \DateTime('1970-01-31 00:00:00', new DateTimeZone($timezone))) + ->setDate(18000, 12, 31) + ->getTimestamp(); $this->assertEquals($expected, $rrule->current()->getTimestamp()); } @@ -336,37 +348,52 @@ public function testFastForwardBeforeMonthlyAdvanced() $this->assertEquals($expected, $rrule->current()->getTimestamp()); // tuesday - $expected += 8 * 24 * 60 * 60; + $expected = (new \DateTime('1970-01-31 00:00:00', new DateTimeZone($timezone))) + ->setDate(8000, 1, 11) + ->getTimestamp(); $rrule->next(); $this->assertEquals($expected, $rrule->current()->getTimestamp()); // wednesday - $expected += 8 * 24 * 60 * 60; + $expected = (new \DateTime('1970-01-31 00:00:00', new DateTimeZone($timezone))) + ->setDate(8000, 1, 19) + ->getTimestamp(); $rrule->next(); $this->assertEquals($expected, $rrule->current()->getTimestamp()); // thursday - $expected += 8 * 24 * 60 * 60; + $expected = (new \DateTime('1970-01-31 00:00:00', new DateTimeZone($timezone))) + ->setDate(8000, 1, 27) + ->getTimestamp(); $rrule->next(); $this->assertEquals($expected, $rrule->current()->getTimestamp()); // monday march - $expected += (29 + 10) * 24 * 60 * 60; + $expected = (new \DateTime('1970-01-31 00:00:00', new DateTimeZone($timezone))) + ->setDate(8000, 3, 6) + ->getTimestamp(); $rrule->next(); $this->assertEquals($expected, $rrule->current()->getTimestamp()); // tuesday - $expected += 8 * 24 * 60 * 60; + $expected = (new \DateTime('1970-01-31 00:00:00', new DateTimeZone($timezone))) + ->setDate(8000, 3, 14) + ->getTimestamp(); $rrule->next(); $this->assertEquals($expected, $rrule->current()->getTimestamp()); // wednesday (this month starts on wednesday so that's just the next day) - $expected += 1 * 24 * 60 * 60; + $expected = (new \DateTime('1970-01-31 00:00:00', new DateTimeZone($timezone))) + ->setDate(8000, 3, 15) + ->getTimestamp(); $rrule->next(); $this->assertEquals($expected, $rrule->current()->getTimestamp()); // thursday $expected += 8 * 24 * 60 * 60; + $expected = (new \DateTime('1970-01-31 00:00:00', new DateTimeZone($timezone))) + ->setDate(8000, 3, 23) + ->getTimestamp(); $rrule->next(); $this->assertEquals($expected, $rrule->current()->getTimestamp()); } diff --git a/tests/VObject/Recur/FastForwardTest.php b/tests/VObject/Recur/FastForwardTest.php index df9120561..7a305b4ad 100644 --- a/tests/VObject/Recur/FastForwardTest.php +++ b/tests/VObject/Recur/FastForwardTest.php @@ -261,32 +261,45 @@ public function testFastForwardMonthly31thDay() // march $rrule->next(); - $expected += (29 + 31) * 24 * 60 * 60; + $expected = (new DateTime('midnight', new DateTimeZone('America/New_York'))) + ->setDate(18000, 3, 31) + ->getTimestamp(); $this->assertEquals($expected, $rrule->current()->getTimestamp()); // may $rrule->next(); $expected += (30 + 31) * 24 * 60 * 60; + $expected = (new DateTime('midnight', new DateTimeZone('America/New_York'))) + ->setDate(18000, 5, 31) + ->getTimestamp(); $this->assertEquals($expected, $rrule->current()->getTimestamp()); // july $rrule->next(); - $expected += (30 + 31) * 24 * 60 * 60; + $expected = (new DateTime('midnight', new DateTimeZone('America/New_York'))) + ->setDate(18000, 7, 31) + ->getTimestamp(); $this->assertEquals($expected, $rrule->current()->getTimestamp()); // august $rrule->next(); - $expected += 31 * 24 * 60 * 60; + $expected = (new DateTime('midnight', new DateTimeZone('America/New_York'))) + ->setDate(18000, 8, 31) + ->getTimestamp(); $this->assertEquals($expected, $rrule->current()->getTimestamp()); // october $rrule->next(); - $expected += (30 + 31) * 24 * 60 * 60; + $expected = (new DateTime('midnight', new DateTimeZone('America/New_York'))) + ->setDate(18000, 10, 31) + ->getTimestamp(); $this->assertEquals($expected, $rrule->current()->getTimestamp()); // december $rrule->next(); - $expected += (30 + 31) * 24 * 60 * 60; + $expected = (new DateTime('midnight', new DateTimeZone('America/New_York'))) + ->setDate(18000, 12, 31) + ->getTimestamp(); $this->assertEquals($expected, $rrule->current()->getTimestamp()); } @@ -307,37 +320,51 @@ public function testFastForwardMonthlyAdvanced() $this->assertEquals($expected, $rrule->current()->getTimestamp()); // tuesday - $expected += 8 * 24 * 60 * 60; + $expected = (new DateTime('midnight', new DateTimeZone($timezone))) + ->setDate(8000, 1, 11) + ->getTimestamp(); $rrule->next(); $this->assertEquals($expected, $rrule->current()->getTimestamp()); // wednesday - $expected += 8 * 24 * 60 * 60; + $expected = (new DateTime('midnight', new DateTimeZone($timezone))) + ->setDate(8000, 1, 19) + ->getTimestamp(); $rrule->next(); $this->assertEquals($expected, $rrule->current()->getTimestamp()); // thursday - $expected += 8 * 24 * 60 * 60; + $expected = (new DateTime('midnight', new DateTimeZone($timezone))) + ->setDate(8000, 1, 27) + ->getTimestamp(); $rrule->next(); $this->assertEquals($expected, $rrule->current()->getTimestamp()); // monday march - $expected += (29 + 10) * 24 * 60 * 60; + $expected = (new DateTime('midnight', new DateTimeZone($timezone))) + ->setDate(8000, 3, 6) + ->getTimestamp(); $rrule->next(); $this->assertEquals($expected, $rrule->current()->getTimestamp()); // tuesday - $expected += 8 * 24 * 60 * 60; + $expected = (new DateTime('midnight', new DateTimeZone($timezone))) + ->setDate(8000, 3, 14) + ->getTimestamp(); $rrule->next(); $this->assertEquals($expected, $rrule->current()->getTimestamp()); // wednesday (this month starts on wednesday so that's just the next day) - $expected += 1 * 24 * 60 * 60; + $expected = (new DateTime('midnight', new DateTimeZone($timezone))) + ->setDate(8000, 3, 15) + ->getTimestamp(); $rrule->next(); $this->assertEquals($expected, $rrule->current()->getTimestamp()); // thursday - $expected += 8 * 24 * 60 * 60; + $expected = (new DateTime('midnight', new DateTimeZone($timezone))) + ->setDate(8000, 3, 23) + ->getTimestamp(); $rrule->next(); $this->assertEquals($expected, $rrule->current()->getTimestamp()); } diff --git a/tests/VObject/Recur/FastForwardToEndTest.php b/tests/VObject/Recur/FastForwardToEndTest.php index 9f54d8386..992f2b9d8 100644 --- a/tests/VObject/Recur/FastForwardToEndTest.php +++ b/tests/VObject/Recur/FastForwardToEndTest.php @@ -186,9 +186,7 @@ public function testFastForwardToEndUntilMonthlyBasic() } /** - * FIXME fails in <=PHP 7.1. - * - * @requires PHP 7.2 + * @requires PHP < 8.1 */ public function testFastForwardToEndCountMonthly31thDay() { @@ -204,6 +202,22 @@ public function testFastForwardToEndCountMonthly31thDay() $this->assertEquals($expected, $rrule->current()->getTimestamp()); } + /** + * @requires PHP >= 8.1 + */ + public function testFastForwardToEndCountMonthly31thDayPHP81() + { + $startDate = new \DateTime('1970-01-31 00:00:00', new \DateTimeZone('America/New_York')); + $rrule = new RRuleIterator('FREQ=MONTHLY;COUNT=10000', $startDate); + + // We do not enforce the timing in case of a count rule as we cannot optimize it + $this->fastForwardToEnd($rrule, false); + + $expected = (new DateTime('midnight', new DateTimeZone('America/New_York'))) + ->setDate(3398, 7, 31); + $this->assertEquals($expected->getTimestamp(), $rrule->current()->getTimestamp()); + } + public function testFastForwardToEndUntilMonthly31thDay() { $startDate = new \DateTime('1970-01-31 00:00:00', new \DateTimeZone('America/New_York')); diff --git a/tests/VObject/TimeZoneUtilTest.php b/tests/VObject/TimeZoneUtilTest.php index 33a1eff98..e6df79aed 100644 --- a/tests/VObject/TimeZoneUtilTest.php +++ b/tests/VObject/TimeZoneUtilTest.php @@ -8,8 +8,6 @@ class TimeZoneUtilTest extends TestCase { public function setUp(): void { - // clearning the tz cache - TimeZoneUtil::$map = null; TimeZoneUtil::clean(); } @@ -36,7 +34,8 @@ public function getMapping() include __DIR__.'/../../lib/timezonedata/windowszones.php', include __DIR__.'/../../lib/timezonedata/lotuszones.php', include __DIR__.'/../../lib/timezonedata/exchangezones.php', - include __DIR__.'/../../lib/timezonedata/php-workaround.php' + include __DIR__.'/../../lib/timezonedata/php-workaround.php', + include __DIR__.'/../../lib/timezonedata/extrazones.php' ); // PHPUNit requires an array of arrays From a5a584701f2aeef42b3ed071414250481b8bce5f Mon Sep 17 00:00:00 2001 From: Valentin BONNEAUD Date: Wed, 15 Dec 2021 16:47:38 +0100 Subject: [PATCH 40/92] Prepare release 4.7.0 (#38) --- CHANGELOG.md | 1 + lib/Version.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b01f32d2f..a35cca2cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ChangeLog 4.7.0 (2021-12-15) ------------------ * #34 Merge upstream changes from sabre-io/vobject:4.4.0 into protonlabs/vobject +* #36 Merge upstream changes from sabre-io/vobject:4.4.1 into protonlabs/vobject 4.6.1 (2021-11-04) ------------------ diff --git a/lib/Version.php b/lib/Version.php index d0d1337fd..84b5b5843 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -14,5 +14,5 @@ class Version /** * Full version number. */ - const VERSION = '4.6.1'; + const VERSION = '4.7.0'; } From 7fdf3bb801fd0d254989fc1be493def265d26807 Mon Sep 17 00:00:00 2001 From: giuseppe-arcuti <93533561+giuseppe-arcuti@users.noreply.github.com> Date: Mon, 10 Jan 2022 16:09:06 +0100 Subject: [PATCH 41/92] Support lowercase timezones (#39) --- lib/TimezoneGuesser/FindFromTimezoneMap.php | 10 +++++++--- tests/VObject/TimeZoneUtilTest.php | 7 +++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/TimezoneGuesser/FindFromTimezoneMap.php b/lib/TimezoneGuesser/FindFromTimezoneMap.php index 0a6b608b4..80d8e5c7b 100644 --- a/lib/TimezoneGuesser/FindFromTimezoneMap.php +++ b/lib/TimezoneGuesser/FindFromTimezoneMap.php @@ -55,13 +55,17 @@ public function find(string $tzid, bool $failIfUncertain = false): ?DateTimeZone private function getTzMaps() { if ([] === $this->map) { - $this->map = array_merge( + $map = array_merge( include __DIR__.'/../timezonedata/windowszones.php', include __DIR__.'/../timezonedata/lotuszones.php', include __DIR__.'/../timezonedata/exchangezones.php', include __DIR__.'/../timezonedata/php-workaround.php', include __DIR__.'/../timezonedata/extrazones.php' ); + $this->map = array_combine( + array_map(static fn (string $key) => mb_strtolower($key, 'UTF-8'), array_keys($map)), + array_values($map), + ); } return $this->map; @@ -69,11 +73,11 @@ private function getTzMaps() private function getTzFromMap(string $tzid): string { - return $this->getTzMaps()[$tzid]; + return $this->getTzMaps()[mb_strtolower($tzid, 'UTF-8')]; } private function hasTzInMap(string $tzid): bool { - return isset($this->getTzMaps()[$tzid]); + return isset($this->getTzMaps()[mb_strtolower($tzid, 'UTF-8')]); } } diff --git a/tests/VObject/TimeZoneUtilTest.php b/tests/VObject/TimeZoneUtilTest.php index e6df79aed..437a5aa13 100644 --- a/tests/VObject/TimeZoneUtilTest.php +++ b/tests/VObject/TimeZoneUtilTest.php @@ -177,6 +177,13 @@ public function testWindowsTimeZone() $this->assertEquals($ex->getName(), $tz->getName()); } + public function testLowerCaseTimeZone() + { + $tz = TimeZoneUtil::getTimeZone('mountain time (us & canada)'); + $ex = new \DateTimeZone('America/Denver'); + $this->assertEquals($ex->getName(), $tz->getName()); + } + /** * @dataProvider getPHPTimeZoneIdentifiers */ From 8e3e2ad5fec75f0dd2a613d80c85fbec3a532510 Mon Sep 17 00:00:00 2001 From: giuseppe-arcuti <93533561+giuseppe-arcuti@users.noreply.github.com> Date: Tue, 11 Jan 2022 11:03:57 +0100 Subject: [PATCH 42/92] Prepare release 4.7.1 (#40) --- CHANGELOG.md | 4 ++++ lib/Version.php | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a35cca2cf..b9b928d64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ ChangeLog ========= +4.7.1 (2022-01-10) +------------------ +* #39 Add support for lowercase timezones + 4.7.0 (2021-12-15) ------------------ * #34 Merge upstream changes from sabre-io/vobject:4.4.0 into protonlabs/vobject diff --git a/lib/Version.php b/lib/Version.php index 84b5b5843..b303f31e5 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -14,5 +14,5 @@ class Version /** * Full version number. */ - const VERSION = '4.7.0'; + const VERSION = '4.7.1'; } From 059aabc79aac2fac9005f3e04c4b0868c3162001 Mon Sep 17 00:00:00 2001 From: giuseppe-arcuti <93533561+giuseppe-arcuti@users.noreply.github.com> Date: Fri, 21 Jan 2022 08:07:36 +0100 Subject: [PATCH 43/92] Add missing microsoft timezones and test with confluence file (#41) --- lib/TimezoneGuesser/FindFromTimezoneMap.php | 4 +- lib/timezonedata/windowszones.php | 87 +++ tests/VObject/TimeZoneUtilTest.php | 11 + .../microsoft-timezones-confluence.csv | 548 ++++++++++++++++++ 4 files changed, 649 insertions(+), 1 deletion(-) create mode 100644 tests/VObject/microsoft-timezones-confluence.csv diff --git a/lib/TimezoneGuesser/FindFromTimezoneMap.php b/lib/TimezoneGuesser/FindFromTimezoneMap.php index 80d8e5c7b..4e0a64e22 100644 --- a/lib/TimezoneGuesser/FindFromTimezoneMap.php +++ b/lib/TimezoneGuesser/FindFromTimezoneMap.php @@ -20,6 +20,8 @@ class FindFromTimezoneMap implements TimezoneFinder public function find(string $tzid, bool $failIfUncertain = false): ?DateTimeZone { + $tzid = str_replace(".", "", $tzid); + // Next, we check if the tzid is somewhere in our tzid map. if ($this->hasTzInMap($tzid)) { return new DateTimeZone($this->getTzFromMap($tzid)); @@ -63,7 +65,7 @@ private function getTzMaps() include __DIR__.'/../timezonedata/extrazones.php' ); $this->map = array_combine( - array_map(static fn (string $key) => mb_strtolower($key, 'UTF-8'), array_keys($map)), + array_map(static fn (string $key) => str_replace(".", "", mb_strtolower($key, 'UTF-8')), array_keys($map)), array_values($map), ); } diff --git a/lib/timezonedata/windowszones.php b/lib/timezonedata/windowszones.php index 2049a95c1..0c8329d23 100644 --- a/lib/timezonedata/windowszones.php +++ b/lib/timezonedata/windowszones.php @@ -149,4 +149,91 @@ 'West Pacific Standard Time' => 'Pacific/Port_Moresby', 'Yakutsk Standard Time' => 'Asia/Yakutsk', 'Yukon Standard Time' => 'America/Whitehorse', + 'coordinated universal time-11' => 'Pacific/Pago_Pago', + 'aleutian islands' => 'America/Adak', + 'marquesas islands' => 'Pacific/Marquesas', + 'coordinated universal time-09' => 'America/Anchorage', + 'baja california' => 'America/Tijuana', + 'coordinated universal time-08' => 'Pacific/Pitcairn', + 'chihuahua, la paz, mazatlan' => 'America/Chihuahua', + 'easter island' => 'Pacific/Easter', + 'guadalajara, mexico city, monterrey' => 'America/Mexico_City', + 'bogota, lima, quito, rio branco' => 'America/Bogota', + 'chetumal' => 'America/Cancun', + 'haiti' => 'America/Port-au-Prince', + 'havana' => 'America/Havana', + 'turks and caicos' => 'America/Grand_Turk', + 'asuncion' => 'America/Asuncion', + 'caracas' => 'America/Caracas', + 'cuiaba' => 'America/Cuiaba', + 'georgetown, la paz, manaus, san juan' => 'America/La_Paz', + 'araguaina' => 'America/Araguaina', + 'cayenne, fortaleza' => 'America/Cayenne', + 'city of buenos aires' => 'America/Argentina/Buenos_Aires', + 'punta arenas' => 'America/Punta_Arenas', + 'saint pierre and miquelon' => 'America/Miquelon', + 'salvador' => 'America/Bahia', + 'coordinated universal time-02' => 'America/Noronha', + 'mid-atlantic - old' => 'America/Noronha', + 'cabo verde is' => 'Atlantic/Cape_Verde', + 'coordinated universal time' => 'UTC', + 'dublin, edinburgh, lisbon, london' => 'Europe/London', + 'monrovia, reykjavik' => 'Atlantic/Reykjavik', + 'belgrade, bratislava, budapest, ljubljana, prague' => 'Europe/Budapest', + 'casablanca' => 'Africa/Casablanca', + 'sao tome' => 'Africa/Sao_Tome', + 'sarajevo, skopje, warsaw, zagreb' => 'Europe/Warsaw', + 'amman' => 'Asia/Amman', + 'athens, bucharest' => 'Europe/Bucharest', + 'beirut' => 'Asia/Beirut', + 'chisinau' => 'Europe/Chisinau', + 'damascus' => 'Asia/Damascus', + 'gaza, hebron' => 'Asia/Hebron', + 'jerusalem' => 'Asia/Jerusalem', + 'kaliningrad' => 'Europe/Kaliningrad', + 'khartoum' => 'Africa/Khartoum', + 'tripoli' => 'Africa/Tripoli', + 'windhoek' => 'Africa/Windhoek', + 'istanbul' => 'Europe/Istanbul', + 'kuwait, riyadh' => 'Asia/Riyadh', + 'minsk' => 'Europe/Minsk', + 'moscow, st petersburg' => 'Europe/Moscow', + 'nairobi' => 'Africa/Nairobi', + 'astrakhan, ulyanovsk' => 'Europe/Astrakhan', + 'izhevsk, samara' => 'Europe/Samara', + 'port louis' => 'Indian/Mauritius', + 'saratov' => 'Europe/Saratov', + 'ashgabat, tashkent' => 'Asia/Tashkent', + 'islamabad, karachi' => 'Asia/Karachi', + 'chennai, kolkata, mumbai, new delhi' => 'Asia/Kolkata', + 'sri jayawardenepura' => 'Asia/Colombo', + 'kathmandu' => 'Asia/Kathmandu', + 'astana' => 'Asia/Almaty', + 'dhaka' => 'Asia/Dhaka', + 'yangon (rangoon)' => 'Asia/Rangoon', + 'barnaul, gorno-altaysk' => 'Asia/Barnaul', + 'tomsk' => 'Asia/Tomsk', + 'beijing, chongqing, hong kong, urumqi' => 'Asia/Shanghai', + 'perth' => 'Australia/Perth', + 'ulaanbaatar' => 'Asia/Ulaanbaatar', + 'eucla' => 'Australia/Eucla', + 'chita' => 'Asia/Chita', + 'seoul' => 'Asia/Seoul', + 'adelaide' => 'Australia/Adelaide', + 'brisbane' => 'Australia/Brisbane', + 'canberra, melbourne, sydney' => 'Australia/Sydney', + 'hobart' => 'Australia/Hobart', + 'lord howe island' => 'Australia/Lord_Howe', + 'bougainville island' => 'Pacific/Bougainville', + 'chokurdakh' => 'Asia/Srednekolymsk', + 'norfolk island' => 'Pacific/Norfolk', + 'solomon is, new caledonia' => 'Pacific/Guadalcanal', + 'anadyr, petropavlovsk-kamchatsky' => 'Asia/Kamchatka', + 'coordinated universal time+12' => 'Pacific/Tarawa', + 'petropavlovsk-kamchatsky - old' => 'Asia/Anadyr', + 'chatham islands' => 'Pacific/Chatham', + 'coordinated universal time+13' => 'Pacific/Enderbury', + "nuku'alofa" => 'Pacific/Tongatapu', + 'kiritimati island' => 'Pacific/Kiritimati', + 'helsinki, kyiv, riga, sofia, tallinn, vilnius' => 'Europe/Helsinki', ]; diff --git a/tests/VObject/TimeZoneUtilTest.php b/tests/VObject/TimeZoneUtilTest.php index 437a5aa13..2abdd87f2 100644 --- a/tests/VObject/TimeZoneUtilTest.php +++ b/tests/VObject/TimeZoneUtilTest.php @@ -433,5 +433,16 @@ public function unSupportTimezoneProvider(): iterable 'origin' => 'EDT', 'expected' => 'America/Manaus', ]; + + if (($handle = fopen(__DIR__ . "/microsoft-timezones-confluence.csv", "r")) !== FALSE) { + $data = fgetcsv($handle); + while (($data = fgetcsv($handle)) !== FALSE) { + yield $data[0] => [ + 'origin' => $data[0], + 'expected' => $data[2] !== '' ? $data[2] : $data[1], + ]; + } + fclose($handle); + } } } diff --git a/tests/VObject/microsoft-timezones-confluence.csv b/tests/VObject/microsoft-timezones-confluence.csv new file mode 100644 index 000000000..2b555de77 --- /dev/null +++ b/tests/VObject/microsoft-timezones-confluence.csv @@ -0,0 +1,548 @@ +Original timezone,Replacement,Proposed,Is manual +"abu dhabi, muscat",Asia/Dubai,Asia/Muscat,TRUE +acre,America/Rio_Branco,America/Rio_Branco,FALSE +"adelaide, central australia",Australia/Adelaide,Australia/Adelaide,FALSE +afghanistan,Asia/Kabul,Asia/Kabul,FALSE +afghanistan standard time,Asia/Kabul,Asia/Kabul,FALSE +africa central,Africa/Maputo,Africa/Maputo,FALSE +africa eastern,Africa/Nairobi,Africa/Nairobi,FALSE +africa farwestern,Africa/El_Aaiun,Africa/El_Aaiun,FALSE +africa southern,Africa/Johannesburg,Africa/Johannesburg,FALSE +africa western,Africa/Lagos,Africa/Lagos,FALSE +aktyubinsk,Asia/Aqtobe,Asia/Aqtobe,FALSE +alaska,America/Anchorage,America/Anchorage,FALSE +alaska hawaii,America/Anchorage,America/Anchorage,FALSE +alaskan,America/Anchorage,America/Anchorage,FALSE +alaskan standard time,America/Anchorage,America/Anchorage,FALSE +aleutian standard time,America/Adak,America/Adak,FALSE +almaty,Asia/Almaty,Asia/Almaty,FALSE +"almaty, novosibirsk, north central asia",Asia/Almaty,Asia/Almaty,FALSE +altai standard time,Asia/Barnaul,Asia/Barnaul,FALSE +amazon,America/Manaus,America/Manaus,FALSE +america central,America/Chicago,America/Chicago,FALSE +america eastern,America/New_York,America/New_York,FALSE +america mountain,America/Denver,America/Denver,FALSE +america pacific,America/Los_Angeles,America/Los_Angeles,FALSE +"amsterdam, berlin, bern, rome, stockholm, vienna",Europe/Berlin,Europe/Berlin,FALSE +anadyr,Asia/Anadyr,Asia/Anadyr,FALSE +apia,Pacific/Apia,Pacific/Apia,FALSE +aqtau,Asia/Aqtau,Asia/Aqtau,FALSE +aqtobe,Asia/Aqtobe,Asia/Aqtobe,FALSE +arab,Asia/Riyadh,Asia/Kuwait,TRUE +arab standard time,Asia/Riyadh,Asia/Riyadh,FALSE +"arab, kuwait, riyadh",Asia/Riyadh,Asia/Kuwait,TRUE +arabian,Asia/Dubai,Asia/Muscat,TRUE +arabian standard time,Asia/Dubai,Asia/Dubai,FALSE +arabic,Asia/Baghdad,Asia/Baghdad,FALSE +arabic standard time,Asia/Baghdad,Asia/Baghdad,FALSE +argentina,America/Argentina/Buenos_Aires,America/Argentina/Buenos_Aires,FALSE +argentina standard time,America/Argentina/Buenos_Aires,America/Argentina/Buenos_Aires,FALSE +argentina western,America/Argentina/San_Luis,America/Argentina/San_Luis,FALSE +arizona,America/Phoenix,America/Phoenix,FALSE +armenia,Asia/Yerevan,Asia/Yerevan,FALSE +armenian,Asia/Yerevan,Asia/Yerevan,FALSE +armenian standard time,Asia/Yerevan,Asia/Yerevan,FALSE +ashkhabad,Asia/Ashgabat,Asia/Ashgabat,FALSE +"astana, dhaka",Asia/Dhaka,Asia/Dhaka,FALSE +astrakhan standard time,Europe/Astrakhan,Europe/Astrakhan,FALSE +"athens, istanbul, minsk",Europe/Athens,Europe/Athens,FALSE +atlantic,America/Halifax,America/Halifax,FALSE +atlantic standard time,America/Halifax,America/Halifax,FALSE +atlantic time (canada),America/Halifax,America/Halifax,FALSE +"auckland, wellington",Pacific/Auckland,Pacific/Auckland,FALSE +aus central,Australia/Darwin,Australia/Darwin,FALSE +aus central standard time,Australia/Darwin,Australia/Darwin,FALSE +aus central w standard time,Australia/Eucla,Australia/Eucla,FALSE +aus eastern,Australia/Sydney,Australia/Sydney,FALSE +aus eastern standard time,Australia/Sydney,Australia/Sydney,FALSE +australia central,Australia/Adelaide,Australia/Adelaide,FALSE +australia centralwestern,Australia/Eucla,Australia/Eucla,FALSE +australia eastern,Australia/Sydney,Australia/Sydney,FALSE +australia western,Australia/Perth,Australia/Perth,FALSE +azerbaijan,Asia/Baku,Asia/Baku,FALSE +azerbaijan standard time,Asia/Baku,Asia/Baku,FALSE +azerbijan,Asia/Baku,Asia/Baku,FALSE +azores,Atlantic/Azores,Atlantic/Azores,FALSE +azores standard time,Atlantic/Azores,Atlantic/Azores,FALSE +baghdad,Asia/Baghdad,Asia/Baghdad,FALSE +bahia standard time,America/Bahia,America/Bahia,FALSE +baku,Asia/Baku,Asia/Baku,FALSE +"baku, tbilisi, yerevan",Asia/Baku,Asia/Baku,FALSE +"bangkok, hanoi, jakarta",Asia/Bangkok,Asia/Bangkok,FALSE +bangladesh,Asia/Dhaka,Asia/Dhaka,FALSE +bangladesh standard time,Asia/Dhaka,Asia/Dhaka,FALSE +"beijing, chongqing, hong kong sar, urumqi",Asia/Shanghai,Asia/Shanghai,FALSE +belarus standard time,Europe/Minsk,Europe/Minsk,FALSE +"belgrade, pozsony, budapest, ljubljana, prague",Europe/Prague,Europe/Prague,FALSE +bering,America/Adak,America/Adak,FALSE +bhutan,Asia/Thimphu,Asia/Thimphu,FALSE +"bogota, lima, quito",America/Bogota,America/Bogota,FALSE +bolivia,America/La_Paz,America/La_Paz,FALSE +borneo,Asia/Kuching,Asia/Kuching,FALSE +bougainville standard time,Pacific/Bougainville,Pacific/Bougainville,FALSE +brasilia,America/Sao_Paulo,America/Sao_Paulo,FALSE +"brisbane, east australia",Australia/Brisbane,Australia/Brisbane,FALSE +british,Europe/London,Europe/London,FALSE +brunei,Asia/Brunei,Asia/Brunei,FALSE +"brussels, copenhagen, madrid, paris",Europe/Paris,Europe/Paris,FALSE +bucharest,Europe/Bucharest,Europe/Bucharest,FALSE +buenos aires,America/Argentina/Buenos_Aires,America/Argentina/Buenos_Aires,FALSE +cairo,Africa/Cairo,Africa/Cairo,FALSE +canada central,America/Edmonton,America/Edmonton,FALSE +canada central standard time,America/Regina,America/Regina,FALSE +"canberra, melbourne, sydney, hobart (year 2000 only)",Australia/Sydney,Australia/Sydney,FALSE +cape verde,Atlantic/Cape_Verde,Atlantic/Cape_Verde,FALSE +cape verde is,Atlantic/Cape_Verde,Atlantic/Cape_Verde,FALSE +cape verde standard time,Atlantic/Cape_Verde,Atlantic/Cape_Verde,FALSE +"caracas, la paz",America/Caracas,America/Caracas,FALSE +"casablanca, monrovia",Africa/Casablanca,Africa/Casablanca,FALSE +casey,Antarctica/Casey,Antarctica/Casey,FALSE +caucasus,Asia/Yerevan,Asia/Yerevan,FALSE +caucasus standard time,Asia/Yerevan,Asia/Yerevan,FALSE +cen australia,Australia/Adelaide,Australia/Adelaide,FALSE +cen australia standard time,Australia/Adelaide,Australia/Adelaide,FALSE +central,America/Chicago,America/Chicago,FALSE +central america,America/Guatemala,America/Guatemala,FALSE +central america standard time,America/Guatemala,America/Guatemala,FALSE +central asia,Asia/Dhaka,Asia/Dhaka,FALSE +central asia standard time,Asia/Almaty,Asia/Almaty,FALSE +central brazilian,America/Manaus,America/Manaus,FALSE +central brazilian standard time,America/Cuiaba,America/Cuiaba,FALSE +central europe,Europe/Prague,Europe/Prague,FALSE +central europe standard time,Europe/Budapest,Europe/Budapest,FALSE +central european,Europe/Belgrade,Europe/Sarajevo,TRUE +central european standard time,Europe/Warsaw,Europe/Warsaw,FALSE +central pacific,Asia/Magadan,Asia/Magadan,FALSE +central pacific standard time,Pacific/Guadalcanal,Pacific/Guadalcanal,FALSE +central standard time,America/Chicago,America/Chicago,FALSE +central standard time (mexico),America/Mexico_City,America/Mexico_City,FALSE +central time (us & canada),America/Chicago,America/Chicago,FALSE +chamorro,Pacific/Guam,Pacific/Saipan,TRUE +chatham,Pacific/Chatham,Pacific/Chatham,FALSE +chatham islands standard time,Pacific/Chatham,Pacific/Chatham,FALSE +chile,America/Santiago,America/Santiago,FALSE +china,Asia/Shanghai,Asia/Shanghai,FALSE +china standard time,Asia/Shanghai,Asia/Shanghai,FALSE +choibalsan,Asia/Choibalsan,Asia/Choibalsan,FALSE +christmas,Indian/Christmas,Indian/Christmas,FALSE +cocos,Indian/Cocos,Indian/Cocos,FALSE +colombia,America/Bogota,America/Bogota,FALSE +cook,Pacific/Rarotonga,Pacific/Rarotonga,FALSE +cuba,America/Havana,America/Havana,FALSE +cuba standard time,America/Havana,America/Havana,FALSE +dacca,Asia/Dhaka,Asia/Dhaka,FALSE +darwin,Australia/Darwin,Australia/Darwin,FALSE +dateline,Pacific/Auckland,Pacific/Auckland,FALSE +dateline standard time,Pacific/Niue,Pacific/Niue,FALSE +davis,Antarctica/Davis,Antarctica/Davis,FALSE +dominican,America/Santo_Domingo,America/Santo_Domingo,FALSE +dumontdurville,Antarctica/DumontDUrville,Antarctica/DumontDUrville,FALSE +dushanbe,Asia/Dushanbe,Asia/Dushanbe,FALSE +dutch guiana,America/Paramaribo,America/Paramaribo,FALSE +e africa,Africa/Nairobi,Africa/Nairobi,FALSE +e africa standard time,Africa/Nairobi,Africa/Nairobi,FALSE +e australia,Australia/Brisbane,Australia/Brisbane,FALSE +e australia standard time,Australia/Brisbane,Australia/Brisbane,FALSE +e europe,Europe/Minsk,Europe/Minsk,FALSE +e europe standard time,Europe/Chisinau,Europe/Chisinau,FALSE +e south america,America/Belem,America/Belem,FALSE +e south america standard time,America/Sao_Paulo,America/Sao_Paulo,FALSE +"east africa, nairobi",Africa/Nairobi,Africa/Nairobi,FALSE +east timor,Asia/Dili,Asia/Dili,FALSE +easter,Pacific/Easter,Pacific/Easter,FALSE +easter island standard time,Pacific/Easter,Pacific/Easter,FALSE +eastern,America/New_York,America/New_York,FALSE +eastern standard time,America/New_York,America/New_York,FALSE +eastern standard time (mexico),America/Cancun,America/Cancun,FALSE +eastern time (us & canada),America/New_York,America/New_York,FALSE +ecuador,America/Guayaquil,America/Guayaquil,FALSE +egypt,Africa/Cairo,Africa/Cairo,FALSE +egypt standard time,Africa/Cairo,Africa/Cairo,FALSE +ekaterinburg,Asia/Yekaterinburg,Asia/Yekaterinburg,FALSE +ekaterinburg standard time,Asia/Yekaterinburg,Asia/Yekaterinburg,FALSE +"eniwetok, kwajalein, dateline time",Pacific/Kwajalein,Pacific/Kwajalein,FALSE +europe central,Europe/Paris,Europe/Paris,FALSE +europe eastern,Europe/Bucharest,Europe/Bucharest,FALSE +europe further eastern,Europe/Minsk,Europe/Minsk,FALSE +europe western,Atlantic/Canary,Atlantic/Canary,FALSE +falkland,Atlantic/Stanley,Atlantic/Stanley,FALSE +fiji,Pacific/Fiji,Pacific/Fiji,FALSE +fiji islands standard time,Pacific/Fiji,Pacific/Fiji,FALSE +"fiji islands, kamchatka, marshall is",Pacific/Fiji,Pacific/Fiji,FALSE +fiji standard time,Pacific/Fiji,Pacific/Fiji,FALSE +fle,Europe/Helsinki,Europe/Helsinki,FALSE +fle standard time,Europe/Kiev,Europe/Kiev,FALSE +french guiana,America/Cayenne,America/Cayenne,FALSE +french southern,Indian/Kerguelen,Indian/Kerguelen,FALSE +frunze,Asia/Bishkek,Asia/Bishkek,FALSE +galapagos,Pacific/Galapagos,Pacific/Galapagos,FALSE +gambier,Pacific/Gambier,Pacific/Gambier,FALSE +georgia,Asia/Tbilisi,Asia/Tbilisi,FALSE +georgian,Asia/Tbilisi,Asia/Tbilisi,FALSE +georgian standard time,Asia/Tbilisi,Asia/Tbilisi,FALSE +gilbert islands,Pacific/Tarawa,Pacific/Tarawa,FALSE +gmt,Europe/London,Europe/London,FALSE +gmt standard time,Europe/London,Europe/London,FALSE +goose bay,America/Goose_Bay,America/Goose_Bay,FALSE +greenland,America/Godthab,America/Godthab,FALSE +greenland central,America/Scoresbysund,America/Scoresbysund,FALSE +greenland eastern,America/Scoresbysund,America/Scoresbysund,FALSE +greenland standard time,America/Godthab,America/Godthab,FALSE +greenland western,America/Godthab,America/Godthab,FALSE +greenwich,Atlantic/Reykjavik,Atlantic/Reykjavik,FALSE +"greenwich mean time; dublin, edinburgh, london",Europe/London,Europe/London,FALSE +"greenwich mean time: dublin, edinburgh, lisbon, london",Europe/Lisbon,Europe/Lisbon,FALSE +greenwich standard time,Atlantic/Reykjavik,Atlantic/Reykjavik,FALSE +gtb,Europe/Athens,Europe/Athens,FALSE +gtb standard time,Europe/Bucharest,Europe/Bucharest,FALSE +guam,Pacific/Guam,Pacific/Guam,FALSE +"guam, port moresby",Pacific/Guam,Pacific/Guam,FALSE +gulf,Asia/Dubai,Asia/Dubai,FALSE +guyana,America/Guyana,America/Guyana,FALSE +haiti standard time,America/Port-au-Prince,America/Port-au-Prince,FALSE +"harare, pretoria",Africa/Maputo,Africa/Harare,TRUE +hawaii,Pacific/Honolulu,Pacific/Honolulu,FALSE +hawaii aleutian,Pacific/Honolulu,Pacific/Honolulu,FALSE +hawaiian,Pacific/Honolulu,Pacific/Honolulu,FALSE +hawaiian standard time,Pacific/Honolulu,Pacific/Honolulu,FALSE +"helsinki, riga, tallinn",Europe/Helsinki,Europe/Helsinki,FALSE +"hobart, tasmania",Australia/Hobart,Australia/Hobart,FALSE +hong kong,Asia/Hong_Kong,Asia/Hong_Kong,FALSE +hovd,Asia/Hovd,Asia/Hovd,FALSE +india,Asia/Kolkata,Asia/Kolkata,FALSE +india standard time,Asia/Kolkata,Asia/Kolkata,FALSE +indian ocean,Indian/Chagos,Indian/Chagos,FALSE +indiana (east),America/New_York,America/Indiana/Indianapolis,TRUE +indochina,Asia/Bangkok,Asia/Bangkok,FALSE +indonesia central,Asia/Makassar,Asia/Makassar,FALSE +indonesia eastern,Asia/Jayapura,Asia/Jayapura,FALSE +indonesia western,Asia/Jakarta,Asia/Jakarta,FALSE +iran,Asia/Tehran,Asia/Tehran,FALSE +iran standard time,Asia/Tehran,Asia/Tehran,FALSE +irish,Europe/Dublin,Europe/Dublin,FALSE +irkutsk,Asia/Irkutsk,Asia/Irkutsk,FALSE +"irkutsk, ulaan bataar",Asia/Irkutsk,Asia/Irkutsk,FALSE +"islamabad, karachi, tashkent",Asia/Karachi,Asia/Karachi,FALSE +israel,Asia/Jerusalem,Asia/Jerusalem,FALSE +israel standard time,Asia/Jerusalem,Asia/Jerusalem,FALSE +"israel, jerusalem standard time",Asia/Jerusalem,Asia/Jerusalem,FALSE +japan,Asia/Tokyo,Asia/Tokyo,FALSE +jordan,Asia/Amman,Asia/Amman,FALSE +jordan standard time,Asia/Amman,Asia/Amman,FALSE +kabul,Asia/Kabul,Asia/Kabul,FALSE +kaliningrad standard time,Europe/Kaliningrad,Europe/Kaliningrad,FALSE +kamchatka,Asia/Kamchatka,Asia/Kamchatka,FALSE +kamchatka standard time,Asia/Kamchatka,Asia/Kamchatka,FALSE +karachi,Asia/Karachi,Asia/Karachi,FALSE +"kathmandu, nepal",Asia/Kathmandu,Asia/Kathmandu,FALSE +kazakhstan eastern,Asia/Almaty,Asia/Almaty,FALSE +kazakhstan western,Asia/Aqtobe,Asia/Aqtobe,FALSE +kizilorda,Asia/Qyzylorda,Asia/Qyzylorda,FALSE +"kolkata, chennai, mumbai, new delhi, india standard time",Asia/Kolkata,Asia/Kolkata,FALSE +korea,Asia/Seoul,Asia/Seoul,FALSE +korea standard time,Asia/Seoul,Asia/Seoul,FALSE +kosrae,Pacific/Kosrae,Pacific/Kosrae,FALSE +krasnoyarsk,Asia/Krasnoyarsk,Asia/Krasnoyarsk,FALSE +"kuala lumpur, singapore",Asia/Shanghai,Asia/Singapore,TRUE +kuybyshev,Europe/Samara,Europe/Samara,FALSE +kwajalein,Pacific/Kwajalein,Pacific/Kwajalein,FALSE +kyrgystan,Asia/Bishkek,Asia/Bishkek,FALSE +lanka,Asia/Colombo,Asia/Colombo,FALSE +liberia,Africa/Monrovia,Africa/Monrovia,FALSE +libya standard time,Africa/Tripoli,Africa/Tripoli,FALSE +line islands,Pacific/Kiritimati,Pacific/Kiritimati,FALSE +line islands standard time,Pacific/Kiritimati,Pacific/Kiritimati,FALSE +lord howe,Australia/Lord_Howe,Australia/Lord_Howe,FALSE +lord howe standard time,Australia/Lord_Howe,Australia/Lord_Howe,FALSE +macau,Asia/Macau,Asia/Macau,FALSE +macquarie,Antarctica/Macquarie,Antarctica/Macquarie,FALSE +magadan,Asia/Magadan,Asia/Magadan,FALSE +magadan standard time,Asia/Magadan,Asia/Magadan,FALSE +"magadan, solomon is, new caledonia",Asia/Magadan,Asia/Magadan,FALSE +magallanes standard time,America/Punta_Arenas,America/Punta_Arenas,FALSE +malaya,Asia/Kuala_Lumpur,Asia/Kuala_Lumpur,FALSE +malaysia,Asia/Kuching,Asia/Kuching,FALSE +maldives,Indian/Maldives,Indian/Maldives,FALSE +marquesas,Pacific/Marquesas,Pacific/Marquesas,FALSE +marquesas standard time,Pacific/Marquesas,Pacific/Marquesas,FALSE +marshall islands,Pacific/Majuro,Pacific/Majuro,FALSE +mauritius,Indian/Mauritius,Indian/Mauritius,FALSE +mauritius standard time,Indian/Mauritius,Indian/Mauritius,FALSE +mawson,Antarctica/Mawson,Antarctica/Mawson,FALSE +mexico,America/Mexico_City,America/Mexico_City,FALSE +"mexico city, tegucigalpa",America/Mexico_City,America/Mexico_City,FALSE +mexico pacific,America/Mazatlan,America/Mazatlan,FALSE +mexico standard time,America/Mexico_City,America/Mexico_City,FALSE +mexico standard time 2,America/Chihuahua,America/Chihuahua,FALSE +mid-atlantic,America/Noronha,America/Noronha,FALSE +mid-atlantic standard time,Atlantic/Cape_Verde,Atlantic/Cape_Verde,FALSE +middle east,Asia/Beirut,Asia/Beirut,FALSE +middle east standard time,Asia/Beirut,Asia/Beirut,FALSE +"midway island, samoa",Pacific/Pago_Pago,Pacific/Midway,TRUE +mongolia,Asia/Ulaanbaatar,Asia/Ulaanbaatar,FALSE +montevideo,America/Montevideo,America/Montevideo,FALSE +montevideo standard time,America/Montevideo,America/Montevideo,FALSE +morocco,Africa/Casablanca,Africa/Casablanca,FALSE +morocco standard time,Africa/Casablanca,Africa/Casablanca,FALSE +moscow,Europe/Moscow,Europe/Moscow,FALSE +"moscow, st petersburg, volgograd",Europe/Moscow,Europe/Moscow,FALSE +mountain,America/Denver,America/Denver,FALSE +mountain standard time,America/Denver,America/Denver,FALSE +mountain standard time (mexico),America/Chihuahua,America/Chihuahua,FALSE +mountain time (us & canada),America/Denver,America/Denver,FALSE +myanmar,Indian/Cocos,Asia/Yangon,TRUE +myanmar standard time,Indian/Cocos,Asia/Yangon,TRUE +n central asia,Asia/Almaty,Asia/Almaty,FALSE +n central asia standard time,Asia/Novosibirsk,Asia/Novosibirsk,FALSE +namibia,Africa/Windhoek,Africa/Windhoek,FALSE +namibia standard time,Africa/Windhoek,Africa/Windhoek,FALSE +nauru,Pacific/Nauru,Pacific/Nauru,FALSE +nepal,Asia/Kathmandu,Asia/Kathmandu,FALSE +nepal standard time,Asia/Kathmandu,Asia/Kathmandu,FALSE +new caledonia,Pacific/Noumea,Pacific/Noumea,FALSE +new zealand,Pacific/Auckland,Pacific/Auckland,FALSE +new zealand standard time,Pacific/Auckland,Pacific/Auckland,FALSE +newfoundland,America/St_Johns,America/St_Johns,FALSE +newfoundland and labrador standard time,America/St_Johns,America/St_Johns,FALSE +newfoundland standard time,America/St_Johns,America/St_Johns,FALSE +niue,Pacific/Niue,Pacific/Niue,FALSE +norfolk,Pacific/Norfolk,Pacific/Norfolk,FALSE +norfolk standard time,Pacific/Norfolk,Pacific/Norfolk,FALSE +noronha,America/Noronha,America/Noronha,FALSE +north asia,Asia/Krasnoyarsk,Asia/Krasnoyarsk,FALSE +north asia east,Asia/Irkutsk,Asia/Irkutsk,FALSE +north asia east standard time,Asia/Irkutsk,Asia/Irkutsk,FALSE +north asia standard time,Asia/Krasnoyarsk,Asia/Krasnoyarsk,FALSE +north korea standard time,Asia/Pyongyang,Asia/Pyongyang,FALSE +north mariana,Pacific/Guam,Pacific/Saipan,TRUE +novosibirsk,Asia/Novosibirsk,Asia/Novosibirsk,FALSE +"nuku'alofa, tonga",Pacific/Tongatapu,Pacific/Tongatapu,FALSE +omsk,Asia/Omsk,Asia/Omsk,FALSE +omsk standard time,Asia/Omsk,Asia/Omsk,FALSE +oral,Asia/Oral,Asia/Oral,FALSE +"osaka, sapporo, tokyo",Asia/Tokyo,Asia/Tokyo,FALSE +pacific,America/Los_Angeles,America/Los_Angeles,FALSE +pacific sa,America/Santiago,America/Santiago,FALSE +pacific sa standard time,America/Santiago,America/Santiago,FALSE +pacific standard time,America/Los_Angeles,America/Los_Angeles,FALSE +pacific standard time (mexico),America/Tijuana,America/Tijuana,FALSE +pacific time (us & canada),America/Los_Angeles,America/Los_Angeles,FALSE +pacific time (us & canada); tijuana,America/Los_Angeles,America/Los_Angeles,FALSE +pakistan,Asia/Karachi,Asia/Karachi,FALSE +pakistan standard time,Asia/Karachi,Asia/Karachi,FALSE +palau,Pacific/Palau,Pacific/Palau,FALSE +papua new guinea,Pacific/Port_Moresby,Pacific/Port_Moresby,FALSE +paraguay,America/Asuncion,America/Asuncion,FALSE +paraguay standard time,America/Asuncion,America/Asuncion,FALSE +"paris, madrid, brussels, copenhagen",Europe/Paris,Europe/Paris,FALSE +"perth, western australia",Australia/Perth,Australia/Perth,FALSE +peru,America/Lima,America/Lima,FALSE +philippines,Asia/Manila,Asia/Manila,FALSE +phoenix islands,Pacific/Enderbury,Pacific/Enderbury,FALSE +pierre miquelon,America/Miquelon,America/Miquelon,FALSE +pitcairn,Pacific/Pitcairn,Pacific/Pitcairn,FALSE +"prague, central europe",Europe/Prague,Europe/Prague,FALSE +pyongyang,Asia/Pyongyang,Asia/Pyongyang,FALSE +qyzylorda,Asia/Qyzylorda,Asia/Qyzylorda,FALSE +qyzylorda standard time,Asia/Qyzylorda,Asia/Qyzylorda,FALSE +rangoon,Indian/Cocos,Asia/Yangon,TRUE +reunion,Indian/Reunion,Indian/Reunion,FALSE +romance,Europe/Paris,Europe/Paris,FALSE +romance standard time,Europe/Paris,Europe/Paris,FALSE +rothera,Antarctica/Rothera,Antarctica/Rothera,FALSE +russia time zone 10,Asia/Srednekolymsk,Asia/Srednekolymsk,FALSE +russia time zone 11,Asia/Kamchatka,Asia/Kamchatka,FALSE +russia time zone 3,Europe/Samara,Europe/Samara,FALSE +russian,Europe/Moscow,Europe/Moscow,FALSE +russian standard time,Europe/Moscow,Europe/Moscow,FALSE +sa eastern,America/Belem,America/Belem,FALSE +sa eastern standard time,America/Cayenne,America/Cayenne,FALSE +sa pacific,America/Bogota,America/Bogota,FALSE +sa pacific standard time,America/Bogota,America/Bogota,FALSE +sa western,America/La_Paz,America/La_Paz,FALSE +sa western standard time,America/La_Paz,America/La_Paz,FALSE +saint pierre standard time,America/Miquelon,America/Miquelon,FALSE +sakhalin,Asia/Sakhalin,Asia/Sakhalin,FALSE +sakhalin standard time,Asia/Sakhalin,Asia/Sakhalin,FALSE +samara,Europe/Samara,Europe/Samara,FALSE +samarkand,Asia/Samarkand,Asia/Samarkand,FALSE +samoa,Pacific/Apia,Pacific/Apia,FALSE +samoa standard time,Pacific/Apia,Pacific/Apia,FALSE +santiago,America/Santiago,America/Santiago,FALSE +sao tome standard time,Africa/Sao_Tome,Africa/Sao_Tome,FALSE +"sarajevo, skopje, sofija, vilnius, warsaw, zagreb",Europe/Belgrade,Europe/Sarajevo,TRUE +saratov standard time,Europe/Saratov,Europe/Saratov,FALSE +saskatchewan,America/Edmonton,America/Edmonton,FALSE +se asia,Asia/Bangkok,Asia/Bangkok,FALSE +se asia standard time,Asia/Bangkok,Asia/Bangkok,FALSE +"seoul, korea standard time",Asia/Seoul,Asia/Seoul,FALSE +seychelles,Indian/Mahe,Indian/Mahe,FALSE +shevchenko,Asia/Aqtau,Asia/Aqtau,FALSE +singapore,Asia/Shanghai,Asia/Singapore,TRUE +singapore standard time,Asia/Shanghai,Asia/Singapore,TRUE +solomon,Pacific/Guadalcanal,Pacific/Guadalcanal,FALSE +south africa,Africa/Maputo,Africa/Harare,TRUE +south africa standard time,Africa/Johannesburg,Africa/Johannesburg,FALSE +south georgia,Atlantic/South_Georgia,Atlantic/South_Georgia,FALSE +"sri jayawardenepura, sri lanka",Asia/Colombo,Asia/Colombo,FALSE +sri lanka,Asia/Colombo,Asia/Colombo,FALSE +sri lanka standard time,Asia/Colombo,Asia/Colombo,FALSE +sudan standard time,Africa/Khartoum,Africa/Khartoum,FALSE +suriname,America/Paramaribo,America/Paramaribo,FALSE +sverdlovsk,Asia/Yekaterinburg,Asia/Yekaterinburg,FALSE +syowa,Antarctica/Syowa,Antarctica/Syowa,FALSE +syria standard time,Asia/Damascus,Asia/Damascus,FALSE +tahiti,Pacific/Tahiti,Pacific/Tahiti,FALSE +taipei,Asia/Taipei,Asia/Taipei,FALSE +taipei standard time,Asia/Taipei,Asia/Taipei,FALSE +tajikistan,Asia/Dushanbe,Asia/Dushanbe,FALSE +tashkent,Asia/Tashkent,Asia/Tashkent,FALSE +tasmania,Australia/Hobart,Australia/Hobart,FALSE +tasmania standard time,Australia/Hobart,Australia/Hobart,FALSE +tbilisi,Asia/Tbilisi,Asia/Tbilisi,FALSE +tehran,Asia/Tehran,Asia/Tehran,FALSE +tocantins standard time,America/Araguaina,America/Araguaina,FALSE +tokelau,Pacific/Fakaofo,Pacific/Fakaofo,FALSE +tokyo,Asia/Tokyo,Asia/Tokyo,FALSE +tokyo standard time,Asia/Tokyo,Asia/Tokyo,FALSE +tomsk standard time,Asia/Tomsk,Asia/Tomsk,FALSE +tonga,Pacific/Tongatapu,Pacific/Tongatapu,FALSE +tonga standard time,Pacific/Tongatapu,Pacific/Tongatapu,FALSE +transbaikal standard time,Asia/Chita,Asia/Chita,FALSE +transitional islamic state of afghanistan standard time,Asia/Kabul,Asia/Kabul,FALSE +turkey,Europe/Istanbul,Europe/Istanbul,FALSE +turkey standard time,Europe/Istanbul,Europe/Istanbul,FALSE +turkmenistan,Asia/Ashgabat,Asia/Ashgabat,FALSE +turks and caicos standard time,America/Grand_Turk,America/Grand_Turk,FALSE +tuvalu,Asia/Kamchatka,Pacific/Funafuti,TRUE +ulaanbaatar standard time,Asia/Ulaanbaatar,Asia/Ulaanbaatar,FALSE +universal coordinated time,UTC,UTC,FALSE +uralsk,Asia/Oral,Asia/Oral,FALSE +uruguay,America/Montevideo,America/Montevideo,FALSE +urumqi,Asia/Urumqi,Asia/Urumqi,FALSE +us eastern,America/New_York,America/Indiana/Indianapolis,TRUE +us eastern standard time,America/New_York,America/New_York,FALSE +us mountain,America/Phoenix,America/Phoenix,FALSE +us mountain standard time,America/Phoenix,America/Phoenix,FALSE +utc-02,America/Noronha,America/Noronha,FALSE +utc-08,Pacific/Pitcairn,Pacific/Pitcairn,FALSE +utc-09,Pacific/Gambier,Pacific/Gambier,FALSE +utc-11,Pacific/Niue,Pacific/Niue,FALSE +utc+12,Pacific/Auckland,Pacific/Auckland,FALSE +uzbekistan,Asia/Tashkent,Asia/Tashkent,FALSE +vanuatu,Pacific/Efate,Pacific/Efate,FALSE +venezuela,America/Caracas,America/Caracas,FALSE +venezuela standard time,America/Caracas,America/Caracas,FALSE +vladivostok,Asia/Vladivostok,Asia/Vladivostok,FALSE +vladivostok standard time,Asia/Vladivostok,Asia/Vladivostok,FALSE +volgograd,Europe/Volgograd,Europe/Volgograd,FALSE +volgograd standard time,Europe/Volgograd,Europe/Volgograd,FALSE +vostok,Antarctica/Vostok,Antarctica/Vostok,FALSE +w australia,Australia/Perth,Australia/Perth,FALSE +w australia standard time,Australia/Perth,Australia/Perth,FALSE +w central africa,Africa/Lagos,Africa/Lagos,FALSE +w central africa standard time,Africa/Lagos,Africa/Lagos,FALSE +w europe,Europe/Amsterdam,Europe/Amsterdam,FALSE +w europe standard time,Europe/Berlin,Europe/Berlin,FALSE +w mongolia standard time,Asia/Hovd,Asia/Hovd,FALSE +wake,Asia/Kamchatka,Pacific/Wake,TRUE +wallis,Asia/Kamchatka,Pacific/Wallis,TRUE +west asia,Asia/Tashkent,Asia/Tashkent,FALSE +west asia standard time,Asia/Tashkent,Asia/Tashkent,FALSE +west bank standard time,Asia/Hebron,Asia/Hebron,FALSE +west central africa,Africa/Lagos,Africa/Luanda,TRUE +west pacific,Pacific/Guam,Pacific/Guam,FALSE +west pacific standard time,Pacific/Port_Moresby,Pacific/Port_Moresby,FALSE +yakutsk,Asia/Yakutsk,Asia/Yakutsk,FALSE +yakutsk standard time,Asia/Yakutsk,Asia/Yakutsk,FALSE +yekaterinburg,Asia/Yekaterinburg,Asia/Yekaterinburg,FALSE +yerevan,Asia/Yerevan,Asia/Yerevan,FALSE +yukon,America/Yakutat,America/Yakutat,FALSE +coordinated universal time-11,Pacific/Pago_Pago,,TRUE +aleutian islands,America/Adak,,TRUE +marquesas islands,Pacific/Marquesas,,TRUE +coordinated universal time-09,America/Anchorage,,TRUE +baja california,America/Tijuana,,TRUE +coordinated universal time-08,Pacific/Pitcairn,,TRUE +"chihuahua, la paz, mazatlan",America/Chihuahua,,TRUE +easter island,Pacific/Easter,,TRUE +"guadalajara, mexico city, monterrey",America/Mexico_City,,TRUE +"bogota, lima, quito, rio branco",America/Bogota,,TRUE +chetumal,America/Cancun,,TRUE +haiti,America/Port-au-Prince,,TRUE +havana,America/Havana,,TRUE +turks and caicos,America/Grand_Turk,,TRUE +asuncion,America/Asuncion,,TRUE +caracas,America/Caracas,,TRUE +cuiaba,America/Cuiaba,,TRUE +"georgetown, la paz, manaus, san juan",America/La_Paz,,TRUE +araguaina,America/Araguaina,,TRUE +"cayenne, fortaleza",America/Cayenne,,TRUE +city of buenos aires,America/Argentina/Buenos_Aires,,TRUE +punta arenas,America/Punta_Arenas,,TRUE +saint pierre and miquelon,America/Miquelon,,TRUE +salvador,America/Bahia,,TRUE +coordinated universal time-02,America/Noronha,,TRUE +mid-atlantic - old,America/Noronha,,TRUE +cabo verde is,Atlantic/Cape_Verde,,TRUE +coordinated universal time,UTC,,TRUE +"dublin, edinburgh, lisbon, london",Europe/London,,TRUE +"monrovia, reykjavik",Atlantic/Reykjavik,,TRUE +"belgrade, bratislava, budapest, ljubljana, prague",Europe/Budapest,,TRUE +casablanca,Africa/Casablanca,,TRUE +sao tome,Africa/Sao_Tome,,TRUE +"sarajevo, skopje, warsaw, zagreb",Europe/Warsaw,,TRUE +amman,Asia/Amman,,TRUE +"athens, bucharest",Europe/Bucharest,,TRUE +beirut,Asia/Beirut,,TRUE +chisinau,Europe/Chisinau,,TRUE +damascus,Asia/Damascus,,TRUE +"gaza, hebron",Asia/Hebron,,TRUE +jerusalem,Asia/Jerusalem,,TRUE +kaliningrad,Europe/Kaliningrad,,TRUE +khartoum,Africa/Khartoum,,TRUE +tripoli,Africa/Tripoli,,TRUE +windhoek,Africa/Windhoek,,TRUE +istanbul,Europe/Istanbul,,TRUE +"kuwait, riyadh",Asia/Riyadh,,TRUE +minsk,Europe/Minsk,,TRUE +"moscow, st petersburg",Europe/Moscow,,TRUE +nairobi,Africa/Nairobi,,TRUE +"astrakhan, ulyanovsk",Europe/Astrakhan,,TRUE +"izhevsk, samara",Europe/Samara,,TRUE +port louis,Indian/Mauritius,,TRUE +saratov,Europe/Saratov,,TRUE +"ashgabat, tashkent",Asia/Tashkent,,TRUE +"islamabad, karachi",Asia/Karachi,,TRUE +"chennai, kolkata, mumbai, new delhi",Asia/Kolkata,,TRUE +sri jayawardenepura,Asia/Colombo,,TRUE +kathmandu,Asia/Kathmandu,,TRUE +astana,Asia/Almaty,,TRUE +dhaka,Asia/Dhaka,,TRUE +yangon (rangoon),Indian/Cocos,Asia/Rangoon,TRUE +"barnaul, gorno-altaysk",Asia/Barnaul,,TRUE +tomsk,Asia/Tomsk,,TRUE +"beijing, chongqing, hong kong, urumqi",Asia/Shanghai,,TRUE +perth,Australia/Perth,,TRUE +ulaanbaatar,Asia/Ulaanbaatar,,TRUE +eucla,Australia/Eucla,,TRUE +chita,Asia/Chita,,TRUE +seoul,Asia/Seoul,,TRUE +adelaide,Australia/Adelaide,,TRUE +brisbane,Australia/Brisbane,,TRUE +"canberra, melbourne, sydney",Australia/Sydney,,TRUE +hobart,Australia/Hobart,,TRUE +lord howe island,Australia/Lord_Howe,,TRUE +bougainville island,Pacific/Bougainville,,TRUE +chokurdakh,Asia/Srednekolymsk,,TRUE +norfolk island,Pacific/Norfolk,,TRUE +"solomon is, new caledonia",Pacific/Guadalcanal,,TRUE +"anadyr, petropavlovsk-kamchatsky",Asia/Kamchatka,,TRUE +coordinated universal time+12,Pacific/Tarawa,,TRUE +petropavlovsk-kamchatsky - old,Asia/Anadyr,,TRUE +chatham islands,Pacific/Chatham,,TRUE +coordinated universal time+13,Pacific/Enderbury,,TRUE +nuku'alofa,Pacific/Tongatapu,,TRUE +kiritimati island,Pacific/Kiritimati,,TRUE +"helsinki, kyiv, riga, sofia, tallinn, vilnius",Europe/Helsinki,,TRUE +"amsterdam, berlin, berne, rome, stockholm, vienne",Europe/Berlin,,TRUE \ No newline at end of file From eaba674866569bfab24f8cfb00032193340b5a59 Mon Sep 17 00:00:00 2001 From: giuseppe-arcuti <93533561+giuseppe-arcuti@users.noreply.github.com> Date: Fri, 21 Jan 2022 10:47:01 +0100 Subject: [PATCH 44/92] Prepare release 4.7.2 (#43) --- CHANGELOG.md | 4 ++++ lib/Version.php | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9b928d64..ec2dfac2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ ChangeLog ========= +4.7.2 (2022-01-21) +------------------ +* #41 Add missing microsoft timezones and test with confluence file + 4.7.1 (2022-01-10) ------------------ * #39 Add support for lowercase timezones diff --git a/lib/Version.php b/lib/Version.php index b303f31e5..61ba73c12 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -14,5 +14,5 @@ class Version /** * Full version number. */ - const VERSION = '4.7.1'; + const VERSION = '4.7.2'; } From 5479bbaf73984d6b8ea3f6c6c02d829d37f44e9e Mon Sep 17 00:00:00 2001 From: liurxliu <31659588+liurxliu@users.noreply.github.com> Date: Thu, 10 Feb 2022 17:44:56 +0800 Subject: [PATCH 45/92] [Calendar] Fix unfolding issue (#42) Co-authored-by: Ren Xie Liu --- lib/Parser/MimeDir.php | 22 +++++- lib/Reader.php | 5 ++ tests/VObject/Parser/UnfoldingTest.php | 99 ++++++++++++++++++++++++++ 3 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 tests/VObject/Parser/UnfoldingTest.php diff --git a/lib/Parser/MimeDir.php b/lib/Parser/MimeDir.php index 9256af6f6..b0b52d223 100644 --- a/lib/Parser/MimeDir.php +++ b/lib/Parser/MimeDir.php @@ -9,6 +9,7 @@ use Sabre\VObject\EofException; use Sabre\VObject\Node; use Sabre\VObject\ParseException; +use Sabre\VObject\Reader; /** * MimeDir parser. @@ -200,15 +201,32 @@ protected function parseLine($line) } $component = $this->root->createComponent(substr($line, 6), [], false); + $prevNode = null; while (true) { // Reading until we hit END: $line = $this->readLine(); if ('END:' === strtoupper(substr($line, 0, 4))) { break; } - $result = $this->parseLine($line); + try { + $result = $this->parseLine($line); + } catch (\Exception $e) { + if (isset($prevNode) + && $e instanceof ParseException && str_contains($e->getMessage(), 'Invalid Mimedir file. Line starting at') + && ($this->options & Reader::OPTION_FIX_UNFOLDING) + ) { + // Fix unfolding + $component->remove($prevNode); + $value = $prevNode->getValue() . ' ' . $line . PHP_EOL; + $prevNode->offsetSet('VALUE', $value); + $prevNode->setValue($value); + $component->add($prevNode); + continue; + } + throw $e; + } if ($result) { - $component->add($result); + $prevNode = $component->add($result); } } diff --git a/lib/Reader.php b/lib/Reader.php index 055d546a5..a68c1af47 100644 --- a/lib/Reader.php +++ b/lib/Reader.php @@ -26,6 +26,11 @@ class Reader */ const OPTION_IGNORE_INVALID_LINES = 2; + /** + * If this option is turned on, it will fix unfolding parse error by adding empty space. + */ + const OPTION_FIX_UNFOLDING = 4; + /** * Parses a vCard or iCalendar object, and returns the top component. * diff --git a/tests/VObject/Parser/UnfoldingTest.php b/tests/VObject/Parser/UnfoldingTest.php new file mode 100644 index 000000000..8d751ae7a --- /dev/null +++ b/tests/VObject/Parser/UnfoldingTest.php @@ -0,0 +1,99 @@ +parse($vcard, Reader::OPTION_FIX_UNFOLDING); + + $this->assertNotNull($vcard->children()[0]->{'X-APPLE-STRUCTURED-LOCATION'}->getValue()); + } + + public function testNotFixUnfolding() + { + $this->expectException(ParseException::class); + + $vcard = <<parse($vcard); + } + + public function testNotFixUnknownProperty() + { + $vcard = <<parse($vcard); + + $this->assertNotNull($vcard->children()[0]->CONFERENCE->getValue()); + } +} From 9ab83bbc1a60b928b38bbcfb2f803477e7ea5a4e Mon Sep 17 00:00:00 2001 From: Valentin BONNEAUD Date: Thu, 10 Feb 2022 10:50:16 +0100 Subject: [PATCH 46/92] 4.8.0 release (#44) --- CHANGELOG.md | 4 ++++ lib/Version.php | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec2dfac2c..3707646f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ ChangeLog ========= +4.8.0 (2022-02-10) +------------------ +* #42 Add option to fix unfolding issues in ICS + 4.7.2 (2022-01-21) ------------------ * #41 Add missing microsoft timezones and test with confluence file diff --git a/lib/Version.php b/lib/Version.php index 61ba73c12..0c49fdda9 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -14,5 +14,5 @@ class Version /** * Full version number. */ - const VERSION = '4.7.2'; + const VERSION = '4.8.0'; } From 2013799ba6263a1bf6c89f51fe7012d52b691b77 Mon Sep 17 00:00:00 2001 From: Valentin BONNEAUD Date: Thu, 10 Feb 2022 10:53:27 +0100 Subject: [PATCH 47/92] Update readme to mention fork (#45) --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b5b1cc96c..cc5f34fb9 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ -sabre/vobject +protonlabs/vobject ============= ![Build Status](https://github.com/ProtonMail/vobject/actions/workflows/actions.yml/badge.svg) [![codecov](https://codecov.io/gh/ProtonMail/vobject/branch/master/graph/badge.svg?token=ARcwkxCKZn)](https://codecov.io/gh/ProtonMail/vobject) +Forked from [sabre/vobject](https://github.com/sabre-io/vobject). The VObject library allows you to easily parse and manipulate [iCalendar](https://tools.ietf.org/html/rfc5545) and [vCard](https://tools.ietf.org/html/rfc6350) objects using PHP. From 9a298f99e820ac56249fa6d0fd97ce0e8c8540df Mon Sep 17 00:00:00 2001 From: liurxliu <31659588+liurxliu@users.noreply.github.com> Date: Tue, 15 Feb 2022 05:34:35 +0800 Subject: [PATCH 48/92] [Calendar] Support UTC-05:00 timezone (#46) Co-authored-by: Ren Xie Liu --- lib/timezonedata/extrazones.php | 1 + tests/VObject/TimeZoneUtilTest.php | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/lib/timezonedata/extrazones.php b/lib/timezonedata/extrazones.php index 134c3cb6d..900775a88 100644 --- a/lib/timezonedata/extrazones.php +++ b/lib/timezonedata/extrazones.php @@ -192,6 +192,7 @@ 'UTC-09' => 'Pacific/Gambier', 'UTC-11' => 'Pacific/Niue', 'UTC+12' => 'Pacific/Auckland', + 'UTC-05:00' => 'America/Lima', 'US Eastern Standard Time' => 'America/New_York', 'tzone://Microsoft/Utc' => 'UTC', 'America/Santa_Isabel' => 'America/Tijuana', diff --git a/tests/VObject/TimeZoneUtilTest.php b/tests/VObject/TimeZoneUtilTest.php index 2abdd87f2..856c19f59 100644 --- a/tests/VObject/TimeZoneUtilTest.php +++ b/tests/VObject/TimeZoneUtilTest.php @@ -445,4 +445,22 @@ public function unSupportTimezoneProvider(): iterable fclose($handle); } } + + /** + * @dataProvider offsetTimeZoneProvider + */ + public function testOffsetTimeZones(string $origin, string $expected) + { + $tz = TimeZoneUtil::getTimeZone($origin, null, true); + $ex = new \DateTimeZone($expected); + $this->assertEquals($ex->getName(), $tz->getName()); + } + + public function offsetTimeZoneProvider(): iterable + { + yield 'UTC-05:00' => [ + 'origin' => 'UTC-05:00', + 'expected' => 'America/Lima', + ]; + } } From 587dd4c3451115d5cf718a9ad28bae83745dc07d Mon Sep 17 00:00:00 2001 From: Ren Xie Liu Date: Tue, 15 Feb 2022 12:17:44 +0800 Subject: [PATCH 49/92] 4.9.0 release --- CHANGELOG.md | 4 ++++ lib/Version.php | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3707646f7..ed7e6e2e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ ChangeLog ========= +4.9.0 (2022-02-15) +------------------ +* #46 Add support UTC-05:00 timezone + 4.8.0 (2022-02-10) ------------------ * #42 Add option to fix unfolding issues in ICS diff --git a/lib/Version.php b/lib/Version.php index 0c49fdda9..628e06ab4 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -14,5 +14,5 @@ class Version /** * Full version number. */ - const VERSION = '4.8.0'; + const VERSION = '4.9.0'; } From 786c7319b4530db6e7f58109f31e3f3a2774f017 Mon Sep 17 00:00:00 2001 From: giuseppe-arcuti <93533561+giuseppe-arcuti@users.noreply.github.com> Date: Tue, 8 Mar 2022 09:38:55 +0100 Subject: [PATCH 50/92] Block invalid combinations of FREQ with BY rules (#48) --- lib/Recur/RRuleIterator.php | 8 ++++++ .../VObject/Recur/EventIterator/MainTest.php | 2 +- tests/VObject/Recur/RRuleIteratorTest.php | 26 ++++++++++++++++++- 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/lib/Recur/RRuleIterator.php b/lib/Recur/RRuleIterator.php index a1b97ebee..d09838398 100644 --- a/lib/Recur/RRuleIterator.php +++ b/lib/Recur/RRuleIterator.php @@ -1027,6 +1027,14 @@ protected function parseRRule($rrule) if (isset($this->count) && isset($this->until)) { throw new InvalidDataException('Can not have both UNTIL and COUNT property at the same time'); } + + if ( + (isset($this->byWeekNo) && $this->frequency !== 'yearly') || + (isset($this->byYearDay) && in_array($this->frequency, ['daily', 'weekly', 'monthly'], true)) || + (isset($this->byMonthDay) && $this->frequency === 'weekly') + ) { + throw new InvalidDataException('Invalid combination of FREQ with BY rules'); + } } /** diff --git a/tests/VObject/Recur/EventIterator/MainTest.php b/tests/VObject/Recur/EventIterator/MainTest.php index cf317f3ea..9350080e4 100644 --- a/tests/VObject/Recur/EventIterator/MainTest.php +++ b/tests/VObject/Recur/EventIterator/MainTest.php @@ -16,7 +16,7 @@ public function testValues() $vcal = new VCalendar(); $ev = $vcal->createComponent('VEVENT'); $ev->UID = 'bla'; - $ev->RRULE = 'FREQ=DAILY;BYHOUR=10;BYMINUTE=5;BYSECOND=16;BYWEEKNO=32;BYYEARDAY=100,200'; + $ev->RRULE = 'FREQ=DAILY;BYHOUR=10;BYMINUTE=5;BYSECOND=16'; $dtStart = $vcal->createProperty('DTSTART'); $dtStart->setDateTime(new DateTimeImmutable('2011-10-07')); diff --git a/tests/VObject/Recur/RRuleIteratorTest.php b/tests/VObject/Recur/RRuleIteratorTest.php index cd4c3f8d4..6cb3feb5b 100644 --- a/tests/VObject/Recur/RRuleIteratorTest.php +++ b/tests/VObject/Recur/RRuleIteratorTest.php @@ -331,6 +331,30 @@ public function testInvalidByMonthDay() ); } + /** @dataProvider invalidFreqByCombinationProviders */ + public function testInvalidFreqByCombination(string $rule) + { + $this->expectException(InvalidDataException::class); + $this->parse( + $rule, + '2011-01-01 00:00:00', + [] + ); + } + + public function invalidFreqByCombinationProviders(): iterable + { + return [ + ['FREQ=DAILY;BYWEEKNO=13,15,50'], + ['FREQ=WEEKLY;BYWEEKNO=13,15,50'], + ['FREQ=MONTHLY;BYWEEKNO=13,15,50'], + ['FREQ=DAILY;BYYEARDAY=1'], + ['FREQ=WEEKLY;BYYEARDAY=1'], + ['FREQ=MONTHLY;BYYEARDAY=1'], + ['FREQ=WEEKLY;BYMONTHDAY=1'], + ]; + } + public function testMonthlyByDay() { $this->parse( @@ -1032,7 +1056,7 @@ public function testUntilAndCount() public function testIgnoredStuff() { $this->parse( - 'FREQ=DAILY;BYSECOND=1;BYMINUTE=1;BYYEARDAY=1;BYWEEKNO=1;COUNT=2', + 'FREQ=DAILY;BYSECOND=1;BYMINUTE=1;COUNT=2', '2014-08-02 00:15:00', [ '2014-08-02 00:15:00', From 13cbb281561c58211291b314e8dd2d6d6ac8bfac Mon Sep 17 00:00:00 2001 From: giuseppe-arcuti <93533561+giuseppe-arcuti@users.noreply.github.com> Date: Tue, 8 Mar 2022 11:00:00 +0100 Subject: [PATCH 51/92] 4.10.0 release (#49) --- CHANGELOG.md | 4 ++++ lib/Version.php | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed7e6e2e5..2db520003 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ ChangeLog ========= +4.10.0 (2022-03-08) +------------------- +* #48 Block invalid combinations of FREQ with BY rules + 4.9.0 (2022-02-15) ------------------ * #46 Add support UTC-05:00 timezone diff --git a/lib/Version.php b/lib/Version.php index 628e06ab4..677b4ed95 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -14,5 +14,5 @@ class Version /** * Full version number. */ - const VERSION = '4.9.0'; + const VERSION = '4.10.0'; } From f2d3f95bcb69f53128041de016a685a724d554c5 Mon Sep 17 00:00:00 2001 From: liurxliu <31659588+liurxliu@users.noreply.github.com> Date: Fri, 22 Apr 2022 15:41:52 +0800 Subject: [PATCH 52/92] Add LowercaseTimezoneIdentifier (#50) Co-authored-by: Ren Xie Liu --- lib/TimeZoneUtil.php | 2 ++ .../LowercaseTimezoneIdentifier.php | 21 ++++++++++++ tests/VObject/TimeZoneUtilTest.php | 33 +++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 lib/TimezoneGuesser/LowercaseTimezoneIdentifier.php diff --git a/lib/TimeZoneUtil.php b/lib/TimeZoneUtil.php index 65f2e6c8c..fe7bc769f 100644 --- a/lib/TimeZoneUtil.php +++ b/lib/TimeZoneUtil.php @@ -9,6 +9,7 @@ use Sabre\VObject\TimezoneGuesser\FindFromTimezoneMap; use Sabre\VObject\TimezoneGuesser\GuessFromLicEntry; use Sabre\VObject\TimezoneGuesser\GuessFromMsTzId; +use Sabre\VObject\TimezoneGuesser\LowercaseTimezoneIdentifier; use Sabre\VObject\TimezoneGuesser\TimezoneFinder; use Sabre\VObject\TimezoneGuesser\TimezoneGuesser; @@ -40,6 +41,7 @@ private function __construct() $this->addFinder('tzid', new FindFromTimezoneIdentifier()); $this->addFinder('tzmap', new FindFromTimezoneMap()); $this->addFinder('offset', new FindFromOffset()); + $this->addFinder('lowercase', new LowercaseTimezoneIdentifier()); } private static function getInstance(): self diff --git a/lib/TimezoneGuesser/LowercaseTimezoneIdentifier.php b/lib/TimezoneGuesser/LowercaseTimezoneIdentifier.php new file mode 100644 index 000000000..69fba4424 --- /dev/null +++ b/lib/TimezoneGuesser/LowercaseTimezoneIdentifier.php @@ -0,0 +1,21 @@ + 'America/Lima', ]; } + + /** + * @dataProvider letterCaseTimeZoneProvider + */ + public function testDifferentLetterCaseTimeZone(string $origin, string $expected) + { + $tz = TimeZoneUtil::getTimeZone($origin, null, true); + $ex = new \DateTimeZone($expected); + $this->assertEquals($ex->getName(), $tz->getName()); + } + + public function letterCaseTimeZoneProvider(): iterable + { + yield 'case 1' => [ + 'origin' => 'Europe/paris', + 'expected' => 'Europe/Paris', + ]; + + yield 'case 2' => [ + 'origin' => 'europe/paris', + 'expected' => 'Europe/Paris', + ]; + + yield 'case 3' => [ + 'origin' => 'Europe/pAris', + 'expected' => 'Europe/Paris', + ]; + + yield 'case 4' => [ + 'origin' => 'Asia/taipei', + 'expected' => 'Asia/Taipei', + ]; + } } From 7d6cba69a8ac296fdd1040e3591537a05142924a Mon Sep 17 00:00:00 2001 From: Ren Xie Liu Date: Fri, 22 Apr 2022 17:56:34 +0800 Subject: [PATCH 53/92] 4.11.0 release --- CHANGELOG.md | 4 ++++ lib/Version.php | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2db520003..064ca2f95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ ChangeLog ========= +4.11.0 (2022-04-22) +------------------- +* #50 Add lowercase timezone finder + 4.10.0 (2022-03-08) ------------------- * #48 Block invalid combinations of FREQ with BY rules diff --git a/lib/Version.php b/lib/Version.php index 677b4ed95..3d6cf4be4 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -14,5 +14,5 @@ class Version /** * Full version number. */ - const VERSION = '4.10.0'; + const VERSION = '4.11.0'; } From 0ed2875e22a144f9437ea574fcde9e857a053004 Mon Sep 17 00:00:00 2001 From: liurxliu <31659588+liurxliu@users.noreply.github.com> Date: Wed, 4 May 2022 21:31:02 +0800 Subject: [PATCH 54/92] [Calendar] Add FindFromOutlookCities timezone finder (#52) Co-authored-by: Ren Xie Liu --- lib/TimeZoneUtil.php | 2 + lib/TimezoneGuesser/FindFromOutlookCities.php | 44 +++++++++++++++++++ tests/VObject/TimeZoneUtilTest.php | 37 ++++++++++++++++ 3 files changed, 83 insertions(+) create mode 100644 lib/TimezoneGuesser/FindFromOutlookCities.php diff --git a/lib/TimeZoneUtil.php b/lib/TimeZoneUtil.php index fe7bc769f..ec4a238d0 100644 --- a/lib/TimeZoneUtil.php +++ b/lib/TimeZoneUtil.php @@ -5,6 +5,7 @@ use DateTimeZone; use InvalidArgumentException; use Sabre\VObject\TimezoneGuesser\FindFromOffset; +use Sabre\VObject\TimezoneGuesser\FindFromOutlookCities; use Sabre\VObject\TimezoneGuesser\FindFromTimezoneIdentifier; use Sabre\VObject\TimezoneGuesser\FindFromTimezoneMap; use Sabre\VObject\TimezoneGuesser\GuessFromLicEntry; @@ -42,6 +43,7 @@ private function __construct() $this->addFinder('tzmap', new FindFromTimezoneMap()); $this->addFinder('offset', new FindFromOffset()); $this->addFinder('lowercase', new LowercaseTimezoneIdentifier()); + $this->addFinder('outlookCities', new FindFromOutlookCities()); } private static function getInstance(): self diff --git a/lib/TimezoneGuesser/FindFromOutlookCities.php b/lib/TimezoneGuesser/FindFromOutlookCities.php new file mode 100644 index 000000000..6ab672ecf --- /dev/null +++ b/lib/TimezoneGuesser/FindFromOutlookCities.php @@ -0,0 +1,44 @@ + 'Asia/Taipei', ]; } + + /** + * @dataProvider outlookCitiesProvider + */ + public function testOutlookCities(string $origin, bool $failIfUncertain, string $expected) + { + $tz = TimeZoneUtil::getTimeZone($origin, null, $failIfUncertain); + $ex = new \DateTimeZone($expected); + $this->assertEquals($ex->getName(), $tz->getName()); + } + + public function outlookCitiesProvider(): iterable + { + yield 'case 1' => [ + 'origin' => 'TZID:(UTC+01:00) Bruxelles\, København\, Madrid\, Paris', + 'failIfUncertain' => true, + 'expected' => 'Europe/Madrid', + ]; + + yield 'case 2' => [ + 'origin' => 'TZID:(UTC+01:00) Bruxelles, København, Madrid, Paris', + 'failIfUncertain' => true, + 'expected' => 'Europe/Madrid', + ]; + + yield 'case 3' => [ + 'origin' => 'TZID:(UTC+01:00)Bruxelles\, København\, Madrid\, Paris', + 'failIfUncertain' => true, + 'expected' => 'Europe/Madrid', + ]; + + yield 'case 4' => [ + 'origin' => 'Bruxelles\, København\, Madrid\, Paris', + 'failIfUncertain' => false, + 'expected' => 'UTC', + ]; + } } From fcf1ee20f303f11c0a4d751818fb1f3b4359ede7 Mon Sep 17 00:00:00 2001 From: Ren Xie Liu Date: Thu, 5 May 2022 14:18:06 +0800 Subject: [PATCH 55/92] 4.12.0 release --- CHANGELOG.md | 4 ++++ lib/Version.php | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 064ca2f95..d04130a93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ ChangeLog ========= +4.12.0 (2022-05-05) +------------------- +* #52 Add FindFromOutlookCities timezone finder + 4.11.0 (2022-04-22) ------------------- * #50 Add lowercase timezone finder diff --git a/lib/Version.php b/lib/Version.php index 3d6cf4be4..282af5fdb 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -14,5 +14,5 @@ class Version /** * Full version number. */ - const VERSION = '4.11.0'; + const VERSION = '4.12.0'; } From 623bd9a54b339e832a1bc8e09c709a92a8809f2e Mon Sep 17 00:00:00 2001 From: liurxliu <31659588+liurxliu@users.noreply.github.com> Date: Thu, 26 May 2022 17:53:55 +0800 Subject: [PATCH 56/92] [Calendar] Handle version timezone (#58) Co-authored-by: Ren Xie Liu --- lib/TimeZoneUtil.php | 2 ++ .../FindFromMzVersionTimezone.php | 35 +++++++++++++++++++ tests/VObject/TimeZoneUtilTest.php | 25 +++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 lib/TimezoneGuesser/FindFromMzVersionTimezone.php diff --git a/lib/TimeZoneUtil.php b/lib/TimeZoneUtil.php index ec4a238d0..b5d67998b 100644 --- a/lib/TimeZoneUtil.php +++ b/lib/TimeZoneUtil.php @@ -4,6 +4,7 @@ use DateTimeZone; use InvalidArgumentException; +use Sabre\VObject\TimezoneGuesser\FindFromMzVersionTimezone; use Sabre\VObject\TimezoneGuesser\FindFromOffset; use Sabre\VObject\TimezoneGuesser\FindFromOutlookCities; use Sabre\VObject\TimezoneGuesser\FindFromTimezoneIdentifier; @@ -44,6 +45,7 @@ private function __construct() $this->addFinder('offset', new FindFromOffset()); $this->addFinder('lowercase', new LowercaseTimezoneIdentifier()); $this->addFinder('outlookCities', new FindFromOutlookCities()); + $this->addFinder('version', new FindFromMzVersionTimezone()); } private static function getInstance(): self diff --git a/lib/TimezoneGuesser/FindFromMzVersionTimezone.php b/lib/TimezoneGuesser/FindFromMzVersionTimezone.php new file mode 100644 index 000000000..d555084fb --- /dev/null +++ b/lib/TimezoneGuesser/FindFromMzVersionTimezone.php @@ -0,0 +1,35 @@ + Eastern Standard Time + */ +class FindFromMzVersionTimezone implements TimezoneFinder +{ + public function find(string $tzid, bool $failIfUncertain = false): ?DateTimeZone + { + if (strlen($tzid) < 1) { + return null; + } + + $trailingChar = (int) $tzid[strlen($tzid)-1]; + if ($trailingChar <= 9 && $trailingChar >= 1) { + $tz = TimeZoneUtil::getTimeZone(substr($tzid, 0, strrpos($tzid, ' '))); + if ($tz->getName() === 'UTC') { + return null; + } + + return $tz; + } + + return null; + } +} diff --git a/tests/VObject/TimeZoneUtilTest.php b/tests/VObject/TimeZoneUtilTest.php index f024b63b4..eae789c5c 100644 --- a/tests/VObject/TimeZoneUtilTest.php +++ b/tests/VObject/TimeZoneUtilTest.php @@ -533,4 +533,29 @@ public function outlookCitiesProvider(): iterable 'expected' => 'UTC', ]; } + + /** + * @dataProvider versionTzProvider + */ + public function testVersionTz(string $origin, bool $failIfUncertain, string $expected) + { + $tz = TimeZoneUtil::getTimeZone($origin, null, $failIfUncertain); + $ex = new \DateTimeZone($expected); + $this->assertEquals($ex->getName(), $tz->getName()); + } + + public function versionTzProvider(): iterable + { + yield 'case 1' => [ + 'origin' => 'Eastern Standard Time 1', + 'failIfUncertain' => true, + 'expected' => 'America/New_York', + ]; + + yield 'case 2' => [ + 'origin' => 'Eastern Standard Time 2', + 'failIfUncertain' => true, + 'expected' => 'America/New_York', + ]; + } } From a165030c81c5ed89cc70765fc65992edb565ac79 Mon Sep 17 00:00:00 2001 From: Christopher Szu Date: Thu, 26 May 2022 20:44:23 +0800 Subject: [PATCH 57/92] Feat/merge upstream 2022 05 (#56) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix Bad file descriptor (7.4) (#469) * travis: allow failure for phpstan for now (#470) * travis: add php 7.4snapshot build (#471) * reduce phpstan level back to 0, as level 1 is failing right now (#472) * reduce phpstan level back to 0, as level 1 is failing right now * travis: no longer allow failures for phpstan * Fixed typo in vobject CLI help (#477) * Release 4.2.1 (#479) * Release 4.2.1 * Update CHANGELOG.md * Prepare next iteration (#480) * Run phpstan on PHP >= 7.1 (#483) * Add TZ in iTip REPLY messages * Added phpstan for tests folder (#485) * Add PHONE-NUMBER value type (used for TEL in vCard 3.0) (#486) Signed-off-by: Christian Kraus * use latest phpstan 0.12.5 in CI * Add PHPstorm .idea to .gitignore * Fix various typos * Release 4.2.2 (#490) * Release 4.2.2 * Update CHANGELOG.md * Prepare next iteration * Update dependencies and code style tools * Remove unneeded 'bootstrap' line from phpstan.neon * Apply php-cs-fixer code style changes * run php-cs-fixer in CI * php-cs-fixer must be at least 2.16.1 for PHP 7.4 * Use phpunit8 where possible * Fixed phpstan level 1 errors * Make sure there is no logic change * Prevent setting foreach key beforehand * Refactored fqcn strings to ::class to allow checking with phpstan (#495) * Release 4.3.0 (#497) * Update CHANGELOG.md * Update Version.php * Added phpstan to dev dependencies * Reset bin-dir config * Added convenient development commands * Cleaned up .gitignore These entries should be in the developer's global .gitignore * cs-fixer: don't check only the lib folder * Decoupled cs-fixer command from chosen tool * Standardize CI * Use phpunit 9 where possible * Only upload coverage when it has been collected * Replace assertRegExp with assertMatchesRegularExpression in unit test * fix an incomplete phpdoc type annotation * Release 4.3.1 * Adjust boolean vars in .travis.yml to prepare for PHP8.0 * Run unit tests on PHP8 * Fixup calendar parameter to Broker parseEvent * Release 4.3.2 * Remove Pacific-New obsolete timezone * Do composer remove --no-update in Travis * Use min php-cs-fixer 2.16.7 * Release 4.3.3 * Add .gitattributes * Explicitly select PHP 8.0 in CI * Use latest php-cs-fixer 2.17.1 * Update windowszones timezone data to 2020-12-13 * Fix typos * Reassign modified date in yearly rrule * Add test * Code style * Add test for calendar expand * adjust unit test settings for time limits Some tests were testing a bug that caused an infinite loop. Annotate those tests with large, small annotations. Turn on enforceTimeLimit, failOnWarning and failOnRisky so that the annotations are enforced when unit tests are run. Add phpunit/php-invoker to the composer require-dev becaause this is required to make the enforceTimeLimit setting effective. * Release 4.3.4 * Make use of until parameter in nextMonthly function * CS FIX * CS FIX * Fix breaking tests * create testMonthlyByDayUntil * create testMonthlyByDayUntilWithImpossibleNextOccurrence * Fix setting properties with group assignment * Unit test for adding properties with group for a VCard * Adapt style * Release 4.3.5 * tests: migrate from Travis to gh-actions * Removed travis config * Minor edit to README * Run phpunit with coverage in CI * Fix deprecated usages and return types on PHP 8.1 * sync ci.yml to match other repos * Changes that should have happened for 4.3.6 * changelog and VERSION bump for 4.3.7 * EventIterator returns wrong endTime (#534) * Reordering of the attendees should not be a signitifcant change (sabre-io#540) * Reordering of vevent should not be a significant change (#542) * Prepare release 4.3.8 * Allow easier extension of the timezone guessing This will ease customization of timezone-guessing as it is now gets easier to extend that process with own implementations (as long as they implement the appropriate interface) This is espechially necessary when wanting to actually guess a timezone via the rules defined in the VTIMEZONE-entry (which is currently not done) * testEmptyTimeZone * Changelog for 4.4.0 * Fix Iterator method signatures This avoids warnings under PHP>=8.1 Real return types should be added before PHP 9 Signed-off-by: Côme Chilliet * Avoid passing null as separator for implode Signed-off-by: Côme Chilliet * Prepare release 4.4.1 * Component/select: Before uppercasing $child->group, make sure it isn't null Signed-off-by: Gergely Nagy * fix php 8.1 null string deprecation in built-in functions * drop duplicated workflow config Co-authored-by: Remi Collet Co-authored-by: Markus Staab Co-authored-by: Dominik Co-authored-by: Jeroen van Oort Co-authored-by: Renaud BOYER Co-authored-by: Christian Kraus Co-authored-by: Thomas Müller Co-authored-by: Phil Davis Co-authored-by: Michael Stilkerich Co-authored-by: Stéphane Co-authored-by: Allon Moritz Co-authored-by: Jair Cueva Junior Co-authored-by: Parajuli Kiran Co-authored-by: Cédric Anne Co-authored-by: Holger Floerke Co-authored-by: Andreas Heigl Co-authored-by: Côme Chilliet Co-authored-by: Gergely Nagy Co-authored-by: Thomas Müller <1005065+DeepDiver1975@users.noreply.github.com> Co-authored-by: Christopher Szu --- lib/Component.php | 8 ++++---- lib/Parameter.php | 4 ++-- lib/Property.php | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/Component.php b/lib/Component.php index f33b628a7..c1a63258e 100644 --- a/lib/Component.php +++ b/lib/Component.php @@ -212,14 +212,14 @@ public function getComponents() * string ("HOME.EMAIL"). If you want to search on a specific property that * has not been assigned a group, specify ".EMAIL". * - * @param string $name + * @param string|null $name * * @return array */ public function select($name) { $group = null; - $name = strtoupper($name); + $name = is_null($name) ? '' : strtoupper($name); if (false !== strpos($name, '.')) { list($group, $name) = explode('.', $name, 2); } @@ -238,7 +238,7 @@ public function select($name) return array_filter( $result, function ($child) use ($group) { - return $child instanceof Property && strtoupper($child->group) === $group; + return $child instanceof Property && strtoupper($child->group ?? '') === $group; } ); } @@ -249,7 +249,7 @@ function ($child) use ($group) { $result = []; foreach ($this->children as $childGroup) { foreach ($childGroup as $child) { - if ($child instanceof Property && strtoupper($child->group) === $group) { + if ($child instanceof Property && $child->group && strtoupper($child->group) === $group) { $result[] = $child; } } diff --git a/lib/Parameter.php b/lib/Parameter.php index 7e4d55743..5df6238e0 100644 --- a/lib/Parameter.php +++ b/lib/Parameter.php @@ -47,12 +47,12 @@ class Parameter extends Node * * It's recommended to use the create:: factory method instead. * - * @param string $name + * @param string|null $name * @param string $value */ public function __construct(Document $root, $name, $value = null) { - $this->name = strtoupper($name); + $this->name = is_null($name) ? '' : strtoupper($name); $this->root = $root; if (is_null($name)) { $this->noName = true; diff --git a/lib/Property.php b/lib/Property.php index 50cda9684..56096dafe 100644 --- a/lib/Property.php +++ b/lib/Property.php @@ -30,7 +30,7 @@ abstract class Property extends Node * * This is only used in vcards * - * @var string + * @var string|null */ public $group; From b1770c9be4632e60003c4ef2b9ca435190a56114 Mon Sep 17 00:00:00 2001 From: Christopher Szu Date: Fri, 27 May 2022 10:18:04 +0800 Subject: [PATCH 58/92] prepare release 4.13.0 --- CHANGELOG.md | 5 +++++ lib/Version.php | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d04130a93..d3d4fd0cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ ChangeLog ========= +4.13.0 (2022-05-27) +------------------- +* #56 Merge upstream changes from sabre-io/vobject@a595790 into protonlabs/vobject +* #58 Handle version timezone + 4.12.0 (2022-05-05) ------------------- * #52 Add FindFromOutlookCities timezone finder diff --git a/lib/Version.php b/lib/Version.php index 282af5fdb..a8f592170 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -14,5 +14,5 @@ class Version /** * Full version number. */ - const VERSION = '4.12.0'; + const VERSION = '4.13.0'; } From 1f66ae121f69cd02bda93412549563a239512bdd Mon Sep 17 00:00:00 2001 From: liurxliu <31659588+liurxliu@users.noreply.github.com> Date: Mon, 30 May 2022 17:28:16 +0800 Subject: [PATCH 59/92] [Calendar] Add FindFromOffsetName (#54) Co-authored-by: Ren Xie Liu --- lib/TimeZoneUtil.php | 4 +- .../FindFromMzVersionTimezone.php | 7 ++- lib/TimezoneGuesser/FindFromOffsetName.php | 53 +++++++++++++++++++ tests/VObject/TimeZoneUtilTest.php | 15 ++++++ 4 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 lib/TimezoneGuesser/FindFromOffsetName.php diff --git a/lib/TimeZoneUtil.php b/lib/TimeZoneUtil.php index b5d67998b..452afc5e5 100644 --- a/lib/TimeZoneUtil.php +++ b/lib/TimeZoneUtil.php @@ -6,6 +6,7 @@ use InvalidArgumentException; use Sabre\VObject\TimezoneGuesser\FindFromMzVersionTimezone; use Sabre\VObject\TimezoneGuesser\FindFromOffset; +use Sabre\VObject\TimezoneGuesser\FindFromOffsetName; use Sabre\VObject\TimezoneGuesser\FindFromOutlookCities; use Sabre\VObject\TimezoneGuesser\FindFromTimezoneIdentifier; use Sabre\VObject\TimezoneGuesser\FindFromTimezoneMap; @@ -46,6 +47,7 @@ private function __construct() $this->addFinder('lowercase', new LowercaseTimezoneIdentifier()); $this->addFinder('outlookCities', new FindFromOutlookCities()); $this->addFinder('version', new FindFromMzVersionTimezone()); + $this->addFinder('offsetName', new FindFromOffsetName()); } private static function getInstance(): self @@ -276,4 +278,4 @@ public static function getIdentifiersBC() { return include __DIR__.'/timezonedata/php-bc.php'; } -} \ No newline at end of file +} diff --git a/lib/TimezoneGuesser/FindFromMzVersionTimezone.php b/lib/TimezoneGuesser/FindFromMzVersionTimezone.php index d555084fb..b6290288f 100644 --- a/lib/TimezoneGuesser/FindFromMzVersionTimezone.php +++ b/lib/TimezoneGuesser/FindFromMzVersionTimezone.php @@ -22,7 +22,12 @@ public function find(string $tzid, bool $failIfUncertain = false): ?DateTimeZone $trailingChar = (int) $tzid[strlen($tzid)-1]; if ($trailingChar <= 9 && $trailingChar >= 1) { - $tz = TimeZoneUtil::getTimeZone(substr($tzid, 0, strrpos($tzid, ' '))); + $emptySpace = strrpos($tzid, ' '); + if ($emptySpace === false) { + return null; + } + + $tz = TimeZoneUtil::getTimeZone(substr($tzid, 0, $emptySpace)); if ($tz->getName() === 'UTC') { return null; } diff --git a/lib/TimezoneGuesser/FindFromOffsetName.php b/lib/TimezoneGuesser/FindFromOffsetName.php new file mode 100644 index 000000000..73a9eab34 --- /dev/null +++ b/lib/TimezoneGuesser/FindFromOffsetName.php @@ -0,0 +1,53 @@ + 'Africa/Lagos', + '+02:00' => 'Africa/Cairo', + '+03:00' => 'Europe/Moscow', + '+04:00' => 'Asia/Dubai', + '+05:00' => 'Asia/Karachi', + '+06:00' => 'Asia/Dhaka', + '+07:00' => 'Asia/Jakarta', + '+08:00' => 'Asia/Shanghai', + '+09:00' => 'Asia/Tokyo', + '+10:00' => 'Australia/Sydney', + '+11:00' => 'Pacific/Noumea', + '+12:00' => 'Pacific/Auckland', + '+13:00' => 'Pacific/Apia', + '-01:00' => 'Atlantic/Cape_Verde', + '-02:00' => 'Atlantic/South_Georgia', + '-03:00' => 'America/Sao_Paulo', + '-04:00' => 'America/Manaus', + '-05:00' => 'America/Lima', + '-06:00' => 'America/Guatemala', + '-07:00' => 'America/Hermosillo', + '-08:00' => 'America/Los_Angeles', + '-09:00' => 'Pacific/Gambier', + '-10:00' => 'America/Anchorage', + '-11:00' => 'Pacific/Niue', + ]; + + public function find(string $tzid, bool $failIfUncertain = false): ?DateTimeZone + { + // only handle number timezone + if (strlen($tzid) > 6) { + return null; + } + + try { + $tzid = new DateTimeZone($tzid); + + return new DateTimeZone(self::$offsetTimezones[$tzid->getName()]) ?? null; + } catch (\Exception $e) { + return null; + } + } +} diff --git a/tests/VObject/TimeZoneUtilTest.php b/tests/VObject/TimeZoneUtilTest.php index eae789c5c..c7117ad4a 100644 --- a/tests/VObject/TimeZoneUtilTest.php +++ b/tests/VObject/TimeZoneUtilTest.php @@ -462,6 +462,21 @@ public function offsetTimeZoneProvider(): iterable 'origin' => 'UTC-05:00', 'expected' => 'America/Lima', ]; + + yield '-5' => [ + 'origin' => '-5', + 'expected' => 'America/Lima', + ]; + + yield '-05' => [ + 'origin' => '-05', + 'expected' => 'America/Lima', + ]; + + yield '-05:00' => [ + 'origin' => '-05:00', + 'expected' => 'America/Lima', + ]; } /** From 9f37c06136c3f0030bca02e93fd5f764f7d6bc22 Mon Sep 17 00:00:00 2001 From: Ren Xie Liu Date: Tue, 31 May 2022 16:15:34 +0800 Subject: [PATCH 60/92] prepare release 4.14.0 --- CHANGELOG.md | 4 ++++ lib/Version.php | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3d4fd0cf..dfbd3cb26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ ChangeLog ========= +4.14.0 (2022-05-31) +------------------- +* #54 Add FindFromOffsetName + 4.13.0 (2022-05-27) ------------------- * #56 Merge upstream changes from sabre-io/vobject@a595790 into protonlabs/vobject diff --git a/lib/Version.php b/lib/Version.php index a8f592170..2ac70886a 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -14,5 +14,5 @@ class Version /** * Full version number. */ - const VERSION = '4.13.0'; + const VERSION = '4.14.0'; } From ee3e095f0a7861fda24ff7a31cb6f239c5515044 Mon Sep 17 00:00:00 2001 From: liurxliu <31659588+liurxliu@users.noreply.github.com> Date: Wed, 22 Jun 2022 19:48:23 +0800 Subject: [PATCH 61/92] [Calendar] Handle customized timezone (#57) * [Calendar] Handle customized timezone * [Calendar] Add active customized time zone guesser flag Co-authored-by: Ren Xie Liu --- lib/Property/ICalendar/DateTime.php | 8 +- lib/TimeZoneUtil.php | 19 +++- .../GuessFromCustomizedTimeZone.php | 86 +++++++++++++++++ lib/TimezoneGuesser/GuessFromLicEntry.php | 4 + tests/VObject/TimeZoneUtilTest.php | 95 +++++++++++++++++++ 5 files changed, 204 insertions(+), 8 deletions(-) create mode 100644 lib/TimezoneGuesser/GuessFromCustomizedTimeZone.php diff --git a/lib/Property/ICalendar/DateTime.php b/lib/Property/ICalendar/DateTime.php index cbafc16ba..afeb8ff81 100644 --- a/lib/Property/ICalendar/DateTime.php +++ b/lib/Property/ICalendar/DateTime.php @@ -131,9 +131,9 @@ public function isFloating() * * @return \DateTimeImmutable */ - public function getDateTime(DateTimeZone $timeZone = null) + public function getDateTime(DateTimeZone $timeZone = null, bool $activeCustomizedGuesser = true) { - $dt = $this->getDateTimes($timeZone); + $dt = $this->getDateTimes($timeZone, $activeCustomizedGuesser); if (!$dt) { return; } @@ -153,13 +153,13 @@ public function getDateTime(DateTimeZone $timeZone = null) * @return \DateTimeImmutable[] * @return \DateTime[] */ - public function getDateTimes(DateTimeZone $timeZone = null) + public function getDateTimes(DateTimeZone $timeZone = null, bool $activeCustomizedGuesser = true) { // Does the property have a TZID? $tzid = $this['TZID']; if ($tzid) { - $timeZone = TimeZoneUtil::getTimeZone((string) $tzid, $this->root, true); + $timeZone = TimeZoneUtil::getTimeZone((string) $tzid, $this->root, true, $activeCustomizedGuesser); } $dts = []; diff --git a/lib/TimeZoneUtil.php b/lib/TimeZoneUtil.php index 452afc5e5..3d9885b0d 100644 --- a/lib/TimeZoneUtil.php +++ b/lib/TimeZoneUtil.php @@ -12,6 +12,7 @@ use Sabre\VObject\TimezoneGuesser\FindFromTimezoneMap; use Sabre\VObject\TimezoneGuesser\GuessFromLicEntry; use Sabre\VObject\TimezoneGuesser\GuessFromMsTzId; +use Sabre\VObject\TimezoneGuesser\GuessFromCustomizedTimeZone; use Sabre\VObject\TimezoneGuesser\LowercaseTimezoneIdentifier; use Sabre\VObject\TimezoneGuesser\TimezoneFinder; use Sabre\VObject\TimezoneGuesser\TimezoneGuesser; @@ -83,7 +84,7 @@ private function addFinder(string $key, TimezoneFinder $finder): void * Alternatively, if $failIfUncertain is set to true, it will throw an * exception if we cannot accurately determine the timezone. */ - private function findTimeZone(string $tzid, Component $vcalendar = null, bool $failIfUncertain = false): DateTimeZone + private function findTimeZone(string $tzid, Component $vcalendar = null, bool $failIfUncertain = false, bool $activeCustomizedGuesser = false): DateTimeZone { foreach ($this->timezoneFinders as $timezoneFinder) { $timezone = $timezoneFinder->find($tzid, $failIfUncertain); @@ -94,11 +95,21 @@ private function findTimeZone(string $tzid, Component $vcalendar = null, bool $f return $timezone; } + if (!$activeCustomizedGuesser) { + unset($this->timezoneGuessers['customized']); + } + if ($vcalendar) { + // We temporary add the customized timezone guesser if needed + $guessers = $this->timezoneGuessers; + if ($activeCustomizedGuesser) { + $guessers[] = new GuessFromCustomizedTimeZone(); + } + // If that didn't work, we will scan VTIMEZONE objects foreach ($vcalendar->select('VTIMEZONE') as $vtimezone) { if ((string) $vtimezone->TZID === $tzid) { - foreach ($this->timezoneGuessers as $timezoneGuesser) { + foreach ($guessers as $timezoneGuesser) { $timezone = $timezoneGuesser->guess($vtimezone, $failIfUncertain); if (!$timezone instanceof DateTimeZone) { continue; @@ -134,9 +145,9 @@ public static function addTimezoneFinder(string $key, TimezoneFinder $finder): v * * @return DateTimeZone */ - public static function getTimeZone($tzid, Component $vcalendar = null, $failIfUncertain = false) + public static function getTimeZone($tzid, Component $vcalendar = null, $failIfUncertain = false, bool $activeCustomizedGuesser = true) { - return self::getInstance()->findTimeZone($tzid, $vcalendar, $failIfUncertain); + return self::getInstance()->findTimeZone($tzid, $vcalendar, $failIfUncertain, $activeCustomizedGuesser); } public static function clean(): void diff --git a/lib/TimezoneGuesser/GuessFromCustomizedTimeZone.php b/lib/TimezoneGuesser/GuessFromCustomizedTimeZone.php new file mode 100644 index 000000000..3cf901ddf --- /dev/null +++ b/lib/TimezoneGuesser/GuessFromCustomizedTimeZone.php @@ -0,0 +1,86 @@ +TZID->getValue() !== 'Customized Time Zone') { + return null; + } + + $timezones = DateTimeZone::listIdentifiers(); + $standard = $vtimezone->STANDARD; + $daylight = $vtimezone->DAYLIGHT; + + $standardOffset = $standard->TZOFFSETTO->getValue(); + $standardRRule = $daylight ? $standard->RRULE->getValue() : 'FREQ=DAILY'; + // The guess will not be perfectly matched since we use the timezone data of the current year + // It might be wrong if the timezone data changed in the past + $year = (new DateTimeImmutable('now'))->format('Y'); + $start = new DateTimeImmutable($year . '-01-01'); + $standardIterator = new RRuleIterator($standardRRule, $start); + $standardIterator->next(); + + $daylightOffset = $daylight ? $daylight->TZOFFSETTO->getValue() : ''; + $daylightRRule = $daylight ? $daylight->RRULE->getValue() : ''; + $daylightIterator = $daylight ? new RRuleIterator($daylightRRule, $standardIterator->current()) : null; + $daylightIterator && $daylightIterator->next(); + + foreach ($timezones as $timezone) { + $tz = new DateTimeZone($timezone); + // check standard + $timestamp = $standardIterator->current()->getTimestamp(); + $transitions = $tz->getTransitions($timestamp, $timestamp + 1); + if (empty($transitions)) { + continue; + } + + $checkOffset = $transitions[0]['offset']; + + if ($checkOffset !== $this->parseOffsetToInteger($standardOffset)) { + continue; + } + + if (!$daylight) { + return TimeZoneUtil::getTimeZone($timezone, null, $failIfUncertain); + } + + // check daylight + $timestamp = $daylightIterator->current()->getTimestamp(); + $transitions = $tz->getTransitions($timestamp, $timestamp + 1); + if (empty($transitions)) { + continue; + } + + $checkOffset = $transitions[0]['offset']; + if ($checkOffset === $this->parseOffsetToInteger($daylightOffset)) { + return TimeZoneUtil::getTimeZone($timezone, null, $failIfUncertain); + } + } + + return null; + } + + private function parseOffsetToInteger(string $offset): int + { + $time = ((int) ($offset[1].$offset[2]) * 60) + (int) ($offset[3].$offset[4]); + + $time = $time * 60; + + if ($offset[0] === "-") { + $time = $time *-1; + } + + return $time; + } +} diff --git a/lib/TimezoneGuesser/GuessFromLicEntry.php b/lib/TimezoneGuesser/GuessFromLicEntry.php index f340a3962..b57029919 100644 --- a/lib/TimezoneGuesser/GuessFromLicEntry.php +++ b/lib/TimezoneGuesser/GuessFromLicEntry.php @@ -21,6 +21,10 @@ public function guess(VTimeZone $vtimezone, bool $failIfUncertain = false): ?Dat $lic = (string) $vtimezone->{'X-LIC-LOCATION'}; + if ($lic === 'Customized Time Zone') { + return null; + } + // Libical generators may specify strings like // "SystemV/EST5EDT". For those we must remove the // SystemV part. diff --git a/tests/VObject/TimeZoneUtilTest.php b/tests/VObject/TimeZoneUtilTest.php index c7117ad4a..c56327e7f 100644 --- a/tests/VObject/TimeZoneUtilTest.php +++ b/tests/VObject/TimeZoneUtilTest.php @@ -573,4 +573,99 @@ public function versionTzProvider(): iterable 'expected' => 'America/New_York', ]; } + + public function testCustomizedTimeZone() + { + $ics = <<assertNotSame('Customized Time Zone', $tz->getName()); + $start = new \DateTimeImmutable('2022-04-25'); + $this->assertSame(10 * 60 * 60, $tz->getOffset($start)); + + $start = new \DateTimeImmutable('2022-11-10'); + $this->assertSame(11 * 60 * 60, $tz->getOffset($start)); + } + + public function testCustomizedTimeZoneWithoutDaylight() + { + $ics = $this->getCustomizedICS(); + $tz = TimeZoneUtil::getTimeZone('Customized Time Zone', Reader::read($ics)); + $this->assertNotSame('Customized Time Zone', $tz->getName()); + $start = new \DateTimeImmutable('2022-04-25'); + $this->assertSame(8 * 60 * 60, $tz->getOffset($start)); + } + + public function testCustomizedTimeZoneFlag() + { + $this->expectException(\InvalidArgumentException::class); + $ics = $this->getCustomizedICS(); + $vobject = Reader::read($ics); + $vobject->VEVENT->DTSTART->getDateTime(null, false); + } + + private function getCustomizedICS(): string + { + return << Date: Thu, 23 Jun 2022 14:12:53 +0800 Subject: [PATCH 62/92] release 4.15.0 --- CHANGELOG.md | 4 ++++ lib/Version.php | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dfbd3cb26..4e3e782a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ ChangeLog ========= +4.15.0 (2022-06-23) +------------------- +* #57 Handle customized timezone + 4.14.0 (2022-05-31) ------------------- * #54 Add FindFromOffsetName diff --git a/lib/Version.php b/lib/Version.php index 2ac70886a..df904561e 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -14,5 +14,5 @@ class Version /** * Full version number. */ - const VERSION = '4.14.0'; + const VERSION = '4.15.0'; } From 665b0ad223f1cf16ee6e08545597ff96497bbd7a Mon Sep 17 00:00:00 2001 From: giuseppe-arcuti <93533561+giuseppe-arcuti@users.noreply.github.com> Date: Wed, 6 Jul 2022 17:00:45 +0200 Subject: [PATCH 63/92] Fix customized timezone guesser (#63) --- .../GuessFromCustomizedTimeZone.php | 17 ++++++++++++++--- tests/VObject/TimeZoneUtilTest.php | 2 +- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/lib/TimezoneGuesser/GuessFromCustomizedTimeZone.php b/lib/TimezoneGuesser/GuessFromCustomizedTimeZone.php index 3cf901ddf..67ff299e6 100644 --- a/lib/TimezoneGuesser/GuessFromCustomizedTimeZone.php +++ b/lib/TimezoneGuesser/GuessFromCustomizedTimeZone.php @@ -21,9 +21,17 @@ public function guess(VTimeZone $vtimezone, bool $failIfUncertain = false): ?Dat $timezones = DateTimeZone::listIdentifiers(); $standard = $vtimezone->STANDARD; $daylight = $vtimezone->DAYLIGHT; + if (!$standard) { + return null; + } + + $standardOffset = $standard->TZOFFSETTO; + if (!$standardOffset) { + return null; + } + $standardOffset = $standardOffset->getValue(); - $standardOffset = $standard->TZOFFSETTO->getValue(); - $standardRRule = $daylight ? $standard->RRULE->getValue() : 'FREQ=DAILY'; + $standardRRule = $standard->RRULE ? $standard->RRULE->getValue() : 'FREQ=DAILY'; // The guess will not be perfectly matched since we use the timezone data of the current year // It might be wrong if the timezone data changed in the past $year = (new DateTimeImmutable('now'))->format('Y'); @@ -31,8 +39,11 @@ public function guess(VTimeZone $vtimezone, bool $failIfUncertain = false): ?Dat $standardIterator = new RRuleIterator($standardRRule, $start); $standardIterator->next(); + if ($daylight && !$daylight->TZOFFSETTO) { + $daylight = null; + } $daylightOffset = $daylight ? $daylight->TZOFFSETTO->getValue() : ''; - $daylightRRule = $daylight ? $daylight->RRULE->getValue() : ''; + $daylightRRule = $daylight ? ($daylight->RRULE ? $daylight->RRULE->getValue() : 'FREQ=DAILY') : ''; $daylightIterator = $daylight ? new RRuleIterator($daylightRRule, $standardIterator->current()) : null; $daylightIterator && $daylightIterator->next(); diff --git a/tests/VObject/TimeZoneUtilTest.php b/tests/VObject/TimeZoneUtilTest.php index c56327e7f..c23dbcf00 100644 --- a/tests/VObject/TimeZoneUtilTest.php +++ b/tests/VObject/TimeZoneUtilTest.php @@ -625,7 +625,7 @@ public function testCustomizedTimeZoneWithoutDaylight() { $ics = $this->getCustomizedICS(); $tz = TimeZoneUtil::getTimeZone('Customized Time Zone', Reader::read($ics)); - $this->assertNotSame('Customized Time Zone', $tz->getName()); + $this->assertSame('Asia/Brunei', $tz->getName()); $start = new \DateTimeImmutable('2022-04-25'); $this->assertSame(8 * 60 * 60, $tz->getOffset($start)); } From 5cde4819ec41e91f16e6b6e9f82a625d8d3d43fa Mon Sep 17 00:00:00 2001 From: giuseppe-arcuti <93533561+giuseppe-arcuti@users.noreply.github.com> Date: Wed, 6 Jul 2022 19:23:23 +0200 Subject: [PATCH 64/92] release 4.16.0 (#64) --- CHANGELOG.md | 4 ++++ lib/Version.php | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e3e782a4..9860f9957 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ ChangeLog ========= +4.16.0 (2022-07-06) +------------------- +* #63 Fix customized timezone guesser + 4.15.0 (2022-06-23) ------------------- * #57 Handle customized timezone diff --git a/lib/Version.php b/lib/Version.php index df904561e..7b64a7a3b 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -14,5 +14,5 @@ class Version /** * Full version number. */ - const VERSION = '4.15.0'; + const VERSION = '4.16.0'; } From 0b93c0bc1e2ee7b58d89aae274bc80e3b0744265 Mon Sep 17 00:00:00 2001 From: liurxliu <31659588+liurxliu@users.noreply.github.com> Date: Fri, 29 Jul 2022 00:26:20 +0800 Subject: [PATCH 65/92] [Calendar] Replace Godthab timezone (#65) Co-authored-by: Ren Xie Liu --- lib/timezonedata/exchangezones.php | 2 +- lib/timezonedata/extrazones.php | 3 ++- lib/timezonedata/lotuszones.php | 2 +- lib/timezonedata/windowszones.php | 2 +- tests/VObject/microsoft-timezones-confluence.csv | 6 +++--- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/timezonedata/exchangezones.php b/lib/timezonedata/exchangezones.php index 586e2615f..9a92cc433 100644 --- a/lib/timezonedata/exchangezones.php +++ b/lib/timezonedata/exchangezones.php @@ -71,7 +71,7 @@ 'Mid-Atlantic' => 'America/Noronha', 'Brasilia' => 'America/Sao_Paulo', // Best guess 'Buenos Aires' => 'America/Argentina/Buenos_Aires', - 'Greenland' => 'America/Godthab', + 'Greenland' => 'Atlantic/Stanley', 'Newfoundland' => 'America/St_Johns', 'Atlantic Time (Canada)' => 'America/Halifax', 'Caracas, La Paz' => 'America/Caracas', diff --git a/lib/timezonedata/extrazones.php b/lib/timezonedata/extrazones.php index 900775a88..6a76a2a41 100644 --- a/lib/timezonedata/extrazones.php +++ b/lib/timezonedata/extrazones.php @@ -75,7 +75,7 @@ 'Goose Bay' => 'America/Goose_Bay', 'Greenland Central' => 'America/Scoresbysund', 'Greenland Eastern' => 'America/Scoresbysund', - 'Greenland Western' => 'America/Godthab', + 'Greenland Western' => 'Atlantic/Stanley', 'Guam' => 'Pacific/Guam', 'Gulf' => 'Asia/Dubai', 'Guyana' => 'America/Guyana', @@ -201,4 +201,5 @@ 'Asia/Kashgar' => 'Asia/Urumqi', 'Pacific/Johnston' => 'Pacific/Honolulu', 'EDT' => 'America/Manaus', + 'America/Godthab' => 'Atlantic/Stanley', ]; diff --git a/lib/timezonedata/lotuszones.php b/lib/timezonedata/lotuszones.php index 4b50808f9..4ba6e3cbe 100644 --- a/lib/timezonedata/lotuszones.php +++ b/lib/timezonedata/lotuszones.php @@ -34,7 +34,7 @@ 'Newfoundland' => 'America/St_Johns', 'Argentina' => 'America/Argentina/Buenos_Aires', 'E. South America' => 'America/Belem', - 'Greenland' => 'America/Godthab', + 'Greenland' => 'Atlantic/Stanley', 'Montevideo' => 'America/Montevideo', 'SA Eastern' => 'America/Belem', // 'Mid-Atlantic' => 'Etc/GMT-2', // conflict with windows timezones. diff --git a/lib/timezonedata/windowszones.php b/lib/timezonedata/windowszones.php index 0c8329d23..669997fd9 100644 --- a/lib/timezonedata/windowszones.php +++ b/lib/timezonedata/windowszones.php @@ -60,7 +60,7 @@ 'GMT Standard Time' => 'Europe/London', 'GTB Standard Time' => 'Europe/Bucharest', 'Georgian Standard Time' => 'Asia/Tbilisi', - 'Greenland Standard Time' => 'America/Godthab', + 'Greenland Standard Time' => 'Atlantic/Stanley', 'Greenwich Standard Time' => 'Atlantic/Reykjavik', 'Haiti Standard Time' => 'America/Port-au-Prince', 'Hawaiian Standard Time' => 'Pacific/Honolulu', diff --git a/tests/VObject/microsoft-timezones-confluence.csv b/tests/VObject/microsoft-timezones-confluence.csv index 2b555de77..f8d83a24a 100644 --- a/tests/VObject/microsoft-timezones-confluence.csv +++ b/tests/VObject/microsoft-timezones-confluence.csv @@ -184,11 +184,11 @@ gilbert islands,Pacific/Tarawa,Pacific/Tarawa,FALSE gmt,Europe/London,Europe/London,FALSE gmt standard time,Europe/London,Europe/London,FALSE goose bay,America/Goose_Bay,America/Goose_Bay,FALSE -greenland,America/Godthab,America/Godthab,FALSE +greenland,Atlantic/Stanley,Atlantic/Stanley,FALSE greenland central,America/Scoresbysund,America/Scoresbysund,FALSE greenland eastern,America/Scoresbysund,America/Scoresbysund,FALSE -greenland standard time,America/Godthab,America/Godthab,FALSE -greenland western,America/Godthab,America/Godthab,FALSE +greenland standard time,Atlantic/Stanley,Atlantic/Stanley,FALSE +greenland western,Atlantic/Stanley,Atlantic/Stanley,FALSE greenwich,Atlantic/Reykjavik,Atlantic/Reykjavik,FALSE "greenwich mean time; dublin, edinburgh, london",Europe/London,Europe/London,FALSE "greenwich mean time: dublin, edinburgh, lisbon, london",Europe/Lisbon,Europe/Lisbon,FALSE From 0c4c6a970f4efa19e16ef7811cfa2cab5218c4f1 Mon Sep 17 00:00:00 2001 From: Ren Xie Liu Date: Fri, 29 Jul 2022 11:56:49 +0800 Subject: [PATCH 66/92] release 4.17.0 --- CHANGELOG.md | 4 ++++ lib/Version.php | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9860f9957..ca198812d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ ChangeLog ========= +4.17.0 (2022-07-29) +------------------- +* #65 [Calendar] Replace Godthab timezone + 4.16.0 (2022-07-06) ------------------- * #63 Fix customized timezone guesser diff --git a/lib/Version.php b/lib/Version.php index 7b64a7a3b..875acf47b 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -14,5 +14,5 @@ class Version /** * Full version number. */ - const VERSION = '4.16.0'; + const VERSION = '4.17.0'; } From ec72aeae385d4c8078817beec099f3fa175a8998 Mon Sep 17 00:00:00 2001 From: Christopher Szu Date: Tue, 30 Aug 2022 11:18:48 +0800 Subject: [PATCH 67/92] Add mapping for CDT timezone --- lib/timezonedata/extrazones.php | 1 + tests/VObject/TimeZoneUtilTest.php | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/lib/timezonedata/extrazones.php b/lib/timezonedata/extrazones.php index 6a76a2a41..b684e9c99 100644 --- a/lib/timezonedata/extrazones.php +++ b/lib/timezonedata/extrazones.php @@ -202,4 +202,5 @@ 'Pacific/Johnston' => 'Pacific/Honolulu', 'EDT' => 'America/Manaus', 'America/Godthab' => 'Atlantic/Stanley', + 'CDT' => 'America/Chicago', ]; diff --git a/tests/VObject/TimeZoneUtilTest.php b/tests/VObject/TimeZoneUtilTest.php index c23dbcf00..fddf3de7e 100644 --- a/tests/VObject/TimeZoneUtilTest.php +++ b/tests/VObject/TimeZoneUtilTest.php @@ -434,6 +434,11 @@ public function unSupportTimezoneProvider(): iterable 'expected' => 'America/Manaus', ]; + yield 'CDT' => [ + 'origin' => 'CDT', + 'expected' => 'America/Chicago', + ]; + if (($handle = fopen(__DIR__ . "/microsoft-timezones-confluence.csv", "r")) !== FALSE) { $data = fgetcsv($handle); while (($data = fgetcsv($handle)) !== FALSE) { From f4a015fde8d494d514b69f635f04df7215805d00 Mon Sep 17 00:00:00 2001 From: Christopher Szu Date: Thu, 1 Sep 2022 13:17:26 +0800 Subject: [PATCH 68/92] prepare release 4.18.0 --- CHANGELOG.md | 4 ++++ lib/Version.php | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca198812d..186ee6e40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ ChangeLog ========= +4.18.0 (2022-09-01) +------------------- +* #69 Add mapping for CDT timezone + 4.17.0 (2022-07-29) ------------------- * #65 [Calendar] Replace Godthab timezone diff --git a/lib/Version.php b/lib/Version.php index 875acf47b..ffc9c1f2f 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -14,5 +14,5 @@ class Version /** * Full version number. */ - const VERSION = '4.17.0'; + const VERSION = '4.18.0'; } From 9aad44d770973879a60d842ce698c88e771706d4 Mon Sep 17 00:00:00 2001 From: Valentin BONNEAUD Date: Wed, 21 Dec 2022 14:06:41 +0100 Subject: [PATCH 69/92] Deprecate Enderbury timezone (#72) --- lib/timezonedata/extrazones.php | 2 +- lib/timezonedata/windowszones.php | 2 +- tests/VObject/microsoft-timezones-confluence.csv | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/timezonedata/extrazones.php b/lib/timezonedata/extrazones.php index b684e9c99..31a165849 100644 --- a/lib/timezonedata/extrazones.php +++ b/lib/timezonedata/extrazones.php @@ -133,7 +133,7 @@ 'Paraguay' => 'America/Asuncion', 'Peru' => 'America/Lima', 'Philippines' => 'Asia/Manila', - 'Phoenix Islands' => 'Pacific/Enderbury', + 'Phoenix Islands' => 'Pacific/Fakaofo', 'Pierre Miquelon' => 'America/Miquelon', 'Pitcairn' => 'Pacific/Pitcairn', 'Pyongyang' => 'Asia/Pyongyang', diff --git a/lib/timezonedata/windowszones.php b/lib/timezonedata/windowszones.php index 669997fd9..c1bd8026f 100644 --- a/lib/timezonedata/windowszones.php +++ b/lib/timezonedata/windowszones.php @@ -232,7 +232,7 @@ 'coordinated universal time+12' => 'Pacific/Tarawa', 'petropavlovsk-kamchatsky - old' => 'Asia/Anadyr', 'chatham islands' => 'Pacific/Chatham', - 'coordinated universal time+13' => 'Pacific/Enderbury', + 'coordinated universal time+13' => 'Pacific/Fakaofo', "nuku'alofa" => 'Pacific/Tongatapu', 'kiritimati island' => 'Pacific/Kiritimati', 'helsinki, kyiv, riga, sofia, tallinn, vilnius' => 'Europe/Helsinki', diff --git a/tests/VObject/microsoft-timezones-confluence.csv b/tests/VObject/microsoft-timezones-confluence.csv index f8d83a24a..bc7945f4e 100644 --- a/tests/VObject/microsoft-timezones-confluence.csv +++ b/tests/VObject/microsoft-timezones-confluence.csv @@ -338,7 +338,7 @@ paraguay standard time,America/Asuncion,America/Asuncion,FALSE "perth, western australia",Australia/Perth,Australia/Perth,FALSE peru,America/Lima,America/Lima,FALSE philippines,Asia/Manila,Asia/Manila,FALSE -phoenix islands,Pacific/Enderbury,Pacific/Enderbury,FALSE +phoenix islands,Pacific/Fakaofo,Pacific/Fakaofo,FALSE pierre miquelon,America/Miquelon,America/Miquelon,FALSE pitcairn,Pacific/Pitcairn,Pacific/Pitcairn,FALSE "prague, central europe",Europe/Prague,Europe/Prague,FALSE @@ -541,7 +541,7 @@ norfolk island,Pacific/Norfolk,,TRUE coordinated universal time+12,Pacific/Tarawa,,TRUE petropavlovsk-kamchatsky - old,Asia/Anadyr,,TRUE chatham islands,Pacific/Chatham,,TRUE -coordinated universal time+13,Pacific/Enderbury,,TRUE +coordinated universal time+13,Pacific/Fakaofo,,TRUE nuku'alofa,Pacific/Tongatapu,,TRUE kiritimati island,Pacific/Kiritimati,,TRUE "helsinki, kyiv, riga, sofia, tallinn, vilnius",Europe/Helsinki,,TRUE From 013a7dbb48d34d6aff74eaf2f0ea61faff807c2d Mon Sep 17 00:00:00 2001 From: giuseppe-arcuti <93533561+giuseppe-arcuti@users.noreply.github.com> Date: Wed, 21 Dec 2022 14:17:24 +0100 Subject: [PATCH 70/92] Release 4.19.0 (#73) --- CHANGELOG.md | 4 ++++ lib/Version.php | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 186ee6e40..c835e36fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ ChangeLog ========= +4.19.0 (2022-12-21) +------------------- +* #72 Deprecate Enderbury timezone + 4.18.0 (2022-09-01) ------------------- * #69 Add mapping for CDT timezone diff --git a/lib/Version.php b/lib/Version.php index ffc9c1f2f..132d98cfc 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -14,5 +14,5 @@ class Version /** * Full version number. */ - const VERSION = '4.18.0'; + const VERSION = '4.19.0'; } From 8a6ec133ef68dd3b236d22ca95990ffcda36d30d Mon Sep 17 00:00:00 2001 From: Christopher Szu Date: Fri, 23 Dec 2022 13:57:32 +0800 Subject: [PATCH 71/92] handle null tzid --- .../GuessFromCustomizedTimeZone.php | 2 +- tests/VObject/Component/VTimeZoneTest.php | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/TimezoneGuesser/GuessFromCustomizedTimeZone.php b/lib/TimezoneGuesser/GuessFromCustomizedTimeZone.php index 67ff299e6..76a53fdb4 100644 --- a/lib/TimezoneGuesser/GuessFromCustomizedTimeZone.php +++ b/lib/TimezoneGuesser/GuessFromCustomizedTimeZone.php @@ -14,7 +14,7 @@ class GuessFromCustomizedTimeZone implements TimezoneGuesser { public function guess(VTimeZone $vtimezone, bool $failIfUncertain = false): ?DateTimeZone { - if ($vtimezone->TZID->getValue() !== 'Customized Time Zone') { + if (null === $vtimezone->TZID || $vtimezone->TZID->getValue() !== 'Customized Time Zone') { return null; } diff --git a/tests/VObject/Component/VTimeZoneTest.php b/tests/VObject/Component/VTimeZoneTest.php index ead22b81e..af9469d36 100644 --- a/tests/VObject/Component/VTimeZoneTest.php +++ b/tests/VObject/Component/VTimeZoneTest.php @@ -51,4 +51,25 @@ public function testGetTimeZone() $obj->VTIMEZONE->getTimeZone() ); } + + public function testGetEmptyTimeZone() + { + $input = <<assertEquals( + $tz, + $obj->VTIMEZONE->getTimeZone() + ); + } } From 7359e882233c36a1710b978376f6825fb82bc4b0 Mon Sep 17 00:00:00 2001 From: Christopher Szu Date: Fri, 23 Dec 2022 15:18:59 +0800 Subject: [PATCH 72/92] Release 4.20.0 --- CHANGELOG.md | 4 ++++ lib/Version.php | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c835e36fe..e25b7aecd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ ChangeLog ========= +4.20.0 (2022-12-23) +------------------- +* #74 Handle null tzid + 4.19.0 (2022-12-21) ------------------- * #72 Deprecate Enderbury timezone diff --git a/lib/Version.php b/lib/Version.php index 132d98cfc..1eed7a286 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -14,5 +14,5 @@ class Version /** * Full version number. */ - const VERSION = '4.19.0'; + const VERSION = '4.20.0'; } From da41cb53a421bd72192b5ef22b190c5ec9a9a480 Mon Sep 17 00:00:00 2001 From: Christopher Szu Date: Tue, 27 Dec 2022 11:38:07 +0800 Subject: [PATCH 73/92] Add timezone mapping for PST --- lib/timezonedata/extrazones.php | 1 + tests/VObject/TimeZoneUtilTest.php | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/lib/timezonedata/extrazones.php b/lib/timezonedata/extrazones.php index 31a165849..58bdabb28 100644 --- a/lib/timezonedata/extrazones.php +++ b/lib/timezonedata/extrazones.php @@ -203,4 +203,5 @@ 'EDT' => 'America/Manaus', 'America/Godthab' => 'Atlantic/Stanley', 'CDT' => 'America/Chicago', + 'PST' => 'America/Los_Angeles', ]; diff --git a/tests/VObject/TimeZoneUtilTest.php b/tests/VObject/TimeZoneUtilTest.php index fddf3de7e..1ca47e338 100644 --- a/tests/VObject/TimeZoneUtilTest.php +++ b/tests/VObject/TimeZoneUtilTest.php @@ -439,6 +439,11 @@ public function unSupportTimezoneProvider(): iterable 'expected' => 'America/Chicago', ]; + yield 'PST' => [ + 'origin' => 'PST', + 'expected' => 'America/Los_Angeles', + ]; + if (($handle = fopen(__DIR__ . "/microsoft-timezones-confluence.csv", "r")) !== FALSE) { $data = fgetcsv($handle); while (($data = fgetcsv($handle)) !== FALSE) { From 6328936fa82b7b201817c31596bbdfbe732710b4 Mon Sep 17 00:00:00 2001 From: Christopher Szu Date: Wed, 28 Dec 2022 12:39:49 +0800 Subject: [PATCH 74/92] Release 4.21.0 --- CHANGELOG.md | 4 ++++ lib/Version.php | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e25b7aecd..e532e3cef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ ChangeLog ========= +4.21.0 (2022-12-28) +------------------- +* #76 Add mapping for PST timezone + 4.20.0 (2022-12-23) ------------------- * #74 Handle null tzid diff --git a/lib/Version.php b/lib/Version.php index 1eed7a286..41ce423dd 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -14,5 +14,5 @@ class Version /** * Full version number. */ - const VERSION = '4.20.0'; + const VERSION = '4.21.0'; } From c948d7118c05feba248075ccb337b88a3219cb16 Mon Sep 17 00:00:00 2001 From: Valentin BONNEAUD Date: Wed, 1 Mar 2023 20:51:01 +0100 Subject: [PATCH 75/92] Add DeprecatedTimezoneIdentifier (#80) --- lib/timezonedata/php-bc.php | 15 +++++++++++++++ tests/VObject/TimeZoneUtilTest.php | 16 ++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/lib/timezonedata/php-bc.php b/lib/timezonedata/php-bc.php index 3116c6868..d9f49008a 100644 --- a/lib/timezonedata/php-bc.php +++ b/lib/timezonedata/php-bc.php @@ -16,6 +16,21 @@ * @license http://sabre.io/license/ Modified BSD License */ return [ + // Moved to backward in 2021b + 'Pacific/Enderbury', + // Moved to backward in 2022b + 'Europe/Kiev', + // Moved to backward in 2022d + 'Europe/Uzhgorod', + 'Europe/Zaporozhye', + // Moved to backward in 2022f + 'America/Thunder_Bay', + 'America/Nipigon', + 'America/Rainy_River', + // Moved to backward in 2022g + 'America/Pangnirtung', + + // Original list 'Africa/Asmera', 'Africa/Timbuktu', 'America/Argentina/ComodRivadavia', diff --git a/tests/VObject/TimeZoneUtilTest.php b/tests/VObject/TimeZoneUtilTest.php index 1ca47e338..069205883 100644 --- a/tests/VObject/TimeZoneUtilTest.php +++ b/tests/VObject/TimeZoneUtilTest.php @@ -184,6 +184,22 @@ public function testLowerCaseTimeZone() $this->assertEquals($ex->getName(), $tz->getName()); } + public function testDeprecatedTimeZone() + { + // Deprecated in 2022b + $tz = TimeZoneUtil::getTimeZone('Europe/Kiev'); + $ex = new \DateTimeZone('Europe/Kiev'); + $this->assertSame($ex->getName(), $tz->getName()); + } + + public function testDeprecatedUnsupportedTimeZone() + { + // Deprecated and unsupported + $tz = TimeZoneUtil::getTimeZone('America/Godthab'); + $ex = new \DateTimeZone('America/Godthab'); + $this->assertNotSame($ex->getName(), $tz->getName()); + } + /** * @dataProvider getPHPTimeZoneIdentifiers */ From 8525beac1eb5e7508977555b74d1423fa2f788cc Mon Sep 17 00:00:00 2001 From: Valentin BONNEAUD Date: Wed, 1 Mar 2023 20:55:49 +0100 Subject: [PATCH 76/92] Release 4.22.0 (#81) --- CHANGELOG.md | 4 ++++ lib/Version.php | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e532e3cef..edf59eb78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ ChangeLog ========= +4.22.0 (2023-03-01) +------------------- +* #80 Allow newly deprecated timezones + 4.21.0 (2022-12-28) ------------------- * #76 Add mapping for PST timezone diff --git a/lib/Version.php b/lib/Version.php index 41ce423dd..858a3e95f 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -14,5 +14,5 @@ class Version /** * Full version number. */ - const VERSION = '4.21.0'; + const VERSION = '4.22.0'; } From 40bf3d99a8b03afec001d26ace894c71d07fd1ac Mon Sep 17 00:00:00 2001 From: Ren Xie Liu Date: Tue, 14 Mar 2023 13:56:04 +0800 Subject: [PATCH 77/92] Redirect Europe/Kyiv timezone --- lib/TimezoneGuesser/FindFromTimezoneIdentifier.php | 8 ++++++++ tests/VObject/TimeZoneUtilTest.php | 11 ++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/TimezoneGuesser/FindFromTimezoneIdentifier.php b/lib/TimezoneGuesser/FindFromTimezoneIdentifier.php index b5c58f715..a2578bc2d 100644 --- a/lib/TimezoneGuesser/FindFromTimezoneIdentifier.php +++ b/lib/TimezoneGuesser/FindFromTimezoneIdentifier.php @@ -12,6 +12,10 @@ */ class FindFromTimezoneIdentifier implements TimezoneFinder { + public const MIGRATION_TIMEZONES = [ + 'Europe/Kyiv' => 'Europe/Kiev', + ]; + public function find(string $tzid, bool $failIfUncertain = false): ?DateTimeZone { // First we will just see if the tzid is a support timezone identifier. @@ -37,6 +41,10 @@ public function find(string $tzid, bool $failIfUncertain = false): ?DateTimeZone $tzid = substr($tzid, 1); } + if (isset(self::MIGRATION_TIMEZONES[$tzid])) { + $tzid = self::MIGRATION_TIMEZONES[$tzid]; + } + // PHP has a bug that logs PHP warnings even it shouldn't: // https://bugs.php.net/bug.php?id=67881 // diff --git a/tests/VObject/TimeZoneUtilTest.php b/tests/VObject/TimeZoneUtilTest.php index 069205883..fb060f6f8 100644 --- a/tests/VObject/TimeZoneUtilTest.php +++ b/tests/VObject/TimeZoneUtilTest.php @@ -229,7 +229,10 @@ public function getPHPTimeZoneIdentifiers() function ($value) { return [$value]; }, - \DateTimeZone::listIdentifiers() + // FIXME remove the filter after finishing timezone migration + array_filter(\DateTimeZone::listIdentifiers(), static function (string $timezone) { + return $timezone !== 'Europe/Kyiv'; + }) ); } @@ -244,6 +247,12 @@ function ($value) { ); } + public function testKyivTimezone() + { + + $this->assertSame('Europe/Kiev', TimeZoneUtil::getTimeZone('Europe/Kyiv')->getName()); + } + public function testTimezoneOffset() { $tz = TimeZoneUtil::getTimeZone('GMT-0400', null, true); From f894aab372f695456318fdca051dce044eb0c646 Mon Sep 17 00:00:00 2001 From: Ren Xie Liu Date: Thu, 13 Apr 2023 10:30:59 +0800 Subject: [PATCH 78/92] Release 4.23.0 --- CHANGELOG.md | 4 ++++ lib/Version.php | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index edf59eb78..0b71ad960 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ ChangeLog ========= +4.23.0 (2023-04-13) +------------------- +* #82 Modify TZID value for migration timezones + 4.22.0 (2023-03-01) ------------------- * #80 Allow newly deprecated timezones diff --git a/lib/Version.php b/lib/Version.php index 858a3e95f..5899f0914 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -14,5 +14,5 @@ class Version /** * Full version number. */ - const VERSION = '4.22.0'; + const VERSION = '4.23.0'; } From 2371b9d14199f2c228a8f4a56d0af40a289b3bf3 Mon Sep 17 00:00:00 2001 From: Christopher Szu Date: Fri, 5 May 2023 11:01:58 +0800 Subject: [PATCH 79/92] [Calendar][CALBE-2290][Feat] added whitelist for illegal values in parameter --- lib/Document.php | 17 +++++++++++++- tests/VObject/InvalidValueParamTest.php | 31 +++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 tests/VObject/InvalidValueParamTest.php diff --git a/lib/Document.php b/lib/Document.php index 14a77c911..7fb04c4a5 100644 --- a/lib/Document.php +++ b/lib/Document.php @@ -78,6 +78,16 @@ abstract class Document extends Component */ public static $valueMap = []; + /** + * List of RFC violating values that we allow in parameter value. + * e.g. LOCATION;VALUE=ERROR:some location + * + * @var string[] + */ + private array $allowedIllegalValues = [ + 'ERROR', + ]; + /** * Creates a new document. * @@ -213,7 +223,12 @@ public function createProperty($name, $value = null, array $parameters = null, $ if (isset($parameters['VALUE'])) { $class = $this->getClassNameForPropertyValue($parameters['VALUE']); if (is_null($class)) { - throw new InvalidDataException('Unsupported VALUE parameter for '.$name.' property. You supplied "'.$parameters['VALUE'].'"'); + if (in_array(strtoupper($parameters['VALUE']), $this->allowedIllegalValues, true)) { + unset($parameters['VALUE']); + $class = $this->getClassNameForPropertyName($name); + } else { + throw new InvalidDataException('Unsupported VALUE parameter for '.$name.' property. You supplied "'.$parameters['VALUE'].'"'); + } } } else { $class = $this->getClassNameForPropertyName($name); diff --git a/tests/VObject/InvalidValueParamTest.php b/tests/VObject/InvalidValueParamTest.php new file mode 100644 index 000000000..52ca7a95f --- /dev/null +++ b/tests/VObject/InvalidValueParamTest.php @@ -0,0 +1,31 @@ +assertEquals("LOCATION:ERROR\r\n", $doc->VEVENT->LOCATION->serialize()); + } +} From aba1a70060ea6606f9ccfba987640542bfd8e301 Mon Sep 17 00:00:00 2001 From: Christopher Szu Date: Fri, 16 Jun 2023 00:31:08 -0700 Subject: [PATCH 80/92] Update tests/VObject/InvalidValueParamTest.php better naming in test case Co-authored-by: giuseppe-arcuti <93533561+giuseppe-arcuti@users.noreply.github.com> --- tests/VObject/InvalidValueParamTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/VObject/InvalidValueParamTest.php b/tests/VObject/InvalidValueParamTest.php index 52ca7a95f..cd069960c 100644 --- a/tests/VObject/InvalidValueParamTest.php +++ b/tests/VObject/InvalidValueParamTest.php @@ -16,7 +16,7 @@ public function testWorkaround() DTSTAMP:20230317T130521Z DTSTART;TZID=Europe/Paris:20170530T200000 LAST-MODIFIED:20230316T155811Z - LOCATION;VALUE=ERROR:ERROR + LOCATION;VALUE=ERROR:EXAMPLE SEQUENCE:0 STATUS:CONFIRMED SUMMARY:AG MP3 @@ -26,6 +26,6 @@ public function testWorkaround() ICS; $doc = Reader::read($event); - $this->assertEquals("LOCATION:ERROR\r\n", $doc->VEVENT->LOCATION->serialize()); + $this->assertEquals("LOCATION:EXAMPLE\r\n", $doc->VEVENT->LOCATION->serialize()); } } From 3a8c783e2029bbfcd99d6b6b1afdab0952ca46a5 Mon Sep 17 00:00:00 2001 From: Christopher Szu Date: Mon, 19 Jun 2023 12:57:17 +0800 Subject: [PATCH 81/92] Release 4.24.0 --- CHANGELOG.md | 4 ++++ lib/Version.php | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b71ad960..a7c24c4c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ ChangeLog ========= +4.24.0 (2023-06-19) +------------------- +* #87 Added whitelist for illegal values in parameter + 4.23.0 (2023-04-13) ------------------- * #82 Modify TZID value for migration timezones diff --git a/lib/Version.php b/lib/Version.php index 5899f0914..b8752bd2f 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -14,5 +14,5 @@ class Version /** * Full version number. */ - const VERSION = '4.23.0'; + const VERSION = '4.24.0'; } From af9a85f30d92e43610d76884d898e5dfd22c0096 Mon Sep 17 00:00:00 2001 From: giuseppe-arcuti <93533561+giuseppe-arcuti@users.noreply.github.com> Date: Mon, 26 Jun 2023 11:19:30 +0200 Subject: [PATCH 82/92] Move America/Yellowknife to deprecated (#89) --- lib/timezonedata/php-bc.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/timezonedata/php-bc.php b/lib/timezonedata/php-bc.php index d9f49008a..bd60f26dc 100644 --- a/lib/timezonedata/php-bc.php +++ b/lib/timezonedata/php-bc.php @@ -29,6 +29,8 @@ 'America/Rainy_River', // Moved to backward in 2022g 'America/Pangnirtung', + // Moved to backward in 2023a + 'America/Yellowknife', // Original list 'Africa/Asmera', From 72c81a9a60b433c4b512712ff9e077ddf2dc708a Mon Sep 17 00:00:00 2001 From: giuseppe-arcuti <93533561+giuseppe-arcuti@users.noreply.github.com> Date: Mon, 26 Jun 2023 11:21:14 +0200 Subject: [PATCH 83/92] Release 4.25 (#90) --- CHANGELOG.md | 4 ++++ lib/Version.php | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7c24c4c6..d3748770d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ ChangeLog ========= +4.25.0 (2023-06-26) +------------------- +* #89 Move America/Yellowknife to deprecated + 4.24.0 (2023-06-19) ------------------- * #87 Added whitelist for illegal values in parameter diff --git a/lib/Version.php b/lib/Version.php index b8752bd2f..92fc61d6d 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -14,5 +14,5 @@ class Version /** * Full version number. */ - const VERSION = '4.24.0'; + const VERSION = '4.25.0'; } From 7391033fe9dcd4f7708d5e13dbb5cf40049c5dda Mon Sep 17 00:00:00 2001 From: Christopher Szu Date: Tue, 7 Nov 2023 15:25:53 -0800 Subject: [PATCH 84/92] [INBE-269][Fix] drop malformed or illegal VALUE parameter --- lib/Document.php | 24 ++++++------------------ tests/VObject/InvalidValueParamTest.php | 23 +++++++++++++++++++++++ 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/lib/Document.php b/lib/Document.php index 7fb04c4a5..6265f34b5 100644 --- a/lib/Document.php +++ b/lib/Document.php @@ -78,16 +78,6 @@ abstract class Document extends Component */ public static $valueMap = []; - /** - * List of RFC violating values that we allow in parameter value. - * e.g. LOCATION;VALUE=ERROR:some location - * - * @var string[] - */ - private array $allowedIllegalValues = [ - 'ERROR', - ]; - /** * Creates a new document. * @@ -221,14 +211,12 @@ public function createProperty($name, $value = null, array $parameters = null, $ if (is_null($class)) { // If a VALUE parameter is supplied, we should use that. if (isset($parameters['VALUE'])) { - $class = $this->getClassNameForPropertyValue($parameters['VALUE']); - if (is_null($class)) { - if (in_array(strtoupper($parameters['VALUE']), $this->allowedIllegalValues, true)) { - unset($parameters['VALUE']); - $class = $this->getClassNameForPropertyName($name); - } else { - throw new InvalidDataException('Unsupported VALUE parameter for '.$name.' property. You supplied "'.$parameters['VALUE'].'"'); - } + if (is_string($parameters['VALUE'])) { + $class = $this->getClassNameForPropertyValue($parameters['VALUE']); + } + if (is_null($class)) { // VALUE is malformed or illegal, drop it + unset($parameters['VALUE']); + $class = $this->getClassNameForPropertyName($name); } } else { $class = $this->getClassNameForPropertyName($name); diff --git a/tests/VObject/InvalidValueParamTest.php b/tests/VObject/InvalidValueParamTest.php index cd069960c..744fb193c 100644 --- a/tests/VObject/InvalidValueParamTest.php +++ b/tests/VObject/InvalidValueParamTest.php @@ -28,4 +28,27 @@ public function testWorkaround() $doc = Reader::read($event); $this->assertEquals("LOCATION:EXAMPLE\r\n", $doc->VEVENT->LOCATION->serialize()); } + + public function testInvalidValue() + { + $event = <<assertEquals("LOCATION:consectetur adipiscing elit\,sed do eiusmod tempor\r\n", $doc->VEVENT->LOCATION->serialize()); + } } From 5ad0f8a308139788758fca1a8a0507036b5af31a Mon Sep 17 00:00:00 2001 From: Christopher Szu Date: Thu, 30 Nov 2023 13:11:09 -0800 Subject: [PATCH 85/92] Release 4.26.0 --- CHANGELOG.md | 4 ++++ lib/Version.php | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3748770d..60b1b86db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ ChangeLog ========= +4.26.0 (2023-11-30) +------------------- +* #92 Drop malformed or illegal VALUE parameter + 4.25.0 (2023-06-26) ------------------- * #89 Move America/Yellowknife to deprecated diff --git a/lib/Version.php b/lib/Version.php index 92fc61d6d..6b0b4a5ce 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -14,5 +14,5 @@ class Version /** * Full version number. */ - const VERSION = '4.25.0'; + const VERSION = '4.26.0'; } From 0d82bc5222e1d8dcc7662c22c0ab2893c537e467 Mon Sep 17 00:00:00 2001 From: Christopher Szu Date: Mon, 18 Dec 2023 10:55:13 +0800 Subject: [PATCH 86/92] Add support for gulf standard time --- lib/timezonedata/extrazones.php | 1 + tests/VObject/TimeZoneUtilTest.php | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/lib/timezonedata/extrazones.php b/lib/timezonedata/extrazones.php index 58bdabb28..6377ac2ae 100644 --- a/lib/timezonedata/extrazones.php +++ b/lib/timezonedata/extrazones.php @@ -204,4 +204,5 @@ 'America/Godthab' => 'Atlantic/Stanley', 'CDT' => 'America/Chicago', 'PST' => 'America/Los_Angeles', + 'Gulf Standard Time' => 'Asia/Muscat', ]; diff --git a/tests/VObject/TimeZoneUtilTest.php b/tests/VObject/TimeZoneUtilTest.php index fb060f6f8..1a81bd600 100644 --- a/tests/VObject/TimeZoneUtilTest.php +++ b/tests/VObject/TimeZoneUtilTest.php @@ -469,6 +469,11 @@ public function unSupportTimezoneProvider(): iterable 'expected' => 'America/Los_Angeles', ]; + yield 'Gulf Standard Time' => [ + 'origin' => 'Gulf Standard Time', + 'expected' => 'Asia/Muscat', + ]; + if (($handle = fopen(__DIR__ . "/microsoft-timezones-confluence.csv", "r")) !== FALSE) { $data = fgetcsv($handle); while (($data = fgetcsv($handle)) !== FALSE) { From cd8741f7c5a40835ed0caf3bbc8cb5751eb09e42 Mon Sep 17 00:00:00 2001 From: Christopher Szu Date: Mon, 18 Dec 2023 13:03:04 +0800 Subject: [PATCH 87/92] Release 4.27.0 --- CHANGELOG.md | 4 ++++ lib/Version.php | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60b1b86db..e657e2fa7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ ChangeLog ========= +4.27.0 (2023-12-18) +------------------- +* #94 Add support for gulf standard time + 4.26.0 (2023-11-30) ------------------- * #92 Drop malformed or illegal VALUE parameter diff --git a/lib/Version.php b/lib/Version.php index 6b0b4a5ce..de8fab5cc 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -14,5 +14,5 @@ class Version /** * Full version number. */ - const VERSION = '4.26.0'; + const VERSION = '4.27.0'; } From 56c6efdcc7cfd5fd918ef78643670a2d1eba0cab Mon Sep 17 00:00:00 2001 From: Christopher Szu Date: Tue, 19 Dec 2023 16:51:30 +0800 Subject: [PATCH 88/92] use dubai instead of muscat --- lib/timezonedata/extrazones.php | 2 +- tests/VObject/TimeZoneUtilTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/timezonedata/extrazones.php b/lib/timezonedata/extrazones.php index 6377ac2ae..711833520 100644 --- a/lib/timezonedata/extrazones.php +++ b/lib/timezonedata/extrazones.php @@ -204,5 +204,5 @@ 'America/Godthab' => 'Atlantic/Stanley', 'CDT' => 'America/Chicago', 'PST' => 'America/Los_Angeles', - 'Gulf Standard Time' => 'Asia/Muscat', + 'Gulf Standard Time' => 'Asia/Dubai', ]; diff --git a/tests/VObject/TimeZoneUtilTest.php b/tests/VObject/TimeZoneUtilTest.php index 1a81bd600..ff409a1ff 100644 --- a/tests/VObject/TimeZoneUtilTest.php +++ b/tests/VObject/TimeZoneUtilTest.php @@ -471,7 +471,7 @@ public function unSupportTimezoneProvider(): iterable yield 'Gulf Standard Time' => [ 'origin' => 'Gulf Standard Time', - 'expected' => 'Asia/Muscat', + 'expected' => 'Asia/Dubai', ]; if (($handle = fopen(__DIR__ . "/microsoft-timezones-confluence.csv", "r")) !== FALSE) { From 883dff59b80fd1fce0b3fd625ab9b2c1931c5978 Mon Sep 17 00:00:00 2001 From: Christopher Szu Date: Tue, 19 Dec 2023 16:59:39 +0800 Subject: [PATCH 89/92] Release 4.28.0 --- CHANGELOG.md | 4 ++++ lib/Version.php | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e657e2fa7..a14c99ab5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ ChangeLog ========= +4.28.0 (2023-12-19) +------------------- +* #96 use dubai instead of muscat + 4.27.0 (2023-12-18) ------------------- * #94 Add support for gulf standard time diff --git a/lib/Version.php b/lib/Version.php index de8fab5cc..6b7729caa 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -14,5 +14,5 @@ class Version /** * Full version number. */ - const VERSION = '4.27.0'; + const VERSION = '4.28.0'; } From 1a908528910556e44b15afa68d8d55a122a56688 Mon Sep 17 00:00:00 2001 From: Christopher Szu Date: Fri, 29 Dec 2023 11:07:30 +0800 Subject: [PATCH 90/92] make test pass from both sides --- .github/workflows/actions.yml | 59 -------------------- lib/Recur/RRuleIterator.php | 13 ++++- tests/VObject/Recur/FastForwardToEndTest.php | 3 + tests/VObject/Recur/RRuleIteratorTest.php | 14 ++++- 4 files changed, 26 insertions(+), 63 deletions(-) delete mode 100644 .github/workflows/actions.yml diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml deleted file mode 100644 index d1c7c97b2..000000000 --- a/.github/workflows/actions.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: Tests - -on: [push] - -jobs: - phpunit: - strategy: - matrix: - operating-system: [ubuntu-latest] - php-versions: ['7.4', '8.0', '8.1'] - - runs-on: ${{ matrix.operating-system }} - - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Setup PHP, with composer and extensions - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-versions }} - extensions: mbstring - coverage: pcov - - - name: Get composer cache directory - id: composer-cache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" - - - name: Cache composer dependencies - uses: actions/cache@v2 - with: - path: ${{ steps.composer-cache.outputs.dir }} - # Use composer.json for key, if composer.lock is not committed. - # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer- - - - name: Install composer dependencies - run: composer install --no-progress --prefer-dist --optimize-autoloader - - - name: Code Analysis (PHP CS-Fixer) - if: matrix.code-analysis == 'yes' - run: php vendor/bin/php-cs-fixer fix --dry-run --diff - - - name: Code Analysis (PHPStan) - if: matrix.code-analysis == 'yes' - run: composer phpstan - - - name: Test with phpunit - run: vendor/bin/phpunit --configuration ./tests/phpunit.xml --coverage-text --coverage-clover=coverage.xml - - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v2 - with: - token: ${{ secrets.CODECOV_TOKEN }} - file: ./coverage.xml - flags: tests - name: codecov-umbrella - fail_ci_if_error: true diff --git a/lib/Recur/RRuleIterator.php b/lib/Recur/RRuleIterator.php index 0b3140bb0..ecf1affeb 100644 --- a/lib/Recur/RRuleIterator.php +++ b/lib/Recur/RRuleIterator.php @@ -28,6 +28,8 @@ class RRuleIterator implements \Iterator */ public const dateUpperLimit = 253402300799; + private bool $yearlySkipUpperLimit; + /** * Creates the Iterator. * @@ -35,11 +37,12 @@ class RRuleIterator implements \Iterator * * @throws InvalidDataException */ - public function __construct($rrule, \DateTimeInterface $start) + public function __construct($rrule, \DateTimeInterface $start, bool $yearlySkipUpperLimit = true) { $this->startDate = $start; $this->parseRRule($rrule); $this->currentDate = clone $this->startDate; + $this->yearlySkipUpperLimit = $yearlySkipUpperLimit; } /* Implementation of the Iterator interface {{{ */ @@ -823,6 +826,14 @@ protected function nextYearly($amount = 1): void (int) $currentMonth, (int) $currentDayOfMonth ); + + // To prevent running this forever (better: until we hit the max date of DateTimeImmutable) we simply + // stop at 9999-12-31. Looks like the year 10000 problem is not solved in php .... + if (!$this->yearlySkipUpperLimit && ($this->currentDate->getTimestamp() > self::dateUpperLimit)) { + $this->currentDate = null; + + return; + } } // If we made it here, it means we got a valid occurrence diff --git a/tests/VObject/Recur/FastForwardToEndTest.php b/tests/VObject/Recur/FastForwardToEndTest.php index 1073a95df..0b94f3945 100644 --- a/tests/VObject/Recur/FastForwardToEndTest.php +++ b/tests/VObject/Recur/FastForwardToEndTest.php @@ -229,6 +229,9 @@ public function testFastForwardToEndUntilMonthly31thDay() $this->assertEquals($expected, $rrule->current()->getTimestamp()); } + /** + * @medium + */ public function testFastForwardToEndCountMonthlyAdvanced() { $startDate = new \DateTime('1970-01-31 00:00:00', new \DateTimeZone('America/New_York')); diff --git a/tests/VObject/Recur/RRuleIteratorTest.php b/tests/VObject/Recur/RRuleIteratorTest.php index 3384506f5..75222f217 100644 --- a/tests/VObject/Recur/RRuleIteratorTest.php +++ b/tests/VObject/Recur/RRuleIteratorTest.php @@ -1052,7 +1052,14 @@ public function testYearlyBySetPosLoop(): void 'FREQ=YEARLY;BYMONTH=5;BYSETPOS=3;BYMONTHDAY=3', '2022-03-03 15:45:00', [], - 'yearly', null, 1, null, '2022-05-01' + 'yearly', + null, + 1, + null, + '2022-05-01', + 'UTC', + false, + false, ); } @@ -1245,10 +1252,11 @@ public function parse( $expectedUntil = null, string $fastForward = null, string $tz = 'UTC', - bool $runTillTheEnd = false + bool $runTillTheEnd = false, + bool $yearlySkipUpperLimit = true ): void { $dt = new \DateTime($start, new \DateTimeZone($tz)); - $parser = new RRuleIterator($rule, $dt); + $parser = new RRuleIterator($rule, $dt, $yearlySkipUpperLimit); $this->assertEquals($expectedFreq, $parser->getFrequency()); $this->assertEquals($expectedCount, $parser->getCount()); From 5c0f7010bb9180979df5df025acce12e862c78cd Mon Sep 17 00:00:00 2001 From: Christopher Szu Date: Fri, 29 Dec 2023 15:40:14 +0800 Subject: [PATCH 91/92] Release 4.29.0 --- CHANGELOG.md | 4 ++++ lib/Version.php | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22ca89327..1478325d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ ChangeLog ========= +4.29.0 (2023-12-29) +------------------- +* #98 Chore/merge upstream master 20231228 + 4.28.0 (2023-12-19) ------------------- * #96 use dubai instead of muscat diff --git a/lib/Version.php b/lib/Version.php index fd597c050..337dacad6 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -14,5 +14,5 @@ class Version /** * Full version number. */ - public const VERSION = '4.28.0'; + public const VERSION = '4.29.0'; } From f5ef6df8c40e96b55688565deac957a7251d6e36 Mon Sep 17 00:00:00 2001 From: Valentin Bonneaud Date: Wed, 10 Apr 2024 13:22:58 +0200 Subject: [PATCH 92/92] Add dependancies audit --- .github/workflows/ci.yml | 43 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3d665e841..93e22978a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,3 +61,46 @@ jobs: - name: Code Coverage uses: codecov/codecov-action@v3 if: matrix.coverage != 'none' + dependencies-audit: + name: Dependencies audit (PHP ${{ matrix.php-versions }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php-versions: ['7.4', '8.0', '8.1', '8.2', '8.3'] + coverage: ['pcov'] + code-analysis: ['no'] + include: + - php-versions: '7.4' + coverage: 'none' + code-analysis: 'yes' + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 #https://github.com/shivammathur/setup-php + with: + php-version: ${{ matrix.php-versions }} + extensions: mbstring, dom, fileinfo, mysql, redis, opcache + coverage: ${{ matrix.coverage }} + tools: composer + + - name: Get composer cache directory + id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - name: Cache composer dependencies + uses: actions/cache@v3 + with: + path: ${{ steps.composer-cache.outputs.dir }} + # Use composer.json for key, if composer.lock is not committed. + # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install composer dependencies + run: composer install --no-progress --prefer-dist --optimize-autoloader + + - name: Composer audit + run: composer audit