Commit | Line | Data |
---|---|---|
8ddc6567 JP |
1 | <?php |
2 | // This file is part of Moodle - http://moodle.org/ | |
3 | // | |
4 | // Moodle is free software: you can redistribute it and/or modify | |
5 | // it under the terms of the GNU General Public License as published by | |
6 | // the Free Software Foundation, either version 3 of the License, or | |
7 | // (at your option) any later version. | |
8 | // | |
9 | // Moodle is distributed in the hope that it will be useful, | |
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | // GNU General Public License for more details. | |
13 | // | |
14 | // You should have received a copy of the GNU General Public License | |
15 | // along with Moodle. If not, see <http://www.gnu.org/licenses/>. | |
16 | ||
17 | /** | |
18 | * Defines test class to test manage rrule during ical imports. | |
19 | * | |
20 | * @package core_calendar | |
21 | * @category test | |
22 | * @copyright 2014 onwards Ankit Agarwal <ankit.agrr@gmail.com> | |
23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
24 | */ | |
25 | ||
26 | defined('MOODLE_INTERNAL') || die(); | |
27 | ||
28 | global $CFG; | |
29 | require_once($CFG->dirroot . '/calendar/lib.php'); | |
30 | ||
31 | use core_calendar\rrule_manager; | |
32 | ||
33 | /** | |
34 | * Defines test class to test manage rrule during ical imports. | |
35 | * | |
36 | * @package core_calendar | |
37 | * @category test | |
38 | * @copyright 2014 onwards Ankit Agarwal <ankit.agrr@gmail.com> | |
39 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
40 | */ | |
41 | class core_calendar_rrule_manager_testcase extends advanced_testcase { | |
42 | ||
43 | /** @var calendar_event a dummy event */ | |
44 | protected $event; | |
45 | ||
46 | /** | |
47 | * Set up method. | |
48 | */ | |
49 | protected function setUp() { | |
50 | global $DB; | |
51 | $this->resetAfterTest(); | |
52 | ||
53 | // Set our timezone based on the timezone in the RFC's samples (US/Eastern). | |
54 | $tz = 'US/Eastern'; | |
55 | $this->setTimezone($tz); | |
56 | $timezone = new DateTimeZone($tz); | |
57 | // Create our event's DTSTART date based on RFC's samples (most commonly used in RFC is 1997-09-02 09:00:00 EDT). | |
58 | $time = DateTime::createFromFormat('Ymd\THis', '19970902T090000', $timezone); | |
59 | $timestart = $time->getTimestamp(); | |
60 | ||
61 | $user = $this->getDataGenerator()->create_user(); | |
62 | $sub = new stdClass(); | |
63 | $sub->url = ''; | |
64 | $sub->courseid = 0; | |
65 | $sub->groupid = 0; | |
66 | $sub->userid = $user->id; | |
67 | $sub->pollinterval = 0; | |
68 | $subid = $DB->insert_record('event_subscriptions', $sub, true); | |
69 | ||
70 | $event = new stdClass(); | |
71 | $event->name = 'Event name'; | |
72 | $event->description = ''; | |
73 | $event->timestart = $timestart; | |
74 | $event->timeduration = 3600; | |
75 | $event->uuid = 'uuid'; | |
76 | $event->subscriptionid = $subid; | |
77 | $event->userid = $user->id; | |
78 | $event->groupid = 0; | |
79 | $event->courseid = 0; | |
80 | $event->eventtype = 'user'; | |
81 | $eventobj = calendar_event::create($event, false); | |
82 | $DB->set_field('event', 'repeatid', $eventobj->id, array('id' => $eventobj->id)); | |
83 | $eventobj->repeatid = $eventobj->id; | |
84 | $this->event = $eventobj; | |
85 | } | |
86 | ||
87 | /** | |
88 | * Test parse_rrule() method. | |
89 | */ | |
90 | public function test_parse_rrule() { | |
91 | $rules = [ | |
92 | 'FREQ=YEARLY', | |
93 | 'COUNT=3', | |
94 | 'INTERVAL=4', | |
95 | 'BYSECOND=20,40', | |
96 | 'BYMINUTE=2,30', | |
97 | 'BYHOUR=3,4', | |
98 | 'BYDAY=MO,TH', | |
99 | 'BYMONTHDAY=20,30', | |
100 | 'BYYEARDAY=300,-20', | |
101 | 'BYWEEKNO=22,33', | |
102 | 'BYMONTH=3,4' | |
103 | ]; | |
104 | $rrule = implode(';', $rules); | |
105 | $mang = new rrule_manager($rrule); | |
106 | $mang->parse_rrule(); | |
107 | ||
108 | $bydayrules = [ | |
109 | (object)[ | |
110 | 'day' => 'MO', | |
111 | 'value' => 0 | |
112 | ], | |
113 | (object)[ | |
114 | 'day' => 'TH', | |
115 | 'value' => 0 | |
116 | ], | |
117 | ]; | |
118 | ||
119 | $props = [ | |
120 | 'freq' => rrule_manager::FREQ_YEARLY, | |
121 | 'count' => 3, | |
122 | 'interval' => 4, | |
123 | 'bysecond' => [20, 40], | |
124 | 'byminute' => [2, 30], | |
125 | 'byhour' => [3, 4], | |
126 | 'byday' => $bydayrules, | |
127 | 'bymonthday' => [20, 30], | |
128 | 'byyearday' => [300, -20], | |
129 | 'byweekno' => [22, 33], | |
130 | 'bymonth' => [3, 4], | |
131 | ]; | |
132 | ||
133 | $reflectionclass = new ReflectionClass($mang); | |
134 | foreach ($props as $prop => $expectedval) { | |
135 | $rcprop = $reflectionclass->getProperty($prop); | |
136 | $rcprop->setAccessible(true); | |
137 | $this->assertEquals($expectedval, $rcprop->getValue($mang)); | |
138 | } | |
139 | } | |
140 | ||
141 | /** | |
142 | * Test exception is thrown for invalid property. | |
143 | */ | |
144 | public function test_parse_rrule_validation() { | |
145 | $rrule = "RANDOM=PROPERTY;"; | |
146 | $mang = new rrule_manager($rrule); | |
147 | $this->expectException('moodle_exception'); | |
148 | $mang->parse_rrule(); | |
149 | } | |
150 | ||
151 | /** | |
152 | * Test exception is thrown for invalid frequency. | |
153 | */ | |
154 | public function test_freq_validation() { | |
155 | $rrule = "FREQ=RANDOMLY;"; | |
156 | $mang = new rrule_manager($rrule); | |
157 | $this->expectException('moodle_exception'); | |
158 | $mang->parse_rrule(); | |
159 | } | |
160 | ||
161 | /** | |
162 | * Test parsing of rules with both COUNT and UNTIL parameters. | |
163 | */ | |
164 | public function test_until_count_validation() { | |
165 | $until = $this->event->timestart + DAYSECS * 4; | |
166 | $until = date('Y-m-d', $until); | |
167 | $rrule = "FREQ=DAILY;COUNT=2;UNTIL=$until"; | |
168 | $mang = new rrule_manager($rrule); | |
169 | $this->expectException('moodle_exception'); | |
170 | $mang->parse_rrule(); | |
171 | } | |
172 | ||
173 | /** | |
174 | * Test parsing of INTERVAL rule. | |
175 | */ | |
176 | public function test_interval_validation() { | |
177 | $rrule = "INTERVAL=0"; | |
178 | $mang = new rrule_manager($rrule); | |
179 | $this->expectException('moodle_exception'); | |
180 | $mang->parse_rrule(); | |
181 | } | |
182 | ||
183 | /** | |
184 | * Test parsing of BYSECOND rule. | |
185 | */ | |
186 | public function test_bysecond_validation() { | |
187 | $rrule = "BYSECOND=30,45,60"; | |
188 | $mang = new rrule_manager($rrule); | |
189 | $this->expectException('moodle_exception'); | |
190 | $mang->parse_rrule(); | |
191 | } | |
192 | ||
193 | /** | |
194 | * Test parsing of BYMINUTE rule. | |
195 | */ | |
196 | public function test_byminute_validation() { | |
197 | $rrule = "BYMINUTE=30,45,60"; | |
198 | $mang = new rrule_manager($rrule); | |
199 | $this->expectException('moodle_exception'); | |
200 | $mang->parse_rrule(); | |
201 | } | |
202 | ||
203 | /** | |
204 | * Test parsing of BYMINUTE rule. | |
205 | */ | |
206 | public function test_byhour_validation() { | |
207 | $rrule = "BYHOUR=23,45"; | |
208 | $mang = new rrule_manager($rrule); | |
209 | $this->expectException('moodle_exception'); | |
210 | $mang->parse_rrule(); | |
211 | } | |
212 | ||
213 | /** | |
214 | * Test parsing of BYDAY rule. | |
215 | */ | |
216 | public function test_byday_validation() { | |
217 | $rrule = "BYDAY=MO,2SE"; | |
218 | $mang = new rrule_manager($rrule); | |
219 | $this->expectException('moodle_exception'); | |
220 | $mang->parse_rrule(); | |
221 | } | |
222 | ||
223 | /** | |
224 | * Test parsing of BYDAY rule with prefixes. | |
225 | */ | |
226 | public function test_byday_with_prefix_validation() { | |
227 | // This is acceptable. | |
228 | $rrule = "FREQ=MONTHLY;BYDAY=-1MO,2SA"; | |
229 | $mang = new rrule_manager($rrule); | |
230 | $mang->parse_rrule(); | |
231 | ||
232 | // This is also acceptable. | |
233 | $rrule = "FREQ=YEARLY;BYDAY=MO,2SA"; | |
234 | $mang = new rrule_manager($rrule); | |
235 | $mang->parse_rrule(); | |
236 | ||
237 | // This is invalid. | |
238 | $rrule = "FREQ=WEEKLY;BYDAY=MO,2SA"; | |
239 | $mang = new rrule_manager($rrule); | |
240 | $this->expectException('moodle_exception'); | |
241 | $mang->parse_rrule(); | |
242 | } | |
243 | ||
244 | /** | |
245 | * Test parsing of BYMONTHDAY rule. | |
246 | */ | |
247 | public function test_bymonthday_upper_bound_validation() { | |
248 | $rrule = "BYMONTHDAY=1,32"; | |
249 | $mang = new rrule_manager($rrule); | |
250 | $this->expectException('moodle_exception'); | |
251 | $mang->parse_rrule(); | |
252 | } | |
253 | ||
254 | /** | |
255 | * Test parsing of BYMONTHDAY rule. | |
256 | */ | |
257 | public function test_bymonthday_0_validation() { | |
258 | $rrule = "BYMONTHDAY=1,0"; | |
259 | $mang = new rrule_manager($rrule); | |
260 | $this->expectException('moodle_exception'); | |
261 | $mang->parse_rrule(); | |
262 | } | |
263 | ||
264 | /** | |
265 | * Test parsing of BYMONTHDAY rule. | |
266 | */ | |
267 | public function test_bymonthday_lower_bound_validation() { | |
268 | $rrule = "BYMONTHDAY=1,-31,-32"; | |
269 | $mang = new rrule_manager($rrule); | |
270 | $this->expectException('moodle_exception'); | |
271 | $mang->parse_rrule(); | |
272 | } | |
273 | ||
274 | /** | |
275 | * Test parsing of BYYEARDAY rule. | |
276 | */ | |
277 | public function test_byyearday_upper_bound_validation() { | |
278 | $rrule = "BYYEARDAY=1,366,367"; | |
279 | $mang = new rrule_manager($rrule); | |
280 | $this->expectException('moodle_exception'); | |
281 | $mang->parse_rrule(); | |
282 | } | |
283 | ||
284 | /** | |
285 | * Test parsing of BYYEARDAY rule. | |
286 | */ | |
287 | public function test_byyearday_0_validation() { | |
288 | $rrule = "BYYEARDAY=0"; | |
289 | $mang = new rrule_manager($rrule); | |
290 | $this->expectException('moodle_exception'); | |
291 | $mang->parse_rrule(); | |
292 | } | |
293 | ||
294 | /** | |
295 | * Test parsing of BYYEARDAY rule. | |
296 | */ | |
297 | public function test_byyearday_lower_bound_validation() { | |
298 | $rrule = "BYYEARDAY=-1,-366,-367"; | |
299 | $mang = new rrule_manager($rrule); | |
300 | $this->expectException('moodle_exception'); | |
301 | $mang->parse_rrule(); | |
302 | } | |
303 | ||
304 | /** | |
305 | * Test parsing of BYWEEKNO rule. | |
306 | */ | |
307 | public function test_non_yearly_freq_with_byweekno() { | |
308 | $rrule = "BYWEEKNO=1,53"; | |
309 | $mang = new rrule_manager($rrule); | |
310 | $this->expectException('moodle_exception'); | |
311 | $mang->parse_rrule(); | |
312 | } | |
313 | ||
314 | /** | |
315 | * Test parsing of BYWEEKNO rule. | |
316 | */ | |
317 | public function test_byweekno_upper_bound_validation() { | |
318 | $rrule = "FREQ=YEARLY;BYWEEKNO=1,53,54"; | |
319 | $mang = new rrule_manager($rrule); | |
320 | $this->expectException('moodle_exception'); | |
321 | $mang->parse_rrule(); | |
322 | } | |
323 | ||
324 | /** | |
325 | * Test parsing of BYWEEKNO rule. | |
326 | */ | |
327 | public function test_byweekno_0_validation() { | |
328 | $rrule = "FREQ=YEARLY;BYWEEKNO=0"; | |
329 | $mang = new rrule_manager($rrule); | |
330 | $this->expectException('moodle_exception'); | |
331 | $mang->parse_rrule(); | |
332 | } | |
333 | ||
334 | /** | |
335 | * Test parsing of BYWEEKNO rule. | |
336 | */ | |
337 | public function test_byweekno_lower_bound_validation() { | |
338 | $rrule = "FREQ=YEARLY;BYWEEKNO=-1,-53,-54"; | |
339 | $mang = new rrule_manager($rrule); | |
340 | $this->expectException('moodle_exception'); | |
341 | $mang->parse_rrule(); | |
342 | } | |
343 | ||
344 | /** | |
345 | * Test parsing of BYMONTH rule. | |
346 | */ | |
347 | public function test_bymonth_upper_bound_validation() { | |
348 | $rrule = "BYMONTH=1,12,13"; | |
349 | $mang = new rrule_manager($rrule); | |
350 | $this->expectException('moodle_exception'); | |
351 | $mang->parse_rrule(); | |
352 | } | |
353 | ||
354 | /** | |
355 | * Test parsing of BYMONTH rule. | |
356 | */ | |
357 | public function test_bymonth_lower_bound_validation() { | |
358 | $rrule = "BYMONTH=0"; | |
359 | $mang = new rrule_manager($rrule); | |
360 | $this->expectException('moodle_exception'); | |
361 | $mang->parse_rrule(); | |
362 | } | |
363 | ||
364 | /** | |
365 | * Test parsing of BYSETPOS rule. | |
366 | */ | |
367 | public function test_bysetpos_without_other_byrules() { | |
368 | $rrule = "BYSETPOS=1,366"; | |
369 | $mang = new rrule_manager($rrule); | |
370 | $this->expectException('moodle_exception'); | |
371 | $mang->parse_rrule(); | |
372 | } | |
373 | ||
374 | /** | |
375 | * Test parsing of BYSETPOS rule. | |
376 | */ | |
377 | public function test_bysetpos_upper_bound_validation() { | |
378 | $rrule = "BYSETPOS=1,366,367"; | |
379 | $mang = new rrule_manager($rrule); | |
380 | $this->expectException('moodle_exception'); | |
381 | $mang->parse_rrule(); | |
382 | } | |
383 | ||
384 | /** | |
385 | * Test parsing of BYSETPOS rule. | |
386 | */ | |
387 | public function test_bysetpos_0_validation() { | |
388 | $rrule = "BYSETPOS=0"; | |
389 | $mang = new rrule_manager($rrule); | |
390 | $this->expectException('moodle_exception'); | |
391 | $mang->parse_rrule(); | |
392 | } | |
393 | ||
394 | /** | |
395 | * Test parsing of BYSETPOS rule. | |
396 | */ | |
397 | public function test_bysetpos_lower_bound_validation() { | |
398 | $rrule = "BYSETPOS=-1,-366,-367"; | |
399 | $mang = new rrule_manager($rrule); | |
400 | $this->expectException('moodle_exception'); | |
401 | $mang->parse_rrule(); | |
402 | } | |
403 | ||
404 | /** | |
405 | * Test recurrence rules for daily frequency. | |
406 | */ | |
407 | public function test_daily_events() { | |
408 | global $DB; | |
409 | ||
410 | $rrule = 'FREQ=DAILY;COUNT=3'; // This should generate 2 child events + 1 parent. | |
411 | $mang = new rrule_manager($rrule); | |
412 | $mang->parse_rrule(); | |
413 | $mang->create_events($this->event); | |
414 | $count = $DB->count_records('event', array('repeatid' => $this->event->id)); | |
415 | $this->assertEquals(3, $count); | |
416 | $result = $DB->record_exists('event', array('repeatid' => $this->event->id, | |
417 | 'timestart' => ($this->event->timestart + DAYSECS))); | |
418 | $this->assertTrue($result); | |
419 | $result = $DB->record_exists('event', array('repeatid' => $this->event->id, | |
420 | 'timestart' => ($this->event->timestart + 2 * DAYSECS))); | |
421 | $this->assertTrue($result); | |
422 | ||
423 | $until = $this->event->timestart + DAYSECS * 2; | |
424 | $until = date('Y-m-d', $until); | |
425 | $rrule = "FREQ=DAILY;UNTIL=$until"; // This should generate 1 child event + 1 parent,since by then until bound would be hit. | |
426 | $mang = new rrule_manager($rrule); | |
427 | $mang->parse_rrule(); | |
428 | $mang->create_events($this->event); | |
429 | $count = $DB->count_records('event', array('repeatid' => $this->event->id)); | |
430 | $this->assertEquals(2, $count); | |
431 | $result = $DB->record_exists('event', array('repeatid' => $this->event->id, | |
432 | 'timestart' => ($this->event->timestart + DAYSECS))); | |
433 | $this->assertTrue($result); | |
434 | ||
435 | $rrule = 'FREQ=DAILY;COUNT=3;INTERVAL=3'; // This should generate 2 child events + 1 parent, every 3rd day. | |
436 | $mang = new rrule_manager($rrule); | |
437 | $mang->parse_rrule(); | |
438 | $mang->create_events($this->event); | |
439 | $count = $DB->count_records('event', array('repeatid' => $this->event->id)); | |
440 | $this->assertEquals(3, $count); | |
441 | $result = $DB->record_exists('event', array('repeatid' => $this->event->id, | |
442 | 'timestart' => ($this->event->timestart + 3 * DAYSECS))); | |
443 | $this->assertTrue($result); | |
444 | $result = $DB->record_exists('event', array('repeatid' => $this->event->id, | |
445 | 'timestart' => ($this->event->timestart + 6 * DAYSECS))); | |
446 | $this->assertTrue($result); | |
447 | } | |
448 | ||
449 | /** | |
450 | * Every 300 days, forever. | |
451 | */ | |
452 | public function test_every_300_days_forever() { | |
453 | global $DB; | |
454 | $startdatetime = new DateTime(date('Y-m-d H:i:s', $this->event->timestart)); | |
455 | ||
456 | $interval = new DateInterval('P300D'); | |
457 | $untildate = new DateTime(); | |
458 | $untildate->add(new DateInterval('P10Y')); | |
459 | $until = $untildate->getTimestamp(); | |
460 | ||
461 | // Forever event. This should generate events for time() + 10 year period, every 300 days. | |
462 | $rrule = 'FREQ=DAILY;INTERVAL=300'; | |
463 | $mang = new rrule_manager($rrule); | |
464 | $mang->parse_rrule(); | |
465 | $mang->create_events($this->event); | |
620fface | 466 | $records = $DB->get_records('event', array('repeatid' => $this->event->id), 'timestart ASC'); |
8ddc6567 JP |
467 | |
468 | $expecteddate = clone($startdatetime); | |
469 | foreach ($records as $record) { | |
470 | $this->assertLessThanOrEqual($until, $record->timestart); | |
471 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
472 | // Go to next iteration. | |
473 | $expecteddate->add($interval); | |
474 | } | |
475 | } | |
476 | ||
477 | /** | |
478 | * Test recurrence rules for weekly frequency. | |
479 | */ | |
480 | public function test_weekly_events() { | |
481 | global $DB; | |
482 | ||
483 | $rrule = 'FREQ=WEEKLY;COUNT=1'; | |
484 | $mang = new rrule_manager($rrule); | |
485 | $mang->parse_rrule(); | |
486 | $mang->create_events($this->event); | |
487 | $count = $DB->count_records('event', array('repeatid' => $this->event->id)); | |
488 | $this->assertEquals(1, $count); | |
489 | for ($i = 0; $i < $count; $i++) { | |
490 | $result = $DB->record_exists('event', array('repeatid' => $this->event->id, | |
491 | 'timestart' => ($this->event->timestart + $i * DAYSECS))); | |
492 | $this->assertTrue($result); | |
493 | } | |
494 | // This much seconds after the start of the day. | |
495 | $offset = $this->event->timestart - mktime(0, 0, 0, date("n", $this->event->timestart), date("j", $this->event->timestart), | |
496 | date("Y", $this->event->timestart)); | |
497 | ||
498 | // This should generate 4 weekly Monday events. | |
499 | $until = $this->event->timestart + WEEKSECS * 4; | |
500 | $until = date('Ymd\This\Z', $until); | |
501 | $rrule = "FREQ=WEEKLY;BYDAY=MO;UNTIL=$until"; | |
502 | $mang = new rrule_manager($rrule); | |
503 | $mang->parse_rrule(); | |
504 | $mang->create_events($this->event); | |
505 | $count = $DB->count_records('event', array('repeatid' => $this->event->id)); | |
506 | $this->assertEquals(4, $count); | |
507 | $timestart = $this->event->timestart; | |
508 | for ($i = 0; $i < $count; $i++) { | |
509 | $timestart = strtotime("+$offset seconds next Monday", $timestart); | |
510 | $result = $DB->record_exists('event', array('repeatid' => $this->event->id, 'timestart' => $timestart)); | |
511 | $this->assertTrue($result); | |
512 | } | |
513 | ||
514 | $startdatetime = new DateTime(date('Y-m-d H:i:s', $this->event->timestart)); | |
515 | $startdate = new DateTime(date('Y-m-d', $this->event->timestart)); | |
516 | ||
517 | $offsetinterval = $startdatetime->diff($startdate, true); | |
518 | $interval = new DateInterval('P3W'); | |
519 | ||
520 | // Every 3 weeks on Monday, Wednesday for 2 times. | |
521 | $rrule = 'FREQ=WEEKLY;INTERVAL=3;BYDAY=MO,WE;COUNT=2'; | |
522 | $mang = new rrule_manager($rrule); | |
523 | $mang->parse_rrule(); | |
524 | $mang->create_events($this->event); | |
525 | ||
526 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
527 | $this->assertCount(2, $records); | |
528 | ||
529 | $expecteddate = clone($startdate); | |
530 | $expecteddate->modify('1997-09-03'); | |
531 | foreach ($records as $record) { | |
532 | $expecteddate->add($offsetinterval); | |
533 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
534 | ||
535 | if (date('D', $record->timestart) === 'Mon') { | |
536 | // Go to the fifth day of this month. | |
537 | $expecteddate->modify('next Wednesday'); | |
538 | } else { | |
539 | // Reset to Monday. | |
540 | $expecteddate->modify('last Monday'); | |
541 | // Go to next period. | |
542 | $expecteddate->add($interval); | |
543 | } | |
544 | } | |
545 | ||
546 | // Forever event. This should generate events over time() + 10 year period, every 50th Monday. | |
547 | $rrule = 'FREQ=WEEKLY;BYDAY=MO;INTERVAL=50'; | |
548 | ||
549 | $mang = new rrule_manager($rrule); | |
550 | $mang->parse_rrule(); | |
551 | $mang->create_events($this->event); | |
552 | ||
553 | $untildate = new DateTime(); | |
554 | $untildate->add(new DateInterval('P10Y')); | |
555 | $until = $untildate->getTimestamp(); | |
556 | ||
557 | $interval = new DateInterval('P50W'); | |
558 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
559 | ||
560 | // First instance of this set of recurring events: Monday, 17-08-1998. | |
561 | $expecteddate = clone($startdate); | |
562 | $expecteddate->modify('1998-08-17'); | |
563 | $expecteddate->add($offsetinterval); | |
564 | foreach ($records as $record) { | |
565 | $eventdateexpected = $expecteddate->format('Y-m-d H:i:s'); | |
566 | $eventdateactual = date('Y-m-d H:i:s', $record->timestart); | |
567 | $this->assertEquals($eventdateexpected, $eventdateactual); | |
568 | ||
569 | $expecteddate->add($interval); | |
570 | $this->assertLessThanOrEqual($until, $record->timestart); | |
571 | } | |
572 | } | |
573 | ||
574 | /** | |
575 | * Test recurrence rules for monthly frequency for RRULE with COUNT and BYMONTHDAY rules set. | |
576 | */ | |
577 | public function test_monthly_events_with_count_bymonthday() { | |
578 | global $DB; | |
579 | ||
580 | $startdatetime = new DateTime(date('Y-m-d H:i:s', $this->event->timestart)); | |
581 | $interval = new DateInterval('P1M'); | |
582 | ||
583 | $rrule = "FREQ=MONTHLY;COUNT=3;BYMONTHDAY=2"; // This should generate 3 events in total. | |
584 | $mang = new rrule_manager($rrule); | |
585 | $mang->parse_rrule(); | |
586 | $mang->create_events($this->event); | |
620fface | 587 | $records = $DB->get_records('event', array('repeatid' => $this->event->id), 'timestart ASC'); |
8ddc6567 JP |
588 | $this->assertCount(3, $records); |
589 | ||
590 | $expecteddate = clone($startdatetime); | |
591 | foreach ($records as $record) { | |
592 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
593 | // Go to next month. | |
594 | $expecteddate->add($interval); | |
595 | } | |
596 | } | |
597 | ||
598 | /** | |
599 | * Test recurrence rules for monthly frequency for RRULE with BYMONTHDAY and UNTIL rules set. | |
600 | */ | |
601 | public function test_monthly_events_with_until_bymonthday() { | |
602 | global $DB; | |
603 | ||
604 | // This should generate 10 child event + 1 parent, since by then until bound would be hit. | |
605 | $until = strtotime('+1 day +10 months', $this->event->timestart); | |
606 | $until = date('Ymd\This\Z', $until); | |
607 | $rrule = "FREQ=MONTHLY;BYMONTHDAY=2;UNTIL=$until"; | |
608 | $mang = new rrule_manager($rrule); | |
609 | $mang->parse_rrule(); | |
610 | $mang->create_events($this->event); | |
611 | $count = $DB->count_records('event', ['repeatid' => $this->event->id]); | |
612 | $this->assertEquals(11, $count); | |
613 | for ($i = 0; $i < 11; $i++) { | |
614 | $time = strtotime("+$i month", $this->event->timestart); | |
615 | $result = $DB->record_exists('event', ['repeatid' => $this->event->id, 'timestart' => $time]); | |
616 | $this->assertTrue($result); | |
617 | } | |
618 | } | |
619 | ||
620 | /** | |
621 | * Test recurrence rules for monthly frequency for RRULE with BYMONTHDAY and UNTIL rules set. | |
622 | */ | |
623 | public function test_monthly_events_with_until_bymonthday_multi() { | |
624 | global $DB; | |
625 | ||
626 | $startdatetime = new DateTime(date('Y-m-d H:i:s', $this->event->timestart)); | |
627 | $startdate = new DateTime(date('Y-m-d', $this->event->timestart)); | |
628 | $offsetinterval = $startdatetime->diff($startdate, true); | |
629 | $interval = new DateInterval('P2M'); | |
630 | $untildate = clone($startdatetime); | |
631 | $untildate->add(new DateInterval('P10M10D')); | |
632 | $until = $untildate->format('Ymd\This\Z'); | |
633 | ||
634 | // This should generate 11 child event + 1 parent, since by then until bound would be hit. | |
635 | $rrule = "FREQ=MONTHLY;INTERVAL=2;BYMONTHDAY=2,5;UNTIL=$until"; | |
636 | ||
637 | $mang = new rrule_manager($rrule); | |
638 | $mang->parse_rrule(); | |
639 | $mang->create_events($this->event); | |
640 | ||
641 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
642 | $this->assertCount(12, $records); | |
643 | ||
644 | $expecteddate = clone($startdate); | |
645 | $expecteddate->add($offsetinterval); | |
646 | foreach ($records as $record) { | |
647 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
648 | ||
649 | if (date('j', $record->timestart) == 2) { | |
650 | // Go to the fifth day of this month. | |
651 | $expecteddate->add(new DateInterval('P3D')); | |
652 | } else { | |
653 | // Reset date to the first day of the month. | |
654 | $expecteddate->modify('first day of this month'); | |
655 | // Go to next month period. | |
656 | $expecteddate->add($interval); | |
657 | // Go to the second day of the next month period. | |
658 | $expecteddate->modify('+1 day'); | |
659 | } | |
660 | } | |
661 | } | |
662 | ||
663 | /** | |
664 | * Test recurrence rules for monthly frequency for RRULE with BYMONTHDAY forever. | |
665 | */ | |
666 | public function test_monthly_events_with_bymonthday_forever() { | |
667 | global $DB; | |
668 | ||
669 | $startdatetime = new DateTime(date('Y-m-d H:i:s', $this->event->timestart)); | |
670 | $startdate = new DateTime(date('Y-m-d', $this->event->timestart)); | |
671 | ||
672 | $offsetinterval = $startdatetime->diff($startdate, true); | |
673 | $interval = new DateInterval('P12M'); | |
674 | ||
675 | // Forever event. This should generate events over 10 year period, on 2nd day of every 12th month. | |
676 | $rrule = "FREQ=MONTHLY;INTERVAL=12;BYMONTHDAY=2"; | |
677 | ||
678 | $mang = new rrule_manager($rrule); | |
679 | $until = time() + (YEARSECS * $mang::TIME_UNLIMITED_YEARS); | |
680 | ||
681 | $mang->parse_rrule(); | |
682 | $mang->create_events($this->event); | |
683 | ||
684 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
685 | ||
686 | $expecteddate = clone($startdate); | |
687 | $expecteddate->add($offsetinterval); | |
688 | foreach ($records as $record) { | |
689 | $this->assertLessThanOrEqual($until, $record->timestart); | |
690 | ||
691 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
692 | ||
693 | // Reset date to the first day of the month. | |
694 | $expecteddate->modify('first day of this month'); | |
695 | // Go to next month period. | |
696 | $expecteddate->add($interval); | |
697 | // Go to the second day of the next month period. | |
698 | $expecteddate->modify('+1 day'); | |
699 | } | |
700 | } | |
701 | ||
702 | /** | |
703 | * Test recurrence rules for monthly frequency for RRULE with COUNT and BYDAY rules set. | |
704 | */ | |
705 | public function test_monthly_events_with_count_byday() { | |
706 | global $DB; | |
707 | ||
708 | $startdatetime = new DateTime(date('Y-m-d H:i:s', $this->event->timestart)); | |
709 | $startdate = new DateTime(date('Y-m-d', $this->event->timestart)); | |
710 | ||
711 | $offsetinterval = $startdatetime->diff($startdate, true); | |
712 | $interval = new DateInterval('P1M'); | |
713 | ||
714 | $rrule = 'FREQ=MONTHLY;COUNT=3;BYDAY=1MO'; // This should generate 3 events in total, first monday of the month. | |
715 | $mang = new rrule_manager($rrule); | |
716 | $mang->parse_rrule(); | |
717 | $mang->create_events($this->event); | |
718 | ||
719 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
720 | ||
721 | // First occurrence of this set of recurring events: 06-10-1997. | |
722 | $expecteddate = clone($startdate); | |
723 | $expecteddate->modify('1997-10-06'); | |
724 | $expecteddate->add($offsetinterval); | |
725 | foreach ($records as $record) { | |
726 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
727 | ||
728 | // Go to next month period. | |
729 | $expecteddate->add($interval); | |
730 | $expecteddate->modify('first Monday of this month'); | |
731 | $expecteddate->add($offsetinterval); | |
732 | } | |
733 | } | |
734 | ||
735 | /** | |
736 | * Test recurrence rules for monthly frequency for RRULE with BYDAY and UNTIL rules set. | |
737 | */ | |
738 | public function test_monthly_events_with_until_byday() { | |
739 | global $DB; | |
740 | ||
741 | // This much seconds after the start of the day. | |
742 | $startdatetime = new DateTime(date('Y-m-d H:i:s', $this->event->timestart)); | |
743 | $startdate = new DateTime(date('Y-m-d', $this->event->timestart)); | |
744 | $offsetinterval = $startdatetime->diff($startdate, true); | |
745 | ||
746 | $untildate = clone($startdatetime); | |
747 | $untildate->add(new DateInterval('P10M1D')); | |
748 | $until = $untildate->format('Ymd\This\Z'); | |
749 | ||
750 | // This rule should generate 9 events in total from first Monday of October 1997 to first Monday of June 1998. | |
751 | $rrule = "FREQ=MONTHLY;BYDAY=1MO;UNTIL=$until"; | |
752 | $mang = new rrule_manager($rrule); | |
753 | $mang->parse_rrule(); | |
754 | $mang->create_events($this->event); | |
755 | ||
756 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
757 | $this->assertCount(9, $records); | |
758 | ||
759 | $expecteddate = clone($startdate); | |
760 | $expecteddate->modify('first Monday of October 1997'); | |
761 | foreach ($records as $record) { | |
762 | $expecteddate->add($offsetinterval); | |
763 | ||
764 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
765 | ||
766 | // Go to next month. | |
767 | $expecteddate->modify('first day of next month'); | |
768 | // Go to the first Monday of the next month. | |
769 | $expecteddate->modify('first Monday of this month'); | |
770 | } | |
771 | } | |
772 | ||
773 | /** | |
774 | * Test recurrence rules for monthly frequency for RRULE with BYMONTHDAY and UNTIL rules set. | |
775 | */ | |
776 | public function test_monthly_events_with_until_byday_multi() { | |
777 | global $DB; | |
778 | ||
779 | $startdatetime = new DateTime(date('Y-m-d H:i:s', $this->event->timestart)); | |
780 | $startdate = new DateTime(date('Y-m-d', $this->event->timestart)); | |
781 | ||
782 | $offsetinterval = $startdatetime->diff($startdate, true); | |
783 | $interval = new DateInterval('P2M'); | |
784 | ||
785 | $untildate = clone($startdatetime); | |
786 | $untildate->add(new DateInterval('P10M20D')); | |
787 | $until = $untildate->format('Ymd\This\Z'); | |
788 | ||
789 | // This should generate 11 events from 17 Sep 1997 to 15 Jul 1998. | |
790 | $rrule = "FREQ=MONTHLY;INTERVAL=2;BYDAY=1MO,3WE;UNTIL=$until"; | |
791 | $mang = new rrule_manager($rrule); | |
792 | $mang->parse_rrule(); | |
793 | $mang->create_events($this->event); | |
794 | ||
795 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
796 | $this->assertCount(11, $records); | |
797 | ||
798 | $expecteddate = clone($startdate); | |
799 | $expecteddate->modify('1997-09-17'); | |
800 | foreach ($records as $record) { | |
801 | $expecteddate->add($offsetinterval); | |
802 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
803 | ||
804 | if (date('D', $record->timestart) === 'Mon') { | |
805 | // Go to the fifth day of this month. | |
806 | $expecteddate->modify('third Wednesday of this month'); | |
807 | } else { | |
808 | // Go to next month period. | |
809 | $expecteddate->add($interval); | |
810 | $expecteddate->modify('first Monday of this month'); | |
811 | } | |
812 | } | |
813 | } | |
814 | ||
815 | /** | |
816 | * Test recurrence rules for monthly frequency for RRULE with BYDAY forever. | |
817 | */ | |
818 | public function test_monthly_events_with_byday_forever() { | |
819 | global $DB; | |
820 | ||
821 | $startdatetime = new DateTime(date('Y-m-d H:i:s', $this->event->timestart)); | |
822 | $startdate = new DateTime(date('Y-m-d', $this->event->timestart)); | |
823 | ||
824 | $offsetinterval = $startdatetime->diff($startdate, true); | |
825 | $interval = new DateInterval('P12M'); | |
826 | ||
827 | // Forever event. This should generate events over 10 year period, on 2nd day of every 12th month. | |
828 | $rrule = "FREQ=MONTHLY;INTERVAL=12;BYDAY=1MO"; | |
829 | ||
830 | $mang = new rrule_manager($rrule); | |
831 | $until = time() + (YEARSECS * $mang::TIME_UNLIMITED_YEARS); | |
832 | ||
833 | $mang->parse_rrule(); | |
834 | $mang->create_events($this->event); | |
835 | ||
836 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
837 | ||
838 | $expecteddate = new DateTime('first Monday of September 1998'); | |
839 | foreach ($records as $record) { | |
840 | $expecteddate->add($offsetinterval); | |
841 | $this->assertLessThanOrEqual($until, $record->timestart); | |
842 | ||
843 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
844 | ||
845 | // Go to next month period. | |
846 | $expecteddate->add($interval); | |
847 | // Reset date to the first Monday of the month. | |
848 | $expecteddate->modify('first Monday of this month'); | |
849 | } | |
850 | } | |
851 | ||
852 | /** | |
853 | * Test recurrence rules for yearly frequency. | |
854 | */ | |
855 | public function test_yearly_events() { | |
856 | global $DB; | |
857 | ||
858 | $startdatetime = new DateTime(date('Y-m-d H:i:s', $this->event->timestart)); | |
859 | $startdate = new DateTime(date('Y-m-d', $this->event->timestart)); | |
860 | ||
861 | $offsetinterval = $startdatetime->diff($startdate, true); | |
862 | $interval = new DateInterval('P1Y'); | |
863 | ||
864 | $rrule = "FREQ=YEARLY;COUNT=3;BYMONTH=9"; // This should generate 3 events in total. | |
865 | $mang = new rrule_manager($rrule); | |
866 | $mang->parse_rrule(); | |
867 | $mang->create_events($this->event); | |
868 | ||
869 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
870 | $this->assertCount(3, $records); | |
871 | ||
872 | $expecteddate = clone($startdatetime); | |
873 | foreach ($records as $record) { | |
874 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
875 | ||
876 | // Go to next period. | |
877 | $expecteddate->add($interval); | |
878 | } | |
879 | ||
880 | // Create a yearly event, until the time limit is hit. | |
881 | $until = strtotime('+20 day +10 years', $this->event->timestart); | |
882 | $until = date('Ymd\THis\Z', $until); | |
883 | $rrule = "FREQ=YEARLY;BYMONTH=9;UNTIL=$until"; // Forever event. | |
884 | $mang = new rrule_manager($rrule); | |
885 | $mang->parse_rrule(); | |
886 | $mang->create_events($this->event); | |
887 | $count = $DB->count_records('event', array('repeatid' => $this->event->id)); | |
888 | $this->assertEquals(11, $count); | |
889 | for ($i = 0, $time = $this->event->timestart; $time < $until; $i++, $yoffset = $i * 2, | |
890 | $time = strtotime("+$yoffset years", $this->event->timestart)) { | |
891 | $result = $DB->record_exists('event', array('repeatid' => $this->event->id, | |
892 | 'timestart' => ($time))); | |
893 | $this->assertTrue($result); | |
894 | } | |
895 | ||
896 | // This should generate 5 events in total, every second year in the given month of the event. | |
897 | $rrule = "FREQ=YEARLY;BYMONTH=9;INTERVAL=2;COUNT=5"; | |
898 | $mang = new rrule_manager($rrule); | |
899 | $mang->parse_rrule(); | |
900 | $mang->create_events($this->event); | |
901 | $count = $DB->count_records('event', array('repeatid' => $this->event->id)); | |
902 | $this->assertEquals(5, $count); | |
903 | for ($i = 0, $time = $this->event->timestart; $i < 5; $i++, $yoffset = $i * 2, | |
904 | $time = strtotime("+$yoffset years", $this->event->timestart)) { | |
905 | $result = $DB->record_exists('event', array('repeatid' => $this->event->id, | |
906 | 'timestart' => ($time))); | |
907 | $this->assertTrue($result); | |
908 | } | |
909 | ||
910 | $rrule = "FREQ=YEARLY;BYMONTH=9;INTERVAL=2"; // Forever event. | |
911 | $mang = new rrule_manager($rrule); | |
912 | $until = time() + (YEARSECS * $mang::TIME_UNLIMITED_YEARS); | |
913 | $mang->parse_rrule(); | |
914 | $mang->create_events($this->event); | |
915 | for ($i = 0, $time = $this->event->timestart; $time < $until; $i++, $yoffset = $i * 2, | |
916 | $time = strtotime("+$yoffset years", $this->event->timestart)) { | |
917 | $result = $DB->record_exists('event', array('repeatid' => $this->event->id, | |
918 | 'timestart' => ($time))); | |
919 | $this->assertTrue($result); | |
920 | } | |
921 | ||
922 | $rrule = "FREQ=YEARLY;COUNT=3;BYMONTH=9;BYDAY=1MO"; // This should generate 3 events in total. | |
923 | $mang = new rrule_manager($rrule); | |
924 | $mang->parse_rrule(); | |
925 | $mang->create_events($this->event); | |
926 | ||
927 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
928 | $this->assertCount(3, $records); | |
929 | ||
930 | $expecteddate = clone($startdatetime); | |
931 | $expecteddate->modify('first Monday of September 1998'); | |
932 | $expecteddate->add($offsetinterval); | |
933 | foreach ($records as $record) { | |
934 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
935 | ||
936 | // Go to next period. | |
937 | $expecteddate->add($interval); | |
938 | $monthyear = $expecteddate->format('F Y'); | |
939 | $expecteddate->modify('first Monday of ' . $monthyear); | |
940 | $expecteddate->add($offsetinterval); | |
941 | } | |
942 | ||
943 | // Create a yearly event on the specified month, until the time limit is hit. | |
944 | $untildate = clone($startdatetime); | |
945 | $untildate->add(new DateInterval('P10Y20D')); | |
946 | $until = $untildate->format('Ymd\THis\Z'); | |
947 | ||
948 | $rrule = "FREQ=YEARLY;BYMONTH=9;UNTIL=$until;BYDAY=1MO"; | |
949 | $mang = new rrule_manager($rrule); | |
950 | $mang->parse_rrule(); | |
951 | $mang->create_events($this->event); | |
952 | ||
953 | // 10 yearly records every first Monday of September 1998 to first Monday of September 2007. | |
954 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
955 | $this->assertCount(10, $records); | |
956 | ||
957 | $expecteddate = clone($startdatetime); | |
958 | $expecteddate->modify('first Monday of September 1998'); | |
959 | $expecteddate->add($offsetinterval); | |
960 | foreach ($records as $record) { | |
961 | $this->assertLessThanOrEqual($untildate->getTimestamp(), $record->timestart); | |
962 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
963 | ||
964 | // Go to next period. | |
965 | $expecteddate->add($interval); | |
966 | $monthyear = $expecteddate->format('F Y'); | |
967 | $expecteddate->modify('first Monday of ' . $monthyear); | |
968 | $expecteddate->add($offsetinterval); | |
969 | } | |
970 | ||
971 | // This should generate 5 events in total, every second year in the month of September. | |
972 | $rrule = "FREQ=YEARLY;BYMONTH=9;INTERVAL=2;COUNT=5;BYDAY=1MO"; | |
973 | $mang = new rrule_manager($rrule); | |
974 | $mang->parse_rrule(); | |
975 | $mang->create_events($this->event); | |
976 | ||
977 | // 5 bi-yearly records every first Monday of September 1998 to first Monday of September 2007. | |
978 | $interval = new DateInterval('P2Y'); | |
979 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
980 | $this->assertCount(5, $records); | |
981 | ||
982 | $expecteddate = clone($startdatetime); | |
983 | $expecteddate->modify('first Monday of September 1999'); | |
984 | $expecteddate->add($offsetinterval); | |
985 | foreach ($records as $record) { | |
986 | $this->assertLessThanOrEqual($untildate->getTimestamp(), $record->timestart); | |
987 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
988 | ||
989 | // Go to next period. | |
990 | $expecteddate->add($interval); | |
991 | $monthyear = $expecteddate->format('F Y'); | |
992 | $expecteddate->modify('first Monday of ' . $monthyear); | |
993 | $expecteddate->add($offsetinterval); | |
994 | } | |
995 | } | |
996 | ||
997 | /** | |
998 | * Test for rrule with FREQ=YEARLY with BYMONTH and BYDAY rules set, recurring forever. | |
999 | */ | |
1000 | public function test_yearly_bymonth_byday_forever() { | |
1001 | global $DB; | |
1002 | ||
1003 | // Every 2 years on the first Monday of September. | |
1004 | $rrule = "FREQ=YEARLY;BYMONTH=9;INTERVAL=2;BYDAY=1MO"; | |
1005 | $mang = new rrule_manager($rrule); | |
1006 | $mang->parse_rrule(); | |
1007 | $mang->create_events($this->event); | |
1008 | ||
1009 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
1010 | ||
1011 | $untildate = new DateTime(); | |
1012 | $untildate->add(new DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y')); | |
1013 | $untiltimestamp = $untildate->getTimestamp(); | |
1014 | ||
1015 | $startdatetime = new DateTime(date('Y-m-d H:i:s', $this->event->timestart)); | |
1016 | $startdate = new DateTime(date('Y-m-d', $this->event->timestart)); | |
1017 | ||
1018 | $offsetinterval = $startdatetime->diff($startdate, true); | |
1019 | $interval = new DateInterval('P2Y'); | |
1020 | ||
1021 | // First occurrence of this set of events is on the first Monday of September 1999. | |
1022 | $expecteddate = clone($startdatetime); | |
1023 | $expecteddate->modify('first Monday of September 1999'); | |
1024 | $expecteddate->add($offsetinterval); | |
1025 | foreach ($records as $record) { | |
1026 | $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); | |
1027 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
1028 | ||
1029 | // Go to next period. | |
1030 | $expecteddate->add($interval); | |
1031 | $monthyear = $expecteddate->format('F Y'); | |
1032 | $expecteddate->modify('first Monday of ' . $monthyear); | |
1033 | $expecteddate->add($offsetinterval); | |
1034 | } | |
1035 | } | |
1036 | ||
1037 | /** | |
1038 | * Test for rrule with FREQ=YEARLY recurring forever. | |
1039 | */ | |
1040 | public function test_yearly_forever() { | |
1041 | global $DB; | |
1042 | ||
1043 | $startdatetime = new DateTime(date('Y-m-d H:i:s', $this->event->timestart)); | |
1044 | ||
1045 | $interval = new DateInterval('P2Y'); | |
1046 | ||
1047 | $rrule = 'FREQ=YEARLY;INTERVAL=2'; // Forever event. | |
1048 | $mang = new rrule_manager($rrule); | |
1049 | $mang->parse_rrule(); | |
1050 | $mang->create_events($this->event); | |
1051 | ||
1052 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
1053 | ||
1054 | $untildate = new DateTime(); | |
1055 | $untildate->add(new DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y')); | |
1056 | $untiltimestamp = $untildate->getTimestamp(); | |
1057 | ||
1058 | $expecteddate = clone($startdatetime); | |
1059 | foreach ($records as $record) { | |
1060 | $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); | |
1061 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
1062 | ||
1063 | // Go to next period. | |
1064 | $expecteddate->add($interval); | |
1065 | } | |
1066 | } | |
1067 | ||
1068 | /******************************************************************************************************************************/ | |
1069 | /* Tests based on the examples from the RFC. */ | |
1070 | /******************************************************************************************************************************/ | |
1071 | ||
1072 | /** | |
1073 | * Daily for 10 occurrences: | |
1074 | * | |
1075 | * DTSTART;TZID=US-Eastern:19970902T090000 | |
1076 | * RRULE:FREQ=DAILY;COUNT=10 | |
1077 | * ==> (1997 9:00 AM EDT)September 2-11 | |
1078 | */ | |
1079 | public function test_daily_count() { | |
1080 | global $DB; | |
1081 | ||
1082 | $startdatetime = new DateTime(date('Y-m-d H:i:s', $this->event->timestart)); | |
1083 | $interval = new DateInterval('P1D'); | |
1084 | ||
1085 | $rrule = 'FREQ=DAILY;COUNT=10'; | |
1086 | $mang = new rrule_manager($rrule); | |
1087 | $mang->parse_rrule(); | |
1088 | $mang->create_events($this->event); | |
1089 | ||
1090 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
1091 | $this->assertCount(10, $records); | |
1092 | ||
1093 | $expecteddate = new DateTime(date('Y-m-d H:i:s', $startdatetime->getTimestamp())); | |
1094 | foreach ($records as $record) { | |
1095 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
1096 | ||
1097 | // Go to next period. | |
1098 | $expecteddate->add($interval); | |
1099 | } | |
1100 | } | |
1101 | ||
1102 | /** | |
1103 | * Daily until December 24, 1997: | |
1104 | * | |
1105 | * DTSTART;TZID=US-Eastern:19970902T090000 | |
1106 | * RRULE:FREQ=DAILY;UNTIL=19971224T000000Z | |
1107 | * ==> (1997 9:00 AM EDT)September 2-30;October 1-25 | |
1108 | * (1997 9:00 AM EST)October 26-31;November 1-30;December 1-23 | |
1109 | */ | |
1110 | public function test_daily_until() { | |
1111 | global $DB; | |
1112 | ||
1113 | $startdatetime = new DateTime(date('Y-m-d H:i:s', $this->event->timestart)); | |
1114 | $interval = new DateInterval('P1D'); | |
1115 | ||
1116 | $untildate = new DateTime('19971224T000000Z'); | |
1117 | $untiltimestamp = $untildate->getTimestamp(); | |
1118 | ||
1119 | $rrule = 'FREQ=DAILY;UNTIL=19971224T000000Z'; | |
1120 | $mang = new rrule_manager($rrule); | |
1121 | $mang->parse_rrule(); | |
1122 | $mang->create_events($this->event); | |
1123 | ||
1124 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
1125 | // 113 daily events from 02-09-1997 to 23-12-1997. | |
1126 | $this->assertCount(113, $records); | |
1127 | ||
1128 | $expecteddate = new DateTime(date('Y-m-d H:i:s', $startdatetime->getTimestamp())); | |
1129 | foreach ($records as $record) { | |
1130 | $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); | |
1131 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
1132 | // Go to next period. | |
1133 | $expecteddate->add($interval); | |
1134 | } | |
1135 | } | |
1136 | ||
1137 | /** | |
1138 | * Every other day - forever: | |
1139 | * | |
1140 | * DTSTART;TZID=US-Eastern:19970902T090000 | |
1141 | * RRULE:FREQ=DAILY;INTERVAL=2 | |
1142 | * ==> (1997 9:00 AM EDT)September2,4,6,8...24,26,28,30;October 2,4,6...20,22,24 | |
1143 | * (1997 9:00 AM EST)October 26,28,30;November 1,3,5,7...25,27,29;Dec 1,3,... | |
1144 | */ | |
1145 | public function test_every_other_day_forever() { | |
1146 | global $DB; | |
1147 | ||
1148 | $startdatetime = new DateTime(date('Y-m-d H:i:s', $this->event->timestart)); | |
1149 | $interval = new DateInterval('P2D'); | |
1150 | ||
1151 | $rrule = 'FREQ=DAILY;INTERVAL=2'; | |
1152 | $mang = new rrule_manager($rrule); | |
1153 | $mang->parse_rrule(); | |
1154 | $mang->create_events($this->event); | |
1155 | ||
1156 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
1157 | ||
1158 | $untildate = new DateTime(); | |
1159 | $untildate->add(new DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y')); | |
1160 | $untiltimestamp = $untildate->getTimestamp(); | |
1161 | ||
1162 | $expecteddate = new DateTime(date('Y-m-d H:i:s', $startdatetime->getTimestamp())); | |
1163 | foreach ($records as $record) { | |
1164 | $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); | |
1165 | ||
1166 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
1167 | // Go to next period. | |
1168 | $expecteddate->add($interval); | |
1169 | } | |
1170 | } | |
1171 | ||
1172 | /** | |
1173 | * Every 10 days, 5 occurrences: | |
1174 | * | |
1175 | * DTSTART;TZID=US-Eastern:19970902T090000 | |
1176 | * RRULE:FREQ=DAILY;INTERVAL=10;COUNT=5 | |
1177 | * ==> (1997 9:00 AM EDT)September 2,12,22;October 2,12 | |
1178 | */ | |
1179 | public function test_every_10_days_5_count() { | |
1180 | global $DB; | |
1181 | ||
1182 | $startdatetime = new DateTime(date('Y-m-d H:i:s', $this->event->timestart)); | |
1183 | $interval = new DateInterval('P10D'); | |
1184 | ||
1185 | $rrule = 'FREQ=DAILY;INTERVAL=10;COUNT=5'; | |
1186 | $mang = new rrule_manager($rrule); | |
1187 | $mang->parse_rrule(); | |
1188 | $mang->create_events($this->event); | |
1189 | ||
1190 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
1191 | $this->assertCount(5, $records); | |
1192 | ||
1193 | $expecteddate = new DateTime(date('Y-m-d H:i:s', $startdatetime->getTimestamp())); | |
1194 | foreach ($records as $record) { | |
1195 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
1196 | // Go to next period. | |
1197 | $expecteddate->add($interval); | |
1198 | } | |
1199 | } | |
1200 | ||
1201 | /** | |
1202 | * Everyday in January, for 3 years: | |
1203 | * | |
1204 | * DTSTART;TZID=US-Eastern:19980101T090000 | |
1205 | * RRULE:FREQ=YEARLY;UNTIL=20000131T090000Z;BYMONTH=1;BYDAY=SU,MO,TU,WE,TH,FR,SA | |
1206 | * ==> (1998 9:00 AM EDT)January 1-31 | |
1207 | * (1999 9:00 AM EDT)January 1-31 | |
1208 | * (2000 9:00 AM EDT)January 1-31 | |
1209 | */ | |
1210 | public function test_everyday_in_jan_for_3_years_yearly() { | |
1211 | global $DB; | |
1212 | ||
1213 | // Change our event's date to 01-01-1998, based on the example from the RFC. | |
1214 | $this->change_event_startdate('19980101T090000', 'US/Eastern'); | |
1215 | ||
1216 | $rrule = 'FREQ=YEARLY;UNTIL=20000131T090000Z;BYMONTH=1;BYDAY=SU,MO,TU,WE,TH,FR,SA'; | |
1217 | $mang = new rrule_manager($rrule); | |
1218 | $mang->parse_rrule(); | |
1219 | $mang->create_events($this->event); | |
1220 | ||
1221 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
1222 | // 92 events from 01-01-1998 to 03-01-2000. | |
1223 | $this->assertCount(92, $records); | |
1224 | ||
1225 | $untildate = new DateTime('20000131T090000Z'); | |
1226 | $untiltimestamp = $untildate->getTimestamp(); | |
1227 | foreach ($records as $record) { | |
1228 | $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); | |
1229 | ||
1230 | // Assert that the event's date is in January. | |
1231 | $this->assertEquals('January', date('F', $record->timestart)); | |
1232 | } | |
1233 | } | |
1234 | ||
1235 | /** | |
1236 | * Everyday in January, for 3 years: | |
1237 | * | |
1238 | * DTSTART;TZID=US-Eastern:19980101T090000 | |
1239 | * RRULE:FREQ=DAILY;UNTIL=20000131T090000Z;BYMONTH=1 | |
1240 | * ==> (1998 9:00 AM EDT)January 1-31 | |
1241 | * (1999 9:00 AM EDT)January 1-31 | |
1242 | * (2000 9:00 AM EDT)January 1-31 | |
1243 | */ | |
1244 | public function test_everyday_in_jan_for_3_years_daily() { | |
1245 | global $DB; | |
1246 | ||
1247 | // Change our event's date to 01-01-1998, based on the example from the RFC. | |
1248 | $this->change_event_startdate('19980101T090000', 'US/Eastern'); | |
1249 | ||
1250 | $rrule = 'FREQ=DAILY;UNTIL=20000131T090000Z;BYMONTH=1'; | |
1251 | $mang = new rrule_manager($rrule); | |
1252 | $mang->parse_rrule(); | |
1253 | $mang->create_events($this->event); | |
1254 | ||
1255 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
1256 | // 92 events from 01-01-1998 to 03-01-2000. | |
1257 | $this->assertCount(92, $records); | |
1258 | ||
1259 | $untildate = new DateTime('20000131T090000Z'); | |
1260 | $untiltimestamp = $untildate->getTimestamp(); | |
1261 | foreach ($records as $record) { | |
1262 | $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); | |
1263 | ||
1264 | // Assert that the event's date is in January. | |
1265 | $this->assertEquals('January', date('F', $record->timestart)); | |
1266 | } | |
1267 | } | |
1268 | ||
1269 | /** | |
1270 | * Weekly for 10 occurrences | |
1271 | * | |
1272 | * DTSTART;TZID=US-Eastern:19970902T090000 | |
1273 | * RRULE:FREQ=WEEKLY;COUNT=10 | |
1274 | * ==> (1997 9:00 AM EDT)September 2,9,16,23,30;October 7,14,21 | |
1275 | * (1997 9:00 AM EST)October 28;November 4 | |
1276 | */ | |
1277 | public function test_weekly_10_count() { | |
1278 | global $DB; | |
1279 | ||
1280 | $interval = new DateInterval('P1W'); | |
1281 | ||
1282 | $rrule = 'FREQ=WEEKLY;COUNT=10'; | |
1283 | $mang = new rrule_manager($rrule); | |
1284 | $mang->parse_rrule(); | |
1285 | $mang->create_events($this->event); | |
1286 | ||
1287 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
1288 | $this->assertCount(10, $records); | |
1289 | ||
1290 | $expecteddate = new DateTime(date('Y-m-d H:i:s', $this->event->timestart)); | |
1291 | foreach ($records as $record) { | |
1292 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
1293 | // Go to next period. | |
1294 | $expecteddate->add($interval); | |
1295 | } | |
1296 | } | |
1297 | ||
1298 | /** | |
1299 | * Weekly until December 24, 1997. | |
1300 | * | |
1301 | * DTSTART;TZID=US-Eastern:19970902T090000 | |
1302 | * RRULE:FREQ=WEEKLY;UNTIL=19971224T000000Z | |
1303 | * ==> (1997 9:00 AM EDT)September 2,9,16,23,30;October 7,14,21,28 | |
1304 | * (1997 9:00 AM EST)November 4,11,18,25;December 2,9,16,23 | |
1305 | */ | |
1306 | public function test_weekly_until_24_dec_1997() { | |
1307 | global $DB; | |
1308 | ||
1309 | $interval = new DateInterval('P1W'); | |
1310 | ||
1311 | $rrule = 'FREQ=WEEKLY;UNTIL=19971224T000000Z'; | |
1312 | $mang = new rrule_manager($rrule); | |
1313 | $mang->parse_rrule(); | |
1314 | $mang->create_events($this->event); | |
1315 | ||
1316 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
1317 | // 17 iterations from 02-09-1997 13:00 UTC to 23-12-1997 13:00 UTC. | |
1318 | $this->assertCount(17, $records); | |
1319 | ||
1320 | $untildate = new DateTime('19971224T000000Z'); | |
1321 | $untiltimestamp = $untildate->getTimestamp(); | |
1322 | $expecteddate = new DateTime(date('Y-m-d H:i:s', $this->event->timestart)); | |
1323 | foreach ($records as $record) { | |
1324 | $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); | |
1325 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
1326 | // Go to next period. | |
1327 | $expecteddate->add($interval); | |
1328 | } | |
1329 | } | |
1330 | ||
1331 | /** | |
1332 | * Every other week - forever: | |
1333 | * | |
1334 | * DTSTART;TZID=US-Eastern:19970902T090000 | |
1335 | * RRULE:FREQ=WEEKLY;INTERVAL=2;WKST=SU | |
1336 | * ==> (1997 9:00 AM EDT)September 2,16,30;October 14 | |
1337 | * (1997 9:00 AM EST)October 28;November 11,25;December 9,23 | |
1338 | * (1998 9:00 AM EST)January 6,20;February | |
1339 | * ... | |
1340 | */ | |
1341 | public function test_every_other_week_forever() { | |
1342 | global $DB; | |
1343 | ||
1344 | $interval = new DateInterval('P2W'); | |
1345 | ||
1346 | $rrule = 'FREQ=WEEKLY;INTERVAL=2;WKST=SU'; | |
1347 | $mang = new rrule_manager($rrule); | |
1348 | $mang->parse_rrule(); | |
1349 | $mang->create_events($this->event); | |
1350 | ||
1351 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
1352 | ||
1353 | $untildate = new DateTime(); | |
1354 | $untildate->add(new DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y')); | |
1355 | $untiltimestamp = $untildate->getTimestamp(); | |
1356 | ||
1357 | $expecteddate = new DateTime(date('Y-m-d H:i:s', $this->event->timestart)); | |
1358 | foreach ($records as $record) { | |
1359 | $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); | |
1360 | ||
1361 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
1362 | // Go to next period. | |
1363 | $expecteddate->add($interval); | |
1364 | } | |
1365 | } | |
1366 | ||
1367 | /** | |
1368 | * Weekly on Tuesday and Thursday for 5 weeks: | |
1369 | * | |
1370 | * DTSTART;TZID=US-Eastern:19970902T090000 | |
1371 | * RRULE:FREQ=WEEKLY;UNTIL=19971007T000000Z;WKST=SU;BYDAY=TU,TH | |
1372 | * ==> (1997 9:00 AM EDT)September 2,4,9,11,16,18,23,25,30;October 2 | |
1373 | */ | |
1374 | public function test_weekly_on_tue_thu_for_5_weeks_by_until() { | |
1375 | global $DB; | |
1376 | ||
1377 | $rrule = 'FREQ=WEEKLY;UNTIL=19971007T000000Z;WKST=SU;BYDAY=TU,TH'; | |
1378 | $mang = new rrule_manager($rrule); | |
1379 | $mang->parse_rrule(); | |
1380 | $mang->create_events($this->event); | |
1381 | ||
1382 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
1383 | // 17 iterations from 02-09-1997 13:00 UTC to 23-12-1997 13:00 UTC. | |
1384 | $this->assertCount(10, $records); | |
1385 | ||
1386 | $untildate = new DateTime('19971007T000000Z'); | |
1387 | $untiltimestamp = $untildate->getTimestamp(); | |
1388 | $expecteddate = new DateTime(date('Y-m-d H:i:s', $this->event->timestart)); | |
1389 | $startdate = new DateTime($expecteddate->format('Y-m-d')); | |
1390 | $offset = $expecteddate->diff($startdate, true); | |
1391 | foreach ($records as $record) { | |
1392 | $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); | |
1393 | ||
1394 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
1395 | // Go to next period. | |
1396 | if ($expecteddate->format('l') === rrule_manager::DAY_TUESDAY) { | |
1397 | $expecteddate->modify('next Thursday'); | |
1398 | } else { | |
1399 | $expecteddate->modify('next Tuesday'); | |
1400 | } | |
1401 | $expecteddate->add($offset); | |
1402 | } | |
1403 | } | |
1404 | ||
1405 | /** | |
1406 | * Weekly on Tuesday and Thursday for 5 weeks: | |
1407 | * | |
1408 | * DTSTART;TZID=US-Eastern:19970902T090000 | |
1409 | * RRULE:FREQ=WEEKLY;COUNT=10;WKST=SU;BYDAY=TU,TH | |
1410 | * ==> (1997 9:00 AM EDT)September 2,4,9,11,16,18,23,25,30;October 2 | |
1411 | */ | |
1412 | public function test_weekly_on_tue_thu_for_5_weeks_by_count() { | |
1413 | global $DB; | |
1414 | ||
1415 | $rrule = 'FREQ=WEEKLY;COUNT=10;WKST=SU;BYDAY=TU,TH'; | |
1416 | $mang = new rrule_manager($rrule); | |
1417 | $mang->parse_rrule(); | |
1418 | $mang->create_events($this->event); | |
1419 | ||
1420 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
1421 | // 17 iterations from 02-09-1997 13:00 UTC to 23-12-1997 13:00 UTC. | |
1422 | $this->assertCount(10, $records); | |
1423 | ||
1424 | $expecteddate = new DateTime(date('Y-m-d H:i:s', $this->event->timestart)); | |
1425 | $startdate = new DateTime($expecteddate->format('Y-m-d')); | |
1426 | $offset = $expecteddate->diff($startdate, true); | |
1427 | foreach ($records as $record) { | |
1428 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
1429 | // Go to next period. | |
1430 | if ($expecteddate->format('l') === rrule_manager::DAY_TUESDAY) { | |
1431 | $expecteddate->modify('next Thursday'); | |
1432 | } else { | |
1433 | $expecteddate->modify('next Tuesday'); | |
1434 | } | |
1435 | $expecteddate->add($offset); | |
1436 | } | |
1437 | } | |
1438 | ||
1439 | /** | |
1440 | * Every other week on Monday, Wednesday and Friday until December 24, 1997, but starting on Tuesday, September 2, 1997: | |
1441 | * | |
1442 | * DTSTART;TZID=US-Eastern:19970902T090000 | |
1443 | * RRULE:FREQ=WEEKLY;INTERVAL=2;UNTIL=19971224T000000Z;WKST=SU;BYDAY=MO,WE,FR | |
1444 | * ==> (1997 9:00 AM EDT)September 3,5,15,17,19,29;October 1,3,13,15,17 | |
1445 | * (1997 9:00 AM EST)October 27,29,31;November 10,12,14,24,26,28;December 8,10,12,22 | |
1446 | */ | |
1447 | public function test_every_other_week_until_24_dec_1997_byday() { | |
1448 | global $DB; | |
1449 | ||
1450 | $rrule = 'FREQ=WEEKLY;INTERVAL=2;UNTIL=19971224T000000Z;WKST=SU;BYDAY=MO,WE,FR'; | |
1451 | $mang = new rrule_manager($rrule); | |
1452 | $mang->parse_rrule(); | |
1453 | $mang->create_events($this->event); | |
1454 | ||
1455 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
1456 | // 24 iterations every M-W-F from 03-09-1997 13:00 UTC to 22-12-1997 13:00 UTC. | |
1457 | $this->assertCount(24, $records); | |
1458 | ||
1459 | $untildate = new DateTime('19971224T000000Z'); | |
1460 | $untiltimestamp = $untildate->getTimestamp(); | |
1461 | ||
1462 | $startdatetime = new DateTime(date('Y-m-d H:i:s', $this->event->timestart)); | |
1463 | $startdate = new DateTime(date('Y-m-d', $this->event->timestart)); | |
1464 | ||
1465 | $offsetinterval = $startdatetime->diff($startdate, true); | |
1466 | ||
1467 | // First occurrence of this set of events is on 3 September 1999. | |
1468 | $expecteddate = clone($startdatetime); | |
1469 | $expecteddate->modify('next Wednesday'); | |
1470 | $expecteddate->add($offsetinterval); | |
1471 | foreach ($records as $record) { | |
1472 | $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); | |
1473 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
1474 | ||
1475 | // Go to next period. | |
1476 | switch ($expecteddate->format('l')) { | |
1477 | case rrule_manager::DAY_MONDAY: | |
1478 | $expecteddate->modify('next Wednesday'); | |
1479 | break; | |
1480 | case rrule_manager::DAY_WEDNESDAY: | |
1481 | $expecteddate->modify('next Friday'); | |
1482 | break; | |
1483 | default: | |
1484 | $expecteddate->modify('next Monday'); | |
1485 | // Increment expected date by 1 week if the next day is Monday. | |
1486 | $expecteddate->add(new DateInterval('P1W')); | |
1487 | break; | |
1488 | } | |
1489 | $expecteddate->add($offsetinterval); | |
1490 | } | |
1491 | } | |
1492 | ||
1493 | /** | |
1494 | * Every other week on Tuesday and Thursday, for 8 occurrences: | |
1495 | * | |
1496 | * DTSTART;TZID=US-Eastern:19970902T090000 | |
1497 | * RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=8;WKST=SU;BYDAY=TU,TH | |
1498 | * ==> (1997 9:00 AM EDT)September 2,4,16,18,30;October 2,14,16 | |
1499 | */ | |
1500 | public function test_every_other_week_byday_8_count() { | |
1501 | global $DB; | |
1502 | ||
1503 | $rrule = 'FREQ=WEEKLY;INTERVAL=2;COUNT=8;WKST=SU;BYDAY=TU,TH'; | |
1504 | $mang = new rrule_manager($rrule); | |
1505 | $mang->parse_rrule(); | |
1506 | $mang->create_events($this->event); | |
1507 | ||
1508 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
1509 | // Should correspond to COUNT rule. | |
1510 | $this->assertCount(8, $records); | |
1511 | ||
1512 | $startdatetime = new DateTime(date('Y-m-d H:i:s', $this->event->timestart)); | |
1513 | $startdate = new DateTime(date('Y-m-d', $this->event->timestart)); | |
1514 | ||
1515 | $offsetinterval = $startdatetime->diff($startdate, true); | |
1516 | ||
1517 | // First occurrence of this set of events is on 2 September 1999. | |
1518 | $expecteddate = clone($startdatetime); | |
1519 | foreach ($records as $record) { | |
1520 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
1521 | ||
1522 | // Go to next period. | |
1523 | switch ($expecteddate->format('l')) { | |
1524 | case rrule_manager::DAY_TUESDAY: | |
1525 | $expecteddate->modify('next Thursday'); | |
1526 | break; | |
1527 | default: | |
1528 | $expecteddate->modify('next Tuesday'); | |
1529 | // Increment expected date by 1 week if the next day is Tuesday. | |
1530 | $expecteddate->add(new DateInterval('P1W')); | |
1531 | break; | |
1532 | } | |
1533 | $expecteddate->add($offsetinterval); | |
1534 | } | |
1535 | } | |
1536 | ||
1537 | /** | |
1538 | * Monthly on the 1st Friday for ten occurrences: | |
1539 | * | |
1540 | * DTSTART;TZID=US-Eastern:19970905T090000 | |
1541 | * RRULE:FREQ=MONTHLY;COUNT=10;BYDAY=1FR | |
1542 | * ==> (1997 9:00 AM EDT)September 5;October 3 | |
1543 | * (1997 9:00 AM EST)November 7;Dec 5 | |
1544 | * (1998 9:00 AM EST)January 2;February 6;March 6;April 3 | |
1545 | * (1998 9:00 AM EDT)May 1;June 5 | |
1546 | */ | |
1547 | public function test_monthly_every_first_friday_10_count() { | |
1548 | global $DB; | |
1549 | ||
1550 | // Change our event's date to 05-09-1997, based on the example from the RFC. | |
1551 | $startdatetime = $this->change_event_startdate('19970905T090000', 'US/Eastern'); | |
1552 | $startdate = new DateTime(date('Y-m-d', $this->event->timestart)); | |
1553 | $offsetinterval = $startdatetime->diff($startdate, true); | |
1554 | ||
1555 | $rrule = 'FREQ=MONTHLY;COUNT=10;BYDAY=1FR'; | |
1556 | $mang = new rrule_manager($rrule); | |
1557 | $mang->parse_rrule(); | |
1558 | $mang->create_events($this->event); | |
1559 | ||
1560 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
1561 | // Should correspond to COUNT rule. | |
1562 | $this->assertCount(10, $records); | |
1563 | ||
1564 | foreach ($records as $record) { | |
1565 | // Get the first Friday of the record's month. | |
1566 | $recordmonthyear = date('F Y', $record->timestart); | |
1567 | $expecteddate = new DateTime('first Friday of ' . $recordmonthyear); | |
1568 | // Add the time of the event. | |
1569 | $expecteddate->add($offsetinterval); | |
1570 | ||
1571 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
1572 | } | |
1573 | } | |
1574 | ||
1575 | /** | |
1576 | * Monthly on the 1st Friday until December 24, 1997: | |
1577 | * | |
1578 | * DTSTART;TZID=US-Eastern:19970905T090000 | |
1579 | * RRULE:FREQ=MONTHLY;UNTIL=19971224T000000Z;BYDAY=1FR | |
1580 | * ==> (1997 9:00 AM EDT)September 5;October 3 | |
1581 | * (1997 9:00 AM EST)November 7;December 5 | |
1582 | */ | |
1583 | public function test_monthly_every_first_friday_until() { | |
1584 | global $DB; | |
1585 | ||
1586 | // Change our event's date to 05-09-1997, based on the example from the RFC. | |
1587 | $startdatetime = $this->change_event_startdate('19970905T090000', 'US/Eastern'); | |
1588 | $startdate = new DateTime(date('Y-m-d', $this->event->timestart)); | |
1589 | $offsetinterval = $startdatetime->diff($startdate, true); | |
1590 | ||
1591 | $rrule = 'FREQ=MONTHLY;UNTIL=19971224T000000Z;BYDAY=1FR'; | |
1592 | $mang = new rrule_manager($rrule); | |
1593 | $mang->parse_rrule(); | |
1594 | $mang->create_events($this->event); | |
1595 | ||
1596 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
1597 | // Should have 4 events, every first friday of September 1997 to December 1997. | |
1598 | $this->assertCount(4, $records); | |
1599 | ||
1600 | foreach ($records as $record) { | |
1601 | // Get the first Friday of the record's month. | |
1602 | $recordmonthyear = date('F Y', $record->timestart); | |
1603 | $expecteddate = new DateTime('first Friday of ' . $recordmonthyear); | |
1604 | // Add the time of the event. | |
1605 | $expecteddate->add($offsetinterval); | |
1606 | ||
1607 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
1608 | } | |
1609 | } | |
1610 | ||
1611 | /** | |
1612 | * Every other month on the 1st and last Sunday of the month for 10 occurrences: | |
1613 | * | |
1614 | * DTSTART;TZID=US-Eastern:19970907T090000 | |
1615 | * RRULE:FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=1SU,-1SU | |
1616 | * ==> (1997 9:00 AM EDT)September 7,28 | |
1617 | * (1997 9:00 AM EST)November 2,30 | |
1618 | * (1998 9:00 AM EST)January 4,25;March 1,29 | |
1619 | * (1998 9:00 AM EDT)May 3,31 | |
1620 | */ | |
1621 | public function test_every_other_month_1st_and_last_sunday_10_count() { | |
1622 | global $DB; | |
1623 | ||
1624 | // Change our event's date to 05-09-1997, based on the example from the RFC. | |
1625 | $startdatetime = $this->change_event_startdate('19970907T090000', 'US/Eastern'); | |
1626 | $startdate = new DateTime(date('Y-m-d', $this->event->timestart)); | |
1627 | $offsetinterval = $startdatetime->diff($startdate, true); | |
1628 | ||
1629 | $rrule = 'FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=1SU,-1SU'; | |
1630 | $mang = new rrule_manager($rrule); | |
1631 | $mang->parse_rrule(); | |
1632 | $mang->create_events($this->event); | |
1633 | ||
1634 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
1635 | // Should have 10 records based on COUNT rule. | |
1636 | $this->assertCount(10, $records); | |
1637 | ||
1638 | // First occurrence is 07-09-1997 which is the first Sunday. | |
1639 | $ordinal = 'first'; | |
1640 | foreach ($records as $record) { | |
1641 | // Get date of the month's first/last Sunday. | |
1642 | $recordmonthyear = date('F Y', $record->timestart); | |
1643 | $expecteddate = new DateTime($ordinal . ' Sunday of ' . $recordmonthyear); | |
1644 | $expecteddate->add($offsetinterval); | |
1645 | ||
1646 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
1647 | if ($ordinal === 'first') { | |
1648 | $ordinal = 'last'; | |
1649 | } else { | |
1650 | $ordinal = 'first'; | |
1651 | } | |
1652 | } | |
1653 | } | |
1654 | ||
1655 | /** | |
1656 | * Monthly on the second to last Monday of the month for 6 months: | |
1657 | * | |
1658 | * DTSTART;TZID=US-Eastern:19970922T090000 | |
1659 | * RRULE:FREQ=MONTHLY;COUNT=6;BYDAY=-2MO | |
1660 | * ==> (1997 9:00 AM EDT)September 22;October 20 | |
1661 | * (1997 9:00 AM EST)November 17;December 22 | |
1662 | * (1998 9:00 AM EST)January 19;February 16 | |
1663 | */ | |
1664 | public function test_monthly_last_monday_for_6_months() { | |
1665 | global $DB; | |
1666 | ||
1667 | // Change our event's date to 05-09-1997, based on the example from the RFC. | |
1668 | $startdatetime = $this->change_event_startdate('19970922T090000', 'US/Eastern'); | |
1669 | $startdate = new DateTime($startdatetime->format('Y-m-d')); | |
1670 | $offsetinterval = $startdatetime->diff($startdate, true); | |
1671 | ||
1672 | $rrule = 'FREQ=MONTHLY;COUNT=6;BYDAY=-2MO'; | |
1673 | $mang = new rrule_manager($rrule); | |
1674 | $mang->parse_rrule(); | |
1675 | $mang->create_events($this->event); | |
1676 | ||
1677 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
1678 | // Should have 6 records based on COUNT rule. | |
1679 | $this->assertCount(6, $records); | |
1680 | ||
1681 | foreach ($records as $record) { | |
1682 | // Get date of the month's last Monday. | |
1683 | $recordmonthyear = date('F Y', $record->timestart); | |
1684 | $expecteddate = new DateTime('last Monday of ' . $recordmonthyear); | |
1685 | // Modify to get the second to the last Monday. | |
1686 | $expecteddate->modify('last Monday'); | |
1687 | // Add offset. | |
1688 | $expecteddate->add($offsetinterval); | |
1689 | ||
1690 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
1691 | } | |
1692 | } | |
1693 | ||
1694 | /** | |
1695 | * Monthly on the third to the last day of the month, forever: | |
1696 | * | |
1697 | * DTSTART;TZID=US-Eastern:19970928T090000 | |
1698 | * RRULE:FREQ=MONTHLY;BYMONTHDAY=-3 | |
1699 | * ==> (1997 9:00 AM EDT)September 28 | |
1700 | * (1997 9:00 AM EST)October 29;November 28;December 29 | |
1701 | * (1998 9:00 AM EST)January 29;February 26 | |
1702 | * ... | |
1703 | */ | |
1704 | public function test_third_to_the_last_day_of_the_month_forever() { | |
1705 | global $DB; | |
1706 | ||
1707 | // Change our event's date to 05-09-1997, based on the example from the RFC. | |
1708 | $this->change_event_startdate('19970928T090000', 'US/Eastern'); | |
1709 | ||
1710 | $rrule = 'FREQ=MONTHLY;BYMONTHDAY=-3'; | |
1711 | $mang = new rrule_manager($rrule); | |
1712 | $mang->parse_rrule(); | |
1713 | $mang->create_events($this->event); | |
1714 | ||
1715 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
1716 | ||
1717 | $untildate = new DateTime(); | |
1718 | $untildate->add(new DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y')); | |
1719 | $untiltimestamp = $untildate->getTimestamp(); | |
1720 | ||
1721 | $subinterval = new DateInterval('P2D'); | |
1722 | foreach ($records as $record) { | |
1723 | $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); | |
1724 | ||
1725 | // Get date of the third to the last day of the month. | |
1726 | $recordmonthyear = date('F Y', $record->timestart); | |
1727 | $expecteddate = new DateTime('last day of ' . $recordmonthyear); | |
1728 | // Set time to 9am. | |
1729 | $expecteddate->setTime(9, 0); | |
1730 | // Modify to get the third to the last day of the month. | |
1731 | $expecteddate->sub($subinterval); | |
1732 | ||
1733 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
1734 | } | |
1735 | } | |
1736 | ||
1737 | /** | |
1738 | * Monthly on the 2nd and 15th of the month for 10 occurrences: | |
1739 | * | |
1740 | * DTSTART;TZID=US-Eastern:19970902T090000 | |
1741 | * RRULE:FREQ=MONTHLY;COUNT=10;BYMONTHDAY=2,15 | |
1742 | * ==> (1997 9:00 AM EDT)September 2,15;October 2,15 | |
1743 | * (1997 9:00 AM EST)November 2,15;December 2,15 | |
1744 | * (1998 9:00 AM EST)January 2,15 | |
1745 | */ | |
1746 | public function test_every_2nd_and_15th_of_the_month_10_count() { | |
1747 | global $DB; | |
1748 | ||
1749 | $startdatetime = new DateTime(date('Y-m-d H:i:s', $this->event->timestart)); | |
1750 | $startdate = new DateTime($startdatetime->format('Y-m-d')); | |
1751 | $offsetinterval = $startdatetime->diff($startdate, true); | |
1752 | ||
1753 | $rrule = 'FREQ=MONTHLY;COUNT=10;BYMONTHDAY=2,15'; | |
1754 | $mang = new rrule_manager($rrule); | |
1755 | $mang->parse_rrule(); | |
1756 | $mang->create_events($this->event); | |
1757 | ||
1758 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
1759 | // Should have 10 records based on COUNT rule. | |
1760 | $this->assertCount(10, $records); | |
1761 | ||
1762 | $day = '02'; | |
1763 | foreach ($records as $record) { | |
1764 | // Get the first Friday of the record's month. | |
1765 | $recordmonthyear = date('Y-m', $record->timestart); | |
1766 | ||
1767 | // Get date of the month's last Monday. | |
1768 | $expecteddate = new DateTime("$recordmonthyear-$day"); | |
1769 | // Add offset. | |
1770 | $expecteddate->add($offsetinterval); | |
1771 | if ($day === '02') { | |
1772 | $day = '15'; | |
1773 | } else { | |
1774 | $day = '02'; | |
1775 | } | |
1776 | ||
1777 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
1778 | } | |
1779 | } | |
1780 | ||
1781 | /** | |
1782 | * Monthly on the first and last day of the month for 10 occurrences: | |
1783 | * | |
1784 | * DTSTART;TZID=US-Eastern:19970930T090000 | |
1785 | * RRULE:FREQ=MONTHLY;COUNT=10;BYMONTHDAY=1,-1 | |
1786 | * ==> (1997 9:00 AM EDT)September 30;October 1 | |
1787 | * (1997 9:00 AM EST)October 31;November 1,30;December 1,31 | |
1788 | * (1998 9:00 AM EST)January 1,31;February 1 | |
1789 | */ | |
1790 | public function test_every_first_and_last_day_of_the_month_10_count() { | |
1791 | global $DB; | |
1792 | ||
1793 | $startdatetime = $this->change_event_startdate('19970930T090000', 'US/Eastern'); | |
1794 | $startdate = new DateTime($startdatetime->format('Y-m-d')); | |
1795 | $offsetinterval = $startdatetime->diff($startdate, true); | |
1796 | ||
1797 | $rrule = 'FREQ=MONTHLY;COUNT=10;BYMONTHDAY=1,-1'; | |
1798 | $mang = new rrule_manager($rrule); | |
1799 | $mang->parse_rrule(); | |
1800 | $mang->create_events($this->event); | |
1801 | ||
1802 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
1803 | // Should have 10 records based on COUNT rule. | |
1804 | $this->assertCount(10, $records); | |
1805 | ||
1806 | // First occurrence is 30-Sep-1997. | |
1807 | $day = 'last'; | |
1808 | foreach ($records as $record) { | |
1809 | // Get the first Friday of the record's month. | |
1810 | $recordmonthyear = date('F Y', $record->timestart); | |
1811 | ||
1812 | // Get date of the month's last Monday. | |
1813 | $expecteddate = new DateTime("$day day of $recordmonthyear"); | |
1814 | // Add offset. | |
1815 | $expecteddate->add($offsetinterval); | |
1816 | ||
1817 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
1818 | ||
1819 | if ($day === 'first') { | |
1820 | $day = 'last'; | |
1821 | } else { | |
1822 | $day = 'first'; | |
1823 | } | |
1824 | } | |
1825 | } | |
1826 | ||
1827 | /** | |
1828 | * Every 18 months on the 10th thru 15th of the month for 10 occurrences: | |
1829 | * | |
1830 | * DTSTART;TZID=US-Eastern:19970910T090000 | |
1831 | * RRULE:FREQ=MONTHLY;INTERVAL=18;COUNT=10;BYMONTHDAY=10,11,12,13,14,15 | |
1832 | * ==> (1997 9:00 AM EDT)September 10,11,12,13,14,15 | |
1833 | * (1999 9:00 AM EST)March 10,11,12,13 | |
1834 | */ | |
1835 | public function test_every_18_months_days_10_to_15_10_count() { | |
1836 | global $DB; | |
1837 | ||
1838 | $startdatetime = $this->change_event_startdate('19970910T090000', 'US/Eastern'); | |
1839 | ||
1840 | $rrule = 'FREQ=MONTHLY;INTERVAL=18;COUNT=10;BYMONTHDAY=10,11,12,13,14,15'; | |
1841 | $mang = new rrule_manager($rrule); | |
1842 | $mang->parse_rrule(); | |
1843 | $mang->create_events($this->event); | |
1844 | ||
1845 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
1846 | // Should have 10 records based on COUNT rule. | |
1847 | $this->assertCount(10, $records); | |
1848 | ||
1849 | // First occurrence is 10-Sep-1997. | |
1850 | $expecteddate = clone($startdatetime); | |
1851 | $expecteddate->setTimezone(new DateTimeZone(get_user_timezone())); | |
1852 | foreach ($records as $record) { | |
1853 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
1854 | ||
1855 | // Get next expected date. | |
1856 | if ($expecteddate->format('d') == 15) { | |
1857 | // If 15th, increment by 18 months. | |
1858 | $expecteddate->add(new DateInterval('P18M')); | |
1859 | // Then go back to the 10th. | |
1860 | $expecteddate->sub(new DateInterval('P5D')); | |
1861 | } else { | |
1862 | // Otherwise, increment by 1 day. | |
1863 | $expecteddate->add(new DateInterval('P1D')); | |
1864 | } | |
1865 | } | |
1866 | } | |
1867 | ||
1868 | /** | |
1869 | * Every Tuesday, every other month: | |
1870 | * | |
1871 | * DTSTART;TZID=US-Eastern:19970902T090000 | |
1872 | * RRULE:FREQ=MONTHLY;INTERVAL=2;BYDAY=TU | |
1873 | * ==> (1997 9:00 AM EDT)September 2,9,16,23,30 | |
1874 | * (1997 9:00 AM EST)November 4,11,18,25 | |
1875 | * (1998 9:00 AM EST)January 6,13,20,27;March 3,10,17,24,31 | |
1876 | * ... | |
1877 | */ | |
1878 | public function test_every_tuesday_every_other_month_forever() { | |
1879 | global $DB; | |
1880 | ||
1881 | $rrule = 'FREQ=MONTHLY;INTERVAL=2;BYDAY=TU'; | |
1882 | $mang = new rrule_manager($rrule); | |
1883 | $mang->parse_rrule(); | |
1884 | $mang->create_events($this->event); | |
1885 | ||
1886 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
1887 | ||
1888 | $untildate = new DateTime(); | |
1889 | $untildate->add(new DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y')); | |
1890 | $untiltimestamp = $untildate->getTimestamp(); | |
1891 | ||
1892 | $expecteddate = new DateTime(date('Y-m-d H:i:s', $this->event->timestart)); | |
1893 | $nextmonth = new DateTime($expecteddate->format('Y-m-d')); | |
1894 | $offset = $expecteddate->diff($nextmonth, true); | |
1895 | $nextmonth->modify('first day of next month'); | |
1896 | foreach ($records as $record) { | |
1897 | $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); | |
1898 | ||
1899 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
1900 | ||
1901 | // Get next expected date. | |
1902 | $expecteddate->modify('next Tuesday'); | |
1903 | if ($expecteddate->getTimestamp() >= $nextmonth->getTimestamp()) { | |
1904 | // Go to the end of the month. | |
1905 | $expecteddate->modify('last day of this month'); | |
1906 | // Find the next Tuesday. | |
1907 | $expecteddate->modify('next Tuesday'); | |
1908 | ||
1909 | // Increment next month by 2 months. | |
1910 | $nextmonth->add(new DateInterval('P2M')); | |
1911 | } | |
1912 | $expecteddate->add($offset); | |
1913 | } | |
1914 | } | |
1915 | ||
1916 | /** | |
1917 | * Yearly in June and July for 10 occurrences: | |
1918 | * | |
1919 | * DTSTART;TZID=US-Eastern:19970610T090000 | |
1920 | * RRULE:FREQ=YEARLY;COUNT=10;BYMONTH=6,7 | |
1921 | * ==> (1997 9:00 AM EDT)June 10;July 10 | |
1922 | * (1998 9:00 AM EDT)June 10;July 10 | |
1923 | * (1999 9:00 AM EDT)June 10;July 10 | |
1924 | * (2000 9:00 AM EDT)June 10;July 10 | |
1925 | * (2001 9:00 AM EDT)June 10;July 10 | |
1926 | * Note: Since none of the BYDAY, BYMONTHDAY or BYYEARDAY components are specified, the day is gotten from DTSTART. | |
1927 | */ | |
1928 | public function test_yearly_in_june_july_10_count() { | |
1929 | global $DB; | |
1930 | ||
1931 | $startdatetime = $this->change_event_startdate('19970610T090000', 'US/Eastern'); | |
1932 | ||
1933 | $rrule = 'FREQ=YEARLY;COUNT=10;BYMONTH=6,7'; | |
1934 | $mang = new rrule_manager($rrule); | |
1935 | $mang->parse_rrule(); | |
1936 | $mang->create_events($this->event); | |
1937 | ||
1938 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
1939 | // Should have 10 records based on COUNT rule. | |
1940 | $this->assertCount(10, $records); | |
1941 | ||
1942 | $expecteddate = $startdatetime; | |
1943 | $expecteddate->setTimezone(new DateTimeZone(get_user_timezone())); | |
1944 | $monthinterval = new DateInterval('P1M'); | |
1945 | $yearinterval = new DateInterval('P1Y'); | |
1946 | foreach ($records as $record) { | |
1947 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
1948 | ||
1949 | // Get next expected date. | |
1950 | if ($expecteddate->format('m') == 6) { | |
1951 | // Go to the month of July. | |
1952 | $expecteddate->add($monthinterval); | |
1953 | } else { | |
1954 | // Go to the month of June next year. | |
1955 | $expecteddate->sub($monthinterval); | |
1956 | $expecteddate->add($yearinterval); | |
1957 | } | |
1958 | } | |
1959 | } | |
1960 | ||
1961 | /** | |
1962 | * Every other year on January, February, and March for 10 occurrences: | |
1963 | * | |
1964 | * DTSTART;TZID=US-Eastern:19970310T090000 | |
1965 | * RRULE:FREQ=YEARLY;INTERVAL=2;COUNT=10;BYMONTH=1,2,3 | |
1966 | * ==> (1997 9:00 AM EST)March 10 | |
1967 | * (1999 9:00 AM EST)January 10;February 10;March 10 | |
1968 | * (2001 9:00 AM EST)January 10;February 10;March 10 | |
1969 | * (2003 9:00 AM EST)January 10;February 10;March 10 | |
1970 | */ | |
1971 | public function test_every_other_year_in_june_july_10_count() { | |
1972 | global $DB; | |
1973 | ||
1974 | $startdatetime = $this->change_event_startdate('19970310T090000', 'US/Eastern'); | |
1975 | ||
1976 | $rrule = 'FREQ=YEARLY;INTERVAL=2;COUNT=10;BYMONTH=1,2,3'; | |
1977 | $mang = new rrule_manager($rrule); | |
1978 | $mang->parse_rrule(); | |
1979 | $mang->create_events($this->event); | |
1980 | ||
1981 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
1982 | // Should have 10 records based on COUNT rule. | |
1983 | $this->assertCount(10, $records); | |
1984 | ||
1985 | $expecteddate = $startdatetime; | |
1986 | $expecteddate->setTimezone(new DateTimeZone(get_user_timezone())); | |
1987 | $monthinterval = new DateInterval('P1M'); | |
1988 | $yearinterval = new DateInterval('P2Y'); | |
1989 | foreach ($records as $record) { | |
1990 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
1991 | ||
1992 | // Get next expected date. | |
1993 | if ($expecteddate->format('m') != 3) { | |
1994 | // Go to the next month. | |
1995 | $expecteddate->add($monthinterval); | |
1996 | } else { | |
1997 | // Go to the month of January next year. | |
1998 | $expecteddate->sub($monthinterval); | |
1999 | $expecteddate->sub($monthinterval); | |
2000 | $expecteddate->add($yearinterval); | |
2001 | } | |
2002 | } | |
2003 | } | |
2004 | ||
2005 | /** | |
2006 | * Every 3rd year on the 1st, 100th and 200th day for 10 occurrences: | |
2007 | * | |
2008 | * DTSTART;TZID=US-Eastern:19970101T090000 | |
2009 | * RRULE:FREQ=YEARLY;INTERVAL=3;COUNT=10;BYYEARDAY=1,100,200 | |
2010 | * ==> (1997 9:00 AM EST)January 1 | |
2011 | * (1997 9:00 AM EDT)April 10;July 19 | |
2012 | * (2000 9:00 AM EST)January 1 | |
2013 | * (2000 9:00 AM EDT)April 9;July 18 | |
2014 | * (2003 9:00 AM EST)January 1 | |
2015 | * (2003 9:00 AM EDT)April 10;July 19 | |
2016 | * (2006 9:00 AM EST)January 1 | |
2017 | */ | |
2018 | public function test_every_3_years_1st_100th_200th_days_10_count() { | |
2019 | global $DB; | |
2020 | ||
2021 | $startdatetime = $this->change_event_startdate('19970101T090000', 'US/Eastern'); | |
2022 | ||
2023 | $rrule = 'FREQ=YEARLY;INTERVAL=3;COUNT=10;BYYEARDAY=1,100,200'; | |
2024 | $mang = new rrule_manager($rrule); | |
2025 | $mang->parse_rrule(); | |
2026 | $mang->create_events($this->event); | |
2027 | ||
2028 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
2029 | // Should have 10 records based on COUNT rule. | |
2030 | $this->assertCount(10, $records); | |
2031 | ||
2032 | $expecteddate = $startdatetime; | |
2033 | $expecteddate->setTimezone(new DateTimeZone(get_user_timezone())); | |
2034 | $hundredthdayinterval = new DateInterval('P99D'); | |
2035 | $twohundredthdayinterval = new DateInterval('P100D'); | |
2036 | $yearinterval = new DateInterval('P3Y'); | |
2037 | ||
2038 | foreach ($records as $record) { | |
2039 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
2040 | ||
2041 | // Get next expected date. | |
2042 | if ($expecteddate->format('z') == 0) { // January 1. | |
2043 | $expecteddate->add($hundredthdayinterval); | |
2044 | } else if ($expecteddate->format('z') == 99) { // 100th day of the year. | |
2045 | $expecteddate->add($twohundredthdayinterval); | |
2046 | } else { // 200th day of the year. | |
2047 | $expecteddate->add($yearinterval); | |
2048 | $expecteddate->modify('January 1'); | |
2049 | } | |
2050 | } | |
2051 | } | |
2052 | ||
2053 | /** | |
2054 | * Every 20th Monday of the year, forever: | |
2055 | * | |
2056 | * DTSTART;TZID=US-Eastern:19970519T090000 | |
2057 | * RRULE:FREQ=YEARLY;BYDAY=20MO | |
2058 | * ==> (1997 9:00 AM EDT)May 19 | |
2059 | * (1998 9:00 AM EDT)May 18 | |
2060 | * (1999 9:00 AM EDT)May 17 | |
2061 | * ... | |
2062 | */ | |
2063 | public function test_yearly_every_20th_monday_forever() { | |
2064 | global $DB; | |
2065 | ||
2066 | // Change our event's date to 19-05-1997, based on the example from the RFC. | |
2067 | $startdatetime = $this->change_event_startdate('19970519T090000', 'US/Eastern'); | |
2068 | ||
2069 | $startdate = new DateTime($startdatetime->format('Y-m-d')); | |
2070 | ||
2071 | $offset = $startdatetime->diff($startdate, true); | |
2072 | ||
2073 | $interval = new DateInterval('P1Y'); | |
2074 | ||
2075 | $rrule = 'FREQ=YEARLY;BYDAY=20MO'; | |
2076 | $mang = new rrule_manager($rrule); | |
2077 | $mang->parse_rrule(); | |
2078 | $mang->create_events($this->event); | |
2079 | ||
2080 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
2081 | ||
2082 | $untildate = new DateTime(); | |
2083 | $untildate->add(new DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y')); | |
2084 | $untiltimestamp = $untildate->getTimestamp(); | |
2085 | ||
2086 | $expecteddate = $startdatetime; | |
2087 | $expecteddate->setTimezone(new DateTimeZone(get_user_timezone())); | |
2088 | foreach ($records as $record) { | |
2089 | $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); | |
2090 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
2091 | ||
2092 | // Go to next period. | |
2093 | $expecteddate->modify('January 1'); | |
2094 | $expecteddate->add($interval); | |
2095 | $expecteddate->modify("+20 Monday"); | |
2096 | $expecteddate->add($offset); | |
2097 | } | |
2098 | } | |
2099 | ||
2100 | /** | |
2101 | * Monday of week number 20 (where the default start of the week is Monday), forever: | |
2102 | * | |
2103 | * DTSTART;TZID=US-Eastern:19970512T090000 | |
2104 | * RRULE:FREQ=YEARLY;BYWEEKNO=20;BYDAY=MO | |
2105 | * ==> (1997 9:00 AM EDT)May 12 | |
2106 | * (1998 9:00 AM EDT)May 11 | |
2107 | * (1999 9:00 AM EDT)May 17 | |
2108 | * ... | |
2109 | */ | |
2110 | public function test_yearly_byweekno_forever() { | |
2111 | global $DB; | |
2112 | ||
2113 | // Change our event's date to 12-05-1997, based on the example from the RFC. | |
2114 | $startdatetime = $this->change_event_startdate('19970512T090000', 'US/Eastern'); | |
2115 | ||
2116 | $startdate = clone($startdatetime); | |
2117 | $startdate->modify($startdate->format('Y-m-d')); | |
2118 | ||
2119 | $offset = $startdatetime->diff($startdate, true); | |
2120 | ||
2121 | $interval = new DateInterval('P1Y'); | |
2122 | ||
2123 | $rrule = 'FREQ=YEARLY;BYWEEKNO=20;BYDAY=MO'; | |
2124 | $mang = new rrule_manager($rrule); | |
2125 | $mang->parse_rrule(); | |
2126 | $mang->create_events($this->event); | |
2127 | ||
2128 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
2129 | ||
2130 | $untildate = new DateTime(); | |
2131 | $untildate->add(new DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y')); | |
2132 | $untiltimestamp = $untildate->getTimestamp(); | |
2133 | ||
2134 | $expecteddate = new DateTime(date('Y-m-d H:i:s', $startdatetime->getTimestamp())); | |
2135 | foreach ($records as $record) { | |
2136 | $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); | |
2137 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
2138 | ||
2139 | // Go to next period. | |
2140 | $expecteddate->add($interval); | |
2141 | $expecteddate->setISODate($expecteddate->format('Y'), 20); | |
2142 | $expecteddate->add($offset); | |
2143 | } | |
2144 | } | |
2145 | ||
2146 | /** | |
2147 | * Every Thursday in March, forever: | |
2148 | * | |
2149 | * DTSTART;TZID=US-Eastern:19970313T090000 | |
2150 | * RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=TH | |
2151 | * ==> (1997 9:00 AM EST)March 13,20,27 | |
2152 | * (1998 9:00 AM EST)March 5,12,19,26 | |
2153 | * (1999 9:00 AM EST)March 4,11,18,25 | |
2154 | * ... | |
2155 | */ | |
2156 | public function test_every_thursday_in_march_forever() { | |
2157 | global $DB; | |
2158 | ||
2159 | // Change our event's date to 12-05-1997, based on the example from the RFC. | |
2160 | $startdatetime = $this->change_event_startdate('19970313T090000', 'US/Eastern'); | |
2161 | ||
2162 | $interval = new DateInterval('P1Y'); | |
2163 | ||
2164 | $rrule = 'FREQ=YEARLY;BYMONTH=3;BYDAY=TH'; | |
2165 | $mang = new rrule_manager($rrule); | |
2166 | $mang->parse_rrule(); | |
2167 | $mang->create_events($this->event); | |
2168 | ||
2169 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
2170 | ||
2171 | $untildate = new DateTime(); | |
2172 | $untildate->add(new DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y')); | |
2173 | $untiltimestamp = $untildate->getTimestamp(); | |
2174 | ||
2175 | $expecteddate = $startdatetime; | |
2176 | $startdate = new DateTime($startdatetime->format('Y-m-d')); | |
2177 | $offsetinterval = $startdatetime->diff($startdate, true); | |
2178 | $expecteddate->setTimezone(new DateTimeZone(get_user_timezone())); | |
2179 | $april1st = new DateTime('1997-04-01'); | |
2180 | foreach ($records as $record) { | |
2181 | $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); | |
2182 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
2183 | ||
2184 | // Go to next period. | |
2185 | $expecteddate->modify('next Thursday'); | |
2186 | if ($expecteddate->getTimestamp() >= $april1st->getTimestamp()) { | |
2187 | // Reset to 1st of March. | |
2188 | $expecteddate->modify('first day of March'); | |
2189 | // Go to next year. | |
2190 | $expecteddate->add($interval); | |
2191 | if ($expecteddate->format('l') !== rrule_manager::DAY_THURSDAY) { | |
2192 | $expecteddate->modify('next Thursday'); | |
2193 | } | |
2194 | // Increment to next year's April 1st. | |
2195 | $april1st->add($interval); | |
2196 | } | |
2197 | $expecteddate->add($offsetinterval); | |
2198 | } | |
2199 | } | |
2200 | ||
2201 | /** | |
2202 | * Every Thursday, but only during June, July, and August, forever: | |
2203 | * | |
2204 | * DTSTART;TZID=US-Eastern:19970605T090000 | |
2205 | * RRULE:FREQ=YEARLY;BYDAY=TH;BYMONTH=6,7,8 | |
2206 | * ==> (1997 9:00 AM EDT)June 5,12,19,26;July 3,10,17,24,31;August 7,14,21,28 | |
2207 | * (1998 9:00 AM EDT)June 4,11,18,25;July 2,9,16,23,30;August 6,13,20,27 | |
2208 | * (1999 9:00 AM EDT)June 3,10,17,24;July 1,8,15,22,29;August 5,12,19,26 | |
2209 | * ... | |
2210 | */ | |
2211 | public function test_every_thursday_june_july_august_forever() { | |
2212 | global $DB; | |
2213 | ||
2214 | // Change our event's date to 05-06-1997, based on the example from the RFC. | |
2215 | $startdatetime = $this->change_event_startdate('19970605T090000', 'US/Eastern'); | |
2216 | ||
2217 | $startdate = new DateTime($startdatetime->format('Y-m-d')); | |
2218 | ||
2219 | $offset = $startdatetime->diff($startdate, true); | |
2220 | ||
2221 | $interval = new DateInterval('P1Y'); | |
2222 | ||
2223 | $rrule = 'FREQ=YEARLY;BYDAY=TH;BYMONTH=6,7,8'; | |
2224 | $mang = new rrule_manager($rrule); | |
2225 | $mang->parse_rrule(); | |
2226 | $mang->create_events($this->event); | |
2227 | ||
2228 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
2229 | ||
2230 | $untildate = new DateTime(); | |
2231 | $untildate->add(new DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y')); | |
2232 | $untiltimestamp = $untildate->getTimestamp(); | |
2233 | ||
2234 | $expecteddate = new DateTime(date('Y-m-d H:i:s', $startdatetime->getTimestamp())); | |
2235 | $september1st = new DateTime('1997-09-01'); | |
2236 | foreach ($records as $record) { | |
2237 | $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); | |
2238 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
2239 | ||
2240 | // Go to next period. | |
2241 | $expecteddate->modify('next Thursday'); | |
2242 | if ($expecteddate->getTimestamp() >= $september1st->getTimestamp()) { | |
2243 | $expecteddate->add($interval); | |
2244 | $expecteddate->modify('June 1'); | |
2245 | if ($expecteddate->format('l') !== rrule_manager::DAY_THURSDAY) { | |
2246 | $expecteddate->modify('next Thursday'); | |
2247 | } | |
2248 | $september1st->add($interval); | |
2249 | } | |
2250 | $expecteddate->add($offset); | |
2251 | } | |
2252 | } | |
2253 | ||
2254 | /** | |
2255 | * Every Friday the 13th, forever: | |
2256 | * | |
2257 | * DTSTART;TZID=US-Eastern:19970902T090000 | |
2258 | * EXDATE;TZID=US-Eastern:19970902T090000 | |
2259 | * RRULE:FREQ=MONTHLY;BYDAY=FR;BYMONTHDAY=13 | |
2260 | * ==> (1998 9:00 AM EST)February 13;March 13;November 13 | |
2261 | * (1999 9:00 AM EDT)August 13 | |
2262 | * (2000 9:00 AM EDT)October 13 | |
2263 | */ | |
2264 | public function test_friday_the_thirteenth_forever() { | |
2265 | global $DB; | |
2266 | ||
2267 | $rrule = 'FREQ=MONTHLY;BYDAY=FR;BYMONTHDAY=13'; | |
2268 | $mang = new rrule_manager($rrule); | |
2269 | $mang->parse_rrule(); | |
2270 | $mang->create_events($this->event); | |
2271 | ||
2272 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
2273 | ||
2274 | $untildate = new DateTime(); | |
2275 | $untildate->add(new DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y')); | |
2276 | $untiltimestamp = $untildate->getTimestamp(); | |
2277 | ||
2278 | foreach ($records as $record) { | |
2279 | $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); | |
2280 | // Assert that the day of the month and the day correspond to Friday the 13th. | |
2281 | $this->assertEquals('Friday 13', date('l d', $record->timestart)); | |
2282 | } | |
2283 | } | |
2284 | ||
2285 | /** | |
2286 | * The first Saturday that follows the first Sunday of the month, forever: | |
2287 | * | |
2288 | * DTSTART;TZID=US-Eastern:19970913T090000 | |
2289 | * RRULE:FREQ=MONTHLY;BYDAY=SA;BYMONTHDAY=7,8,9,10,11,12,13 | |
2290 | * ==> (1997 9:00 AM EDT)September 13;October 11 | |
2291 | * (1997 9:00 AM EST)November 8;December 13 | |
2292 | * (1998 9:00 AM EST)January 10;February 7;March 7 | |
2293 | * (1998 9:00 AM EDT)April 11;May 9;June 13... | |
2294 | */ | |
2295 | public function test_first_saturday_following_first_sunday_forever() { | |
2296 | global $DB; | |
2297 | ||
2298 | $startdatetime = $this->change_event_startdate('19970913T090000', 'US/Eastern'); | |
2299 | $startdate = new DateTime($startdatetime->format('Y-m-d')); | |
2300 | $offset = $startdatetime->diff($startdate, true); | |
2301 | ||
2302 | $rrule = 'FREQ=MONTHLY;BYDAY=SA;BYMONTHDAY=7,8,9,10,11,12,13'; | |
2303 | $mang = new rrule_manager($rrule); | |
2304 | $mang->parse_rrule(); | |
2305 | $mang->create_events($this->event); | |
2306 | ||
2307 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
2308 | ||
2309 | $untildate = new DateTime(); | |
2310 | $untildate->add(new DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y')); | |
2311 | $untiltimestamp = $untildate->getTimestamp(); | |
2312 | $bymonthdays = [7, 8, 9, 10, 11, 12, 13]; | |
2313 | foreach ($records as $record) { | |
2314 | $recordmonthyear = date('F Y', $record->timestart); | |
2315 | $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); | |
2316 | ||
2317 | // Get first Saturday after the first Sunday of the month. | |
2318 | $expecteddate = new DateTime('first Sunday of ' . $recordmonthyear); | |
2319 | $expecteddate->modify('next Saturday'); | |
2320 | $expecteddate->add($offset); | |
2321 | ||
2322 | // Assert the record's date corresponds to the first Saturday of the month. | |
2323 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
2324 | ||
2325 | // Assert that the record is either the 7th, 8th, 9th, ... 13th day of the month. | |
2326 | $this->assertContains(date('j', $record->timestart), $bymonthdays); | |
2327 | } | |
2328 | } | |
2329 | ||
2330 | /** | |
2331 | * Every four years, the first Tuesday after a Monday in November, forever (U.S. Presidential Election day): | |
2332 | * | |
2333 | * DTSTART;TZID=US-Eastern:19961105T090000 | |
2334 | * RRULE:FREQ=YEARLY;INTERVAL=4;BYMONTH=11;BYDAY=TU;BYMONTHDAY=2,3,4,5,6,7,8 | |
2335 | * ==> (1996 9:00 AM EST)November 5 | |
2336 | * (2000 9:00 AM EST)November 7 | |
2337 | * (2004 9:00 AM EST)November 2 | |
2338 | * ... | |
2339 | */ | |
2340 | public function test_every_us_presidential_election_forever() { | |
2341 | global $DB; | |
2342 | ||
2343 | $startdatetime = $this->change_event_startdate('19961105T090000', 'US/Eastern'); | |
2344 | $startdate = new DateTime($startdatetime->format('Y-m-d')); | |
2345 | $offset = $startdatetime->diff($startdate, true); | |
2346 | ||
2347 | $rrule = 'FREQ=YEARLY;INTERVAL=4;BYMONTH=11;BYDAY=TU;BYMONTHDAY=2,3,4,5,6,7,8'; | |
2348 | $mang = new rrule_manager($rrule); | |
2349 | $mang->parse_rrule(); | |
2350 | $mang->create_events($this->event); | |
2351 | ||
2352 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
2353 | ||
2354 | $untildate = new DateTime(); | |
2355 | $untildate->add(new DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y')); | |
2356 | $untiltimestamp = $untildate->getTimestamp(); | |
2357 | $bymonthdays = [2, 3, 4, 5, 6, 7, 8]; | |
2358 | foreach ($records as $record) { | |
2359 | $recordmonthyear = date('F Y', $record->timestart); | |
2360 | $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); | |
2361 | ||
2362 | // Get first Saturday after the first Sunday of the month. | |
2363 | $expecteddate = new DateTime('first Monday of ' . $recordmonthyear); | |
2364 | $expecteddate->modify('next Tuesday'); | |
2365 | $expecteddate->add($offset); | |
2366 | ||
2367 | // Assert the record's date corresponds to the first Saturday of the month. | |
2368 | $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); | |
2369 | ||
2370 | // Assert that the record is either the 2nd, 3rd, 4th ... 8th day of the month. | |
2371 | $this->assertContains(date('j', $record->timestart), $bymonthdays); | |
2372 | } | |
2373 | } | |
2374 | ||
2375 | /** | |
2376 | * The 3rd instance into the month of one of Tuesday, Wednesday or Thursday, for the next 3 months: | |
2377 | * | |
2378 | * DTSTART;TZID=US-Eastern:19970904T090000 | |
2379 | * RRULE:FREQ=MONTHLY;COUNT=3;BYDAY=TU,WE,TH;BYSETPOS=3 | |
2380 | * ==> (1997 9:00 AM EDT)September 4;October 7 | |
2381 | * (1997 9:00 AM EST)November 6 | |
2382 | */ | |
2383 | public function test_monthly_bysetpos_3_count() { | |
2384 | global $DB; | |
2385 | ||
2386 | $this->change_event_startdate('19970904T090000', 'US/Eastern'); | |
2387 | ||
2388 | $rrule = 'FREQ=MONTHLY;COUNT=3;BYDAY=TU,WE,TH;BYSETPOS=3'; | |
2389 | $mang = new rrule_manager($rrule); | |
2390 | $mang->parse_rrule(); | |
2391 | $mang->create_events($this->event); | |
2392 | ||
2393 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
2394 | $this->assertCount(3, $records); | |
2395 | ||
2396 | $expecteddates = [ | |
2397 | (new DateTime('1997-09-04 09:00:00 EDT'))->getTimestamp(), | |
2398 | (new DateTime('1997-10-07 09:00:00 EDT'))->getTimestamp(), | |
2399 | (new DateTime('1997-11-06 09:00:00 EST'))->getTimestamp() | |
2400 | ]; | |
2401 | foreach ($records as $record) { | |
2402 | $this->assertContains($record->timestart, $expecteddates, date('Y-m-d H:i:s', $record->timestart) . ' is not found.'); | |
2403 | } | |
2404 | } | |
2405 | ||
2406 | /** | |
2407 | * The 2nd to last weekday of the month: | |
2408 | * | |
2409 | * DTSTART;TZID=US-Eastern:19970929T090000 | |
2410 | * RRULE:FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-2 | |
2411 | * ==> (1997 9:00 AM EDT)September 29 | |
2412 | * (1997 9:00 AM EST)October 30;November 27;December 30 | |
2413 | * (1998 9:00 AM EST)January 29;February 26;March 30 | |
2414 | * ... | |
2415 | */ | |
2416 | public function test_second_to_the_last_weekday_of_the_month_forever() { | |
2417 | global $DB; | |
2418 | ||
2419 | $this->change_event_startdate('19970929T090000', 'US/Eastern'); | |
2420 | ||
2421 | $rrule = 'FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-2'; | |
2422 | $mang = new rrule_manager($rrule); | |
2423 | $mang->parse_rrule(); | |
2424 | $mang->create_events($this->event); | |
2425 | ||
2426 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
2427 | ||
2428 | $expecteddates = [ | |
2429 | (new DateTime('1997-09-29 09:00:00 EDT'))->getTimestamp(), | |
2430 | (new DateTime('1997-10-30 09:00:00 EST'))->getTimestamp(), | |
2431 | (new DateTime('1997-11-27 09:00:00 EST'))->getTimestamp(), | |
2432 | (new DateTime('1997-12-30 09:00:00 EST'))->getTimestamp(), | |
2433 | (new DateTime('1998-01-29 09:00:00 EST'))->getTimestamp(), | |
2434 | (new DateTime('1998-02-26 09:00:00 EST'))->getTimestamp(), | |
2435 | (new DateTime('1998-03-30 09:00:00 EST'))->getTimestamp(), | |
2436 | ]; | |
2437 | ||
2438 | $untildate = new DateTime(); | |
2439 | $untildate->add(new DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y')); | |
2440 | $untiltimestamp = $untildate->getTimestamp(); | |
2441 | ||
2442 | $i = 0; | |
2443 | foreach ($records as $record) { | |
2444 | $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); | |
2445 | ||
2446 | // Confirm that the first 7 records correspond to the expected dates listed above. | |
2447 | if ($i < 7) { | |
2448 | $this->assertEquals($expecteddates[$i], $record->timestart); | |
2449 | $i++; | |
2450 | } | |
2451 | } | |
2452 | } | |
2453 | ||
2454 | /** | |
2455 | * Every 3 hours from 9:00 AM to 5:00 PM on a specific day: | |
2456 | * | |
2457 | * DTSTART;TZID=US-Eastern:19970902T090000 | |
2458 | * RRULE:FREQ=HOURLY;INTERVAL=3;UNTIL=19970902T210000Z | |
2459 | * ==> (September 2, 1997 EDT)09:00,12:00,15:00 | |
2460 | */ | |
2461 | public function test_every_3hours_9am_to_5pm() { | |
2462 | global $DB; | |
2463 | ||
2464 | $rrule = 'FREQ=HOURLY;INTERVAL=3;UNTIL=19970902T210000Z'; | |
2465 | $mang = new rrule_manager($rrule); | |
2466 | $mang->parse_rrule(); | |
2467 | $mang->create_events($this->event); | |
2468 | ||
2469 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
2470 | $this->assertCount(3, $records); | |
2471 | ||
2472 | $expecteddates = [ | |
2473 | (new DateTime('1997-09-02 09:00:00 EDT'))->getTimestamp(), | |
2474 | (new DateTime('1997-09-02 12:00:00 EDT'))->getTimestamp(), | |
2475 | (new DateTime('1997-09-02 15:00:00 EDT'))->getTimestamp(), | |
2476 | ]; | |
2477 | foreach ($records as $record) { | |
2478 | $this->assertContains($record->timestart, $expecteddates, date('Y-m-d H:i:s', $record->timestart) . ' is not found.'); | |
2479 | } | |
2480 | } | |
2481 | ||
2482 | /** | |
2483 | * Every 15 minutes for 6 occurrences: | |
2484 | * | |
2485 | * DTSTART;TZID=US-Eastern:19970902T090000 | |
2486 | * RRULE:FREQ=MINUTELY;INTERVAL=15;COUNT=6 | |
2487 | * ==> (September 2, 1997 EDT)09:00,09:15,09:30,09:45,10:00,10:15 | |
2488 | */ | |
2489 | public function test_every_15minutes_6_count() { | |
2490 | global $DB; | |
2491 | ||
2492 | $rrule = 'FREQ=MINUTELY;INTERVAL=15;COUNT=6'; | |
2493 | $mang = new rrule_manager($rrule); | |
2494 | $mang->parse_rrule(); | |
2495 | $mang->create_events($this->event); | |
2496 | ||
2497 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
2498 | $this->assertCount(6, $records); | |
2499 | ||
2500 | $expecteddates = [ | |
2501 | (new DateTime('1997-09-02 09:00:00 EDT'))->getTimestamp(), | |
2502 | (new DateTime('1997-09-02 09:15:00 EDT'))->getTimestamp(), | |
2503 | (new DateTime('1997-09-02 09:30:00 EDT'))->getTimestamp(), | |
2504 | (new DateTime('1997-09-02 09:45:00 EDT'))->getTimestamp(), | |
2505 | (new DateTime('1997-09-02 10:00:00 EDT'))->getTimestamp(), | |
2506 | (new DateTime('1997-09-02 10:15:00 EDT'))->getTimestamp(), | |
2507 | ]; | |
2508 | foreach ($records as $record) { | |
2509 | $this->assertContains($record->timestart, $expecteddates, date('Y-m-d H:i:s', $record->timestart) . ' is not found.'); | |
2510 | } | |
2511 | } | |
2512 | ||
2513 | /** | |
2514 | * Every hour and a half for 4 occurrences: | |
2515 | * | |
2516 | * DTSTART;TZID=US-Eastern:19970902T090000 | |
2517 | * RRULE:FREQ=MINUTELY;INTERVAL=90;COUNT=4 | |
2518 | * ==> (September 2, 1997 EDT)09:00,10:30;12:00;13:30 | |
2519 | */ | |
2520 | public function test_every_90minutes_4_count() { | |
2521 | global $DB; | |
2522 | ||
2523 | $rrule = 'FREQ=MINUTELY;INTERVAL=90;COUNT=4'; | |
2524 | $mang = new rrule_manager($rrule); | |
2525 | $mang->parse_rrule(); | |
2526 | $mang->create_events($this->event); | |
2527 | ||
2528 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
2529 | $this->assertCount(4, $records); | |
2530 | ||
2531 | $expecteddates = [ | |
2532 | (new DateTime('1997-09-02 09:00:00 EDT'))->getTimestamp(), | |
2533 | (new DateTime('1997-09-02 10:30:00 EDT'))->getTimestamp(), | |
2534 | (new DateTime('1997-09-02 12:00:00 EDT'))->getTimestamp(), | |
2535 | (new DateTime('1997-09-02 13:30:00 EDT'))->getTimestamp(), | |
2536 | ]; | |
2537 | foreach ($records as $record) { | |
2538 | $this->assertContains($record->timestart, $expecteddates, date('Y-m-d H:i:s', $record->timestart) . ' is not found.'); | |
2539 | } | |
2540 | } | |
2541 | ||
2542 | /** | |
2543 | * Every 20 minutes from 9:00 AM to 4:40 PM every day for 100 times: | |
2544 | * | |
2545 | * (Original RFC example is set to everyday forever, but that will just take a lot of time for the test, | |
2546 | * so just limit the count to 50). | |
2547 | * | |
2548 | * DTSTART;TZID=US-Eastern:19970902T090000 | |
2549 | * RRULE:FREQ=DAILY;BYHOUR=9,10,11,12,13,14,15,16;BYMINUTE=0,20,40;COUNT=50 | |
2550 | * ==> (September 2, 1997 EDT)9:00,9:20,9:40,10:00,10:20,...16:00,16:20,16:40 | |
2551 | * (September 3, 1997 EDT)9:00,9:20,9:40,10:00,10:20,...16:00,16:20,16:40 | |
2552 | * ... | |
2553 | */ | |
2554 | public function test_every_20minutes_daily_byhour_byminute_50_count() { | |
2555 | global $DB; | |
2556 | ||
2557 | $rrule = 'FREQ=DAILY;BYHOUR=9,10,11,12,13,14,15,16;BYMINUTE=0,20,40;COUNT=50'; | |
2558 | $mang = new rrule_manager($rrule); | |
2559 | $mang->parse_rrule(); | |
2560 | $mang->create_events($this->event); | |
2561 | ||
2562 | $byminuteinterval = new DateInterval('PT20M'); | |
2563 | $bydayinterval = new DateInterval('P1D'); | |
2564 | $date = new DateTime('1997-09-02 09:00:00 EDT'); | |
2565 | $expecteddates = []; | |
2566 | $count = 50; | |
2567 | for ($i = 0; $i < $count; $i++) { | |
2568 | $expecteddates[] = $date->getTimestamp(); | |
2569 | $date->add($byminuteinterval); | |
2570 | if ($date->format('H') > 16) { | |
2571 | // Go to next day. | |
2572 | $date->add($bydayinterval); | |
2573 | // Reset time to 9am. | |
2574 | $date->setTime(9, 0); | |
2575 | } | |
2576 | } | |
2577 | ||
2578 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
2579 | $this->assertCount($count, $records); | |
2580 | ||
2581 | foreach ($records as $record) { | |
2582 | $this->assertContains($record->timestart, $expecteddates, date('Y-m-d H:i:s', $record->timestart) . ' is not found.'); | |
2583 | } | |
2584 | } | |
2585 | ||
2586 | /** | |
2587 | * Every 20 minutes from 9:00 AM to 4:40 PM every day for 100 times: | |
2588 | * | |
2589 | * (Original RFC example is set to everyday forever, but that will just take a lot of time for the test, | |
2590 | * so just limit the count to 50). | |
2591 | * | |
2592 | * DTSTART;TZID=US-Eastern:19970902T090000 | |
2593 | * RRULE:FREQ=MINUTELY;INTERVAL=20;BYHOUR=9,10,11,12,13,14,15,16;COUNT=50 | |
2594 | * ==> (September 2, 1997 EDT)9:00,9:20,9:40,10:00,10:20,...16:00,16:20,16:40 | |
2595 | * (September 3, 1997 EDT)9:00,9:20,9:40,10:00,10:20,...16:00,16:20,16:40 | |
2596 | * ... | |
2597 | */ | |
2598 | public function test_every_20minutes_minutely_byhour_50_count() { | |
2599 | global $DB; | |
2600 | ||
2601 | $rrule = 'FREQ=MINUTELY;INTERVAL=20;BYHOUR=9,10,11,12,13,14,15,16;COUNT=50'; | |
2602 | $mang = new rrule_manager($rrule); | |
2603 | $mang->parse_rrule(); | |
2604 | $mang->create_events($this->event); | |
2605 | ||
2606 | $byminuteinterval = new DateInterval('PT20M'); | |
2607 | $bydayinterval = new DateInterval('P1D'); | |
2608 | $date = new DateTime('1997-09-02 09:00:00'); | |
2609 | $expecteddates = []; | |
2610 | $count = 50; | |
2611 | for ($i = 0; $i < $count; $i++) { | |
2612 | $expecteddates[] = $date->getTimestamp(); | |
2613 | $date->add($byminuteinterval); | |
2614 | if ($date->format('H') > 16) { | |
2615 | // Go to next day. | |
2616 | $date->add($bydayinterval); | |
2617 | // Reset time to 9am. | |
2618 | $date->setTime(9, 0); | |
2619 | } | |
2620 | } | |
2621 | ||
2622 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
2623 | $this->assertCount($count, $records); | |
2624 | ||
2625 | foreach ($records as $record) { | |
2626 | $this->assertContains($record->timestart, $expecteddates, date('Y-m-d H:i:s', $record->timestart) . ' is not found.'); | |
2627 | } | |
2628 | } | |
2629 | ||
2630 | /** | |
2631 | * An example where the days generated makes a difference because of WKST: | |
2632 | * | |
2633 | * DTSTART;TZID=US-Eastern:19970805T090000 | |
2634 | * RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=MO | |
2635 | * ==> (1997 EDT)Aug 5,10,19,24 | |
2636 | */ | |
2637 | public function test_weekly_byday_with_wkst_mo() { | |
2638 | global $DB; | |
2639 | ||
2640 | $this->change_event_startdate('19970805T090000', 'US/Eastern'); | |
2641 | ||
2642 | $rrule = 'FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=MO'; | |
2643 | $mang = new rrule_manager($rrule); | |
2644 | $mang->parse_rrule(); | |
2645 | $mang->create_events($this->event); | |
2646 | ||
2647 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
2648 | $this->assertCount(4, $records); | |
2649 | ||
2650 | $expecteddates = [ | |
2651 | (new DateTime('1997-08-05 09:00:00 EDT'))->getTimestamp(), | |
2652 | (new DateTime('1997-08-10 09:00:00 EDT'))->getTimestamp(), | |
2653 | (new DateTime('1997-08-19 09:00:00 EDT'))->getTimestamp(), | |
2654 | (new DateTime('1997-08-24 09:00:00 EDT'))->getTimestamp(), | |
2655 | ]; | |
2656 | foreach ($records as $record) { | |
2657 | $this->assertContains($record->timestart, $expecteddates, date('Y-m-d H:i:s', $record->timestart) . ' is not found.'); | |
2658 | } | |
2659 | } | |
2660 | ||
2661 | /** | |
2662 | * An example where the days generated makes a difference because of WKST: | |
2663 | * Changing only WKST from MO to SU, yields different results... | |
2664 | * | |
2665 | * DTSTART;TZID=US-Eastern:19970805T090000 | |
2666 | * RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=SU | |
2667 | * ==> (1997 EDT)August 5,17,19,31 | |
2668 | */ | |
2669 | public function test_weekly_byday_with_wkst_su() { | |
2670 | global $DB; | |
2671 | ||
2672 | $this->change_event_startdate('19970805T090000', 'US/Eastern'); | |
2673 | ||
2674 | $rrule = 'FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=SU'; | |
2675 | $mang = new rrule_manager($rrule); | |
2676 | $mang->parse_rrule(); | |
2677 | $mang->create_events($this->event); | |
2678 | ||
2679 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
2680 | $this->assertCount(4, $records); | |
2681 | ||
2682 | $expecteddates = [ | |
2683 | (new DateTime('1997-08-05 09:00:00 EDT'))->getTimestamp(), | |
2684 | (new DateTime('1997-08-17 09:00:00 EDT'))->getTimestamp(), | |
2685 | (new DateTime('1997-08-19 09:00:00 EDT'))->getTimestamp(), | |
2686 | (new DateTime('1997-08-31 09:00:00 EDT'))->getTimestamp(), | |
2687 | ]; | |
2688 | ||
2689 | foreach ($records as $record) { | |
2690 | $this->assertContains($record->timestart, $expecteddates, date('Y-m-d H:i:s', $record->timestart) . ' is not found.'); | |
2691 | } | |
2692 | } | |
2693 | ||
6d5661a9 JP |
2694 | /* |
2695 | * Other edge case tests. | |
2696 | */ | |
2697 | ||
2698 | /** | |
2699 | * Tests for MONTHLY RRULE with BYMONTHDAY set to 31. | |
2700 | * Should not include February, April, June, September and November. | |
2701 | */ | |
2702 | public function test_monthly_bymonthday_31() { | |
2703 | global $DB; | |
2704 | ||
2705 | $rrule = 'FREQ=MONTHLY;BYMONTHDAY=31;COUNT=20'; | |
2706 | $mang = new rrule_manager($rrule); | |
2707 | $mang->parse_rrule(); | |
2708 | $mang->create_events($this->event); | |
2709 | ||
2710 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
2711 | $this->assertCount(20, $records); | |
2712 | ||
2713 | $non31months = ['February', 'April', 'June', 'September', 'November']; | |
2714 | ||
2715 | foreach ($records as $record) { | |
2716 | $month = date('F', $record->timestart); | |
2717 | $this->assertNotContains($month, $non31months); | |
2718 | } | |
2719 | } | |
2720 | ||
2721 | /** | |
2722 | * Tests for the last day in February. (Leap year test) | |
2723 | */ | |
2724 | public function test_yearly_on_the_last_day_of_february() { | |
2725 | global $DB; | |
2726 | ||
2727 | $rrule = 'FREQ=YEARLY;BYMONTH=2;BYMONTHDAY=-1;COUNT=30'; | |
2728 | $mang = new rrule_manager($rrule); | |
2729 | $mang->parse_rrule(); | |
2730 | $mang->create_events($this->event); | |
2731 | ||
2732 | $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); | |
2733 | $this->assertCount(30, $records); | |
2734 | ||
2735 | foreach ($records as $record) { | |
2736 | $date = new DateTime(date('Y-m-d H:i:s', $record->timestart)); | |
2737 | $year = $date->format('Y'); | |
2738 | $day = $date->format('d'); | |
2739 | if ($year % 4 == 0) { | |
2740 | $this->assertEquals(29, $day); | |
2741 | } else { | |
2742 | $this->assertEquals(28, $day); | |
2743 | } | |
2744 | } | |
2745 | } | |
2746 | ||
8ddc6567 JP |
2747 | /** |
2748 | * Change the event's timestart (DTSTART) based on the test's needs. | |
2749 | * | |
2750 | * @param string $datestr The date string. In YYYYmmddThhiiss format. e.g. 19990902T090000. | |
2751 | * @param string $timezonestr A valid timezone string. e.g. 'US/Eastern'. | |
2752 | * @return bool|DateTime | |
2753 | */ | |
2754 | protected function change_event_startdate($datestr, $timezonestr) { | |
2755 | $timezone = new DateTimeZone($timezonestr); | |
2756 | $newdatetime = DateTime::createFromFormat('Ymd\THis', $datestr, $timezone); | |
2757 | ||
2758 | // Update the start date of the parent event. | |
2759 | $calevent = calendar_event::load($this->event->id); | |
2760 | $updatedata = (object)[ | |
2761 | 'timestart' => $newdatetime->getTimestamp(), | |
2762 | 'repeatid' => $this->event->id | |
2763 | ]; | |
2764 | $calevent->update($updatedata, false); | |
2765 | $this->event->timestart = $calevent->timestart; | |
2766 | ||
2767 | return $newdatetime; | |
2768 | } | |
2769 | } |