Merge branch '2015.11-develop' into 2016.11
[tine20] / tests / tine20 / Calendar / RruleTests.php
1 <?php
2 /**
3  * Tine 2.0 - http://www.tine20.org
4  * 
5  * @package     Calendar
6  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
7  * @copyright   Copyright (c) 2009 Metaways Infosystems GmbH (http://www.metaways.de)
8  * @author      Cornelius Weiss <c.weiss@metaways.de>
9  */
10
11 /**
12  * Test helper
13  */
14 require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'TestHelper.php';
15
16 /**
17  * Test class for Calendar_Model_Rrule
18  * 
19  * @package     Calendar
20  */
21 class Calendar_RruleTests extends PHPUnit_Framework_TestCase
22 {
23     
24     public function setUp()
25     {
26         
27     }
28     
29     public function testSetFromString()
30     {
31         $rrule = new Calendar_Model_Rrule(array());
32         $rrule->setFromString("FREQ=WEEKLY;INTERVAL=3;UNTIL=2009-06-05 00:00:00;WKST=SU;BYDAY=TU,TH");
33         
34         $this->assertEquals(Calendar_Model_Rrule::FREQ_WEEKLY, $rrule->freq);
35         $this->assertTrue($rrule->until->equals(new Tinebase_DateTime('2009-06-05 00:00:00')));
36     }
37     
38     public function testToString()
39     {
40         $rruleString = "FREQ=WEEKLY;INTERVAL=3;UNTIL=2009-06-05 00:00:00;WKST=SU;BYDAY=TU,TH";
41         $rrule = new Calendar_Model_Rrule(array());
42         $rrule->setFromString($rruleString);
43         
44         $this->assertEquals($rruleString, (string)$rrule);
45     }
46     
47     public function testIsValid()
48     {
49         // test invalid by normal validation actions
50         $rrule = new Calendar_Model_Rrule(array(
51             'interval'  => 'NotAnInt'
52         ), true);
53         $rrule->bypassFilters = false;
54         $this->assertFalse($rrule->isValid(false));
55         
56         // count and until are not allowed together
57         $rrule = new Calendar_Model_Rrule(array(
58             'count'  => '10',
59             'until'  => '2012-01-09 09:33:00'
60         ), true);
61         $this->assertFalse($rrule->isValid(false), 'until & count');
62         
63         // test invalid by setFromString
64         $this->setExpectedException('Tinebase_Exception_Record_Validation');
65         $rruleString = "FREQ=WEEKLY;INTERVAL=NotAnInt";
66         $rrule = new Calendar_Model_Rrule(array());
67         $rrule->setFromString($rruleString);
68         
69         
70     }
71     /************************ recur computation tests ************************/
72     
73     public function testCalcDaily()
74     {
75         $event = new Calendar_Model_Event(array(
76             'uid'           => Tinebase_Record_Abstract::generateUID(),
77             'summary'       => 'change t-shirt',
78             'dtstart'       => '1979-06-05 08:00:00',
79             'dtend'         => '1979-06-05 08:05:00',
80             'rrule'         => 'FREQ=DAILY;INTERVAL=2;UNTIL=2009-04-01 08:00:00',
81             'exdate'        => '2009-03-31 07:00:00',
82             'originator_tz' => 'Europe/Berlin',
83             'rrule_until'   => '2009-04-01 08:00:00',
84             Tinebase_Model_Grants::GRANT_EDIT     => true,
85         ));
86         
87         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event', array(
88             array(
89                 'uid'           => $event->uid,
90                 'summary'       => 'take the blue t-shirt',
91                 'dtstart'       => '2009-03-25 08:00:00',
92                 'dtend'         => '2009-03-25 08:05:00',
93                 'recurid'       => $event->uid . '-' . '2009-03-25 08:00:00',
94                 Tinebase_Model_Grants::GRANT_EDIT     => true,
95             )
96         ));
97         
98         // note: 2009-03-29 Europe/Berlin switched to DST
99         $from = new Tinebase_DateTime('2009-03-23 00:00:00');
100         $until = new Tinebase_DateTime('2009-04-03 23:59:59');
101         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($event, $exceptions, $from, $until);
102         
103         $this->assertEquals('2009-03-23 08:00:00', $recurSet[0]->dtstart->get(Tinebase_Record_Abstract::ISO8601LONG));
104         $this->assertEquals('2009-03-27 08:00:00', $recurSet[1]->dtstart->get(Tinebase_Record_Abstract::ISO8601LONG));
105         $this->assertEquals('2009-03-29 07:00:00', $recurSet[2]->dtstart->get(Tinebase_Record_Abstract::ISO8601LONG));
106         //$this->assertEquals('2009-04-02 07:00:00', $recurSet[3]->dtstart->get(Tinebase_Record_Abstract::ISO8601LONG));
107         $this->assertEquals(3, count($recurSet));
108         
109         // lets also cover the case when recurevent start during calcualtion period:
110         $from = new Tinebase_DateTime('1979-06-01 00:00:00');
111         $until = new Tinebase_DateTime('1979-06-14 23:59:59');
112         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($event, $exceptions, $from, $until);
113         $this->assertEquals(4, count($recurSet), 'recur start in period failed');
114
115         // lets cover the case when search period boudaries are in the middle of the recur events
116         // lets also cover the case when recurevent start during calcualtion period:
117         $from = new Tinebase_DateTime('2009-03-27 08:03:00');
118         $until = new Tinebase_DateTime('2009-03-29 07:03:00');
119         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($event, $exceptions, $from, $until);
120         $this->assertEquals(2, count($recurSet), 'boundary inclusions failed');
121         
122         // and finaly lets cover the case when period boundaries are the boundaries of the recur events
123         $from = new Tinebase_DateTime('2009-03-01 08:05:00');
124         $until = new Tinebase_DateTime('2009-03-03 08:00:00');
125         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($event, $exceptions, $from, $until);
126         $this->assertEquals(1, count($recurSet), 'boundary exclusion failed');
127         $this->assertEquals('2009-03-03 08:00:00', $recurSet[0]->dtstart->get(Tinebase_Record_Abstract::ISO8601LONG));
128     }
129     
130     public function testCalcWeekly()
131     {
132         // note: 1979-06-05 was a tuesday
133         // note: 1979 had no DST in Europe/Berlin
134         $event = new Calendar_Model_Event(array(
135             'uid'           => Tinebase_Record_Abstract::generateUID(),
136             'summary'       => 'take a bath',
137             'dtstart'       => '1979-06-05 18:00:00',
138             'dtend'         => '1979-06-05 19:00:00',
139             'rrule'         => 'FREQ=WEEKLY;INTERVAL=1;BYDAY=SU',
140             'originator_tz' => 'Europe/Berlin',
141             Tinebase_Model_Grants::GRANT_EDIT     => true,
142         ));
143         
144         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
145         
146         $from = new Tinebase_DateTime('2009-06-01 00:00:00');
147         $until = new Tinebase_DateTime('2009-06-30 23:59:59');
148         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($event, $exceptions, $from, $until);
149         $this->assertEquals(4, count($recurSet), '2009-06 has 4 sundays');
150         
151         $from = new Tinebase_DateTime('2013-06-01 00:00:00');
152         $until = new Tinebase_DateTime('2013-06-30 23:59:59');
153         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($event, $exceptions, $from, $until);
154         $this->assertEquals(5, count($recurSet), '2013-06 has 5 sundays');
155         
156         $from = new Tinebase_DateTime('1979-06-01 00:00:00');
157         $until = new Tinebase_DateTime('1979-06-20 23:59:59');
158         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($event, $exceptions, $from, $until);
159         $this->assertEquals(2, count($recurSet), 'test the first sunday (1979-06-10)');
160         
161         // period boudaries in the middle of the recur events
162         $from = new Tinebase_DateTime('2009-04-05 17:30:00');
163         $until = new Tinebase_DateTime('2009-04-12 17:30:00');
164         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($event, $exceptions, $from, $until);
165         $this->assertEquals(2, count($recurSet), 'boundaries inclusion failed');
166         
167         // odd interval
168         $event->rrule = 'FREQ=WEEKLY;INTERVAL=2;BYDAY=SU';
169         $from = new Tinebase_DateTime('2009-04-12 00:00:00');
170         $until = new Tinebase_DateTime('2009-05-03 23:59:59');
171         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($event, $exceptions, $from, $until);
172         $this->assertEquals(2, count($recurSet), 'odd interval failed');
173     }
174     
175     /**
176      * BYDAY are meant in the week of baseEvent -> WKST
177      * 
178      */
179     public function testCalcWeeklyWKST()
180     {
181         $event = new Calendar_Model_Event(array(
182             'uid'           => Tinebase_Record_Abstract::generateUID(),
183             'summary'       => 'weekly with BYDAY and WKST',
184             'dtstart'       => '2011-11-16 10:30:00',
185             'dtend'         => '2011-11-16 12:30:00',
186             'rrule'         => 'FREQ=WEEKLY;INTERVAL=2;BYDAY=MO,TH;WKST=MO',
187             'originator_tz' => 'Europe/Berlin',
188             Tinebase_Model_Grants::GRANT_EDIT     => true,
189         ));
190         
191         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
192         
193         $from = new Tinebase_DateTime('2011-11-14 00:00:00');
194         $until = new Tinebase_DateTime('2011-12-04 23:59:59');
195         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($event, $exceptions, $from, $until);
196         
197         $this->assertEquals(3, count($recurSet), 'recur set failed');
198         $this->assertTrue(array_search('2011-11-17 10:30:00', $recurSet->dtstart) !== FALSE);
199         $this->assertTrue(array_search('2011-11-28 10:30:00', $recurSet->dtstart) !== FALSE);
200         $this->assertTrue(array_search('2011-12-01 10:30:00', $recurSet->dtstart) !== FALSE);
201     }
202     
203     /**
204      * RRULE:FREQ=WEEKLY
205      * => treat like INTERVAL=1;BYDAY=SU
206      */
207     public function testCalcWeeklyOmmitByDay()
208     {
209         $event = new Calendar_Model_Event(array(
210             'uid'           => Tinebase_Record_Abstract::generateUID(),
211             'summary'       => 'weekly without byday clause',
212             'dtstart'       => '2011-11-16 10:30:00',
213             'dtend'         => '2011-11-16 12:30:00',
214             'rrule'         => 'FREQ=WEEKLY',
215             'originator_tz' => 'Europe/Berlin',
216             Tinebase_Model_Grants::GRANT_EDIT     => true,
217         ));
218         
219         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
220         
221         $from = new Tinebase_DateTime('2011-11-16 00:00:00');
222         $until = new Tinebase_DateTime('2011-11-30 23:59:59');
223         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($event, $exceptions, $from, $until);
224         
225         $this->assertEquals(2, count($recurSet), 'recur set failed');
226         $this->assertTrue(array_search('2011-11-23 10:30:00', $recurSet->dtstart) !== FALSE);
227         $this->assertTrue(array_search('2011-11-30 10:30:00', $recurSet->dtstart) !== FALSE);
228     }
229     
230     /**
231      * 2009-07-15 if wday skipping in calculation is done in UTC, we get an extra event
232      *            and all Recurrences are calculated one day late...
233      *            
234      */
235     public function testCalcWeeklyAllDay()
236     {
237         // NOTE allday in Europe/Berlin leads to UTC daybreak
238         $event = new Calendar_Model_Event(array(
239             'uid'              => Tinebase_Record_Abstract::generateUID(),
240             'summary'          => 'testCalcWeeklyAllDay',
241             'dtstart'          => '2009-05-31 22:00:00',
242             'dtend'            => '2009-06-01 21:59:00',
243             'is_all_day_event' => true,
244             'rrule'            => 'FREQ=WEEKLY;INTERVAL=1;BYDAY=MO',
245             'originator_tz'    => 'Europe/Berlin',
246             Tinebase_Model_Grants::GRANT_EDIT        => true,
247         ));
248         
249         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
250         
251         $from = new Tinebase_DateTime('2009-05-31 22:00:00');
252         $until = new Tinebase_DateTime('2009-07-05 21:59:59');
253         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($event, $exceptions, $from, $until);
254         
255         
256         $this->assertEquals(4, count($recurSet), 'odd interval failed');
257         $this->assertEquals('2009-06-07 22:00:00', $recurSet[0]->dtstart->get(Tinebase_Record_Abstract::ISO8601LONG));
258         $this->assertEquals('2009-06-08 21:59:00', $recurSet[0]->dtend->get(Tinebase_Record_Abstract::ISO8601LONG));
259     }
260     
261     /**
262      * RRULE:FREQ=MONTHLY
263      * => INTEVAL=1;BYMONTHDAY=<monthday of dtstart>
264      * 
265      */
266     public function testCalcMonthlyOmmitByClause()
267     {
268         $event = new Calendar_Model_Event(array(
269             'uid'           => Tinebase_Record_Abstract::generateUID(),
270             'summary'       => 'monthly without by* clause',
271             'dtstart'       => '2011-11-16 10:30:00',
272             'dtend'         => '2011-11-16 12:30:00',
273             'rrule'         => 'FREQ=MONTHLY',
274             'originator_tz' => 'Europe/Berlin',
275             Tinebase_Model_Grants::GRANT_EDIT     => true,
276         ));
277         
278         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
279         
280         $from = new Tinebase_DateTime('2011-12-01 00:00:00');
281         $until = new Tinebase_DateTime('2011-12-30 23:59:59');
282         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($event, $exceptions, $from, $until);
283         
284         $this->assertEquals(1, count($recurSet), 'recur set failed');
285         $this->assertTrue(array_search('2011-12-16 10:30:00', $recurSet->dtstart) !== FALSE);
286     }
287     
288     public function testCalcMonthlyByMonthDay()
289     {
290         $event = new Calendar_Model_Event(array(
291             'uid'           => Tinebase_Record_Abstract::generateUID(),
292             'summary'       => 'celebrate my month day',
293             'dtstart'       => '1979-06-05 15:00:00',
294             'dtend'         => '1979-06-05 16:00:00',
295             'rrule'         => 'FREQ=MONTHLY;INTERVAL=1;BYMONTHDAY=5',
296             'exdate'        => '2009-02-05 15:00:00,2009-05-05 14:00:00',
297             'originator_tz' => 'Europe/Berlin',
298             Tinebase_Model_Grants::GRANT_EDIT     => true,
299         ));
300         
301         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event', array(
302             array(
303                 'uid'           => $event->uid,
304                 'summary'       => 'official birthday party',
305                 'dtstart'       => '2009-06-05 20:00:00',
306                 'dtend'         => '2009-06-06 05:00:00',
307                 'recurid'       => $event->uid . '-' . '2009-06-05 14:00:00',
308                 Tinebase_Model_Grants::GRANT_EDIT     => true,
309             )
310         ));
311         
312         // note: 2009-03-29 Europe/Berlin switched to DST
313         $from = new Tinebase_DateTime('2009-01-01 00:00:00');
314         $until = new Tinebase_DateTime('2009-06-30 23:59:59');
315         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($event, $exceptions, $from, $until);
316         
317         
318         $this->assertEquals('2009-01-05 15:00:00', $recurSet[0]->dtstart->get(Tinebase_Record_Abstract::ISO8601LONG));
319         $this->assertEquals('2009-03-05 15:00:00', $recurSet[1]->dtstart->get(Tinebase_Record_Abstract::ISO8601LONG));
320         $this->assertEquals('2009-04-05 14:00:00', $recurSet[2]->dtstart->get(Tinebase_Record_Abstract::ISO8601LONG));
321         $this->assertEquals(3, count($recurSet));
322         
323         // lets also cover the case when recurevent start during calcualtion period:
324         $from = new Tinebase_DateTime('1979-06-01 00:00:00');
325         $until = new Tinebase_DateTime('1979-10-31 23:49:59');
326         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($event, $exceptions, $from, $until);
327         $this->assertEquals(4, count($recurSet), 'recur start in period failed');
328         
329         // lets cover the case when search period boudaries are in the middle of the recur events
330         // lets also cover the case when recurevent start during calcualtion period:
331         $from = new Tinebase_DateTime('2009-03-05 15:30:00');
332         $until = new Tinebase_DateTime('2009-04-05 14:30:00');
333         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($event, $exceptions, $from, $until);
334         $this->assertEquals(2, count($recurSet), 'boundary inclusions failed');
335         
336         // and finaly lets cover the case when period boundaries are the boundaries of the recur events
337         $from = new Tinebase_DateTime('2009-03-05 16:00:00');
338         $until = new Tinebase_DateTime('2009-04-05 14:00:00');
339         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($event, $exceptions, $from, $until);
340         $this->assertEquals(1, count($recurSet), 'boundary exclusion failed');
341         $this->assertEquals('2009-04-05 14:00:00', $recurSet[0]->dtstart->get(Tinebase_Record_Abstract::ISO8601LONG));
342         
343         // try odd interval
344         $event->rrule = 'FREQ=MONTHLY;INTERVAL=5;BYMONTHDAY=5';
345         $from = new Tinebase_DateTime('2009-01-05 00:00:00');
346         $until = new Tinebase_DateTime('2009-11-05 23:59:59');
347         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($event, $exceptions, $from, $until);
348         $this->assertEquals(2, count($recurSet), 'odd interval failed');
349         $this->assertEquals('2009-01-05 15:00:00', $recurSet[0]->dtstart->get(Tinebase_Record_Abstract::ISO8601LONG));
350         $this->assertEquals('2009-11-05 15:00:00', $recurSet[1]->dtstart->get(Tinebase_Record_Abstract::ISO8601LONG));
351         
352     }
353     
354     /**
355      * test dtstart of base event is not dtstart of first recur instance
356      */
357     public function testCalcMonthlyByMonthDayStart()
358     {
359         $event = new Calendar_Model_Event(array(
360             'uid'           => Tinebase_Record_Abstract::generateUID(),
361             'summary'       => 'testCalcMonthlyByDayStart',
362             'dtstart'       => '2009-07-10 10:00:00',
363             'dtend'         => '2009-07-10 11:00:00',
364             'rrule'         => 'FREQ=MONTHLY;INTERVAL=1;BYMONTHDAY=20',
365             'originator_tz' => 'Europe/Berlin',
366             Tinebase_Model_Grants::GRANT_EDIT     => true,
367         ));
368         
369         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
370         
371         $from = new Tinebase_DateTime('2009-07-01 00:00:00');
372         $until = new Tinebase_DateTime('2009-08-31 23:59:59');
373         
374         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($event, $exceptions, $from, $until);
375         
376         $this->assertEquals(2, count($recurSet));
377         $this->assertEquals('2009-07-20 10:00:00', $recurSet[0]->dtstart->get(Tinebase_Record_Abstract::ISO8601LONG));
378     }
379     
380     public function testCalcMonthlyByMonthDayStartAllDay()
381     {
382         $event = new Calendar_Model_Event(array(
383             'uid'             => Tinebase_Record_Abstract::generateUID(),
384             'summary'         => 'testCalcMonthlyByMonthDayStartAllDay',
385             'dtstart'         => '2009-07-14 22:00:00',
386             'dtend'           => '2009-07-15 21:59:00',
387             'is_all_day_event' => true,
388             'rrule'           => 'FREQ=MONTHLY;INTERVAL=1;BYMONTHDAY=15',
389             'originator_tz'   => 'Europe/Berlin',
390             Tinebase_Model_Grants::GRANT_EDIT       => true,
391         ));
392         
393         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
394         
395         $from = new Tinebase_DateTime('2009-06-28 22:00:00');
396         $until = new Tinebase_DateTime('2009-09-06 21:59:59');
397         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($event, $exceptions, $from, $until);
398         
399         $this->assertEquals(1, count($recurSet));
400         $this->assertEquals('2009-08-14 22:00:00', $recurSet[0]->dtstart->get(Tinebase_Record_Abstract::ISO8601LONG));
401         $this->assertEquals('2009-08-15 21:59:00', $recurSet[0]->dtend->get(Tinebase_Record_Abstract::ISO8601LONG));
402
403         // test switch from DST to NO DST
404         $from = new Tinebase_DateTime('2009-11-30 22:00:00');
405         $until = new Tinebase_DateTime('2009-12-31 21:59:59');
406         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($event, $exceptions, $from, $until);
407         
408         $this->assertEquals(1, count($recurSet));
409         $this->assertEquals('2009-12-14 23:00:00', $recurSet[0]->dtstart->get(Tinebase_Record_Abstract::ISO8601LONG));
410         $this->assertEquals('2009-12-15 22:59:00', $recurSet[0]->dtend->get(Tinebase_Record_Abstract::ISO8601LONG));
411     }
412     
413     public function testCalcMonthlyByDay()
414     {
415         $event = new Calendar_Model_Event(array(
416             'uid'           => Tinebase_Record_Abstract::generateUID(),
417             'summary'       => 'thanks for moni',
418             'dtstart'       => '1974-02-11 15:00:00',
419             'dtend'         => '1974-02-11 16:00:00',
420             'rrule'         => 'FREQ=MONTHLY;INTERVAL=5;BYDAY=4SU',
421             'originator_tz' => 'Europe/Berlin',
422             Tinebase_Model_Grants::GRANT_EDIT     => true,
423         ));
424         
425         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
426         
427         $from = new Tinebase_DateTime('2009-02-22 00:00:00');
428         $until = new Tinebase_DateTime('2009-07-26 23:59:59');
429         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($event, $exceptions, $from, $until);
430         $this->assertEquals(2, count($recurSet), 'forward skip failed');
431         $this->assertEquals('2009-02-22 15:00:00', $recurSet[0]->dtstart->get(Tinebase_Record_Abstract::ISO8601LONG));
432         $this->assertEquals('2009-07-26 14:00:00', $recurSet[1]->dtstart->get(Tinebase_Record_Abstract::ISO8601LONG));
433     }
434     
435     public function testCalcMonthlyByDaySupressToEarly()
436     {
437         $event = new Calendar_Model_Event(array(
438             'uid'           => Tinebase_Record_Abstract::generateUID(),
439             'summary'       => 'monthly sencond th',
440             'dtstart'       => '2011-11-24 08:00:00',
441             'dtend'         => '2011-11-24 10:00:00',
442             'rrule'         => 'FREQ=MONTHLY;INTERVAL=1;BYDAY=2TH',
443             'originator_tz' => 'Europe/Berlin',
444             Tinebase_Model_Grants::GRANT_EDIT     => true,
445         ));
446         
447         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
448         
449         $from = new Tinebase_DateTime('2011-10-31 00:00:00');
450         $until = new Tinebase_DateTime('2011-12-04 23:59:59');
451         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($event, $exceptions, $from, $until);
452         
453         $this->assertEquals(0, count($recurSet), 'events before baseEvent dtstart must not be included');
454         
455     }
456     
457     public function testCalcMonthlyByDayBackwardSkip()
458     {
459         $event = new Calendar_Model_Event(array(
460             'uid'           => Tinebase_Record_Abstract::generateUID(),
461             'summary'       => 'two monthly last wendsday',
462             'dtstart'       => '2009-04-29 15:00:00',
463             'dtend'         => '2009-04-29 16:00:00',
464             'rrule'         => 'FREQ=MONTHLY;INTERVAL=2;BYDAY=-1WE',
465             'originator_tz' => 'Europe/Berlin',
466             Tinebase_Model_Grants::GRANT_EDIT     => true,
467         ));
468         
469         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event', array(
470             array(
471                 'uid'           => $event->uid,
472                 'summary'       => 'two monthly last wendsday exception',
473                 'dtstart'       => '2009-06-24 15:00:00',
474                 'dtend'         => '2009-06-24 16:00:00',
475                 'recurid'       => $event->uid . '-' . '2009-06-24 15:00:00',
476                 Tinebase_Model_Grants::GRANT_EDIT     => true,
477             )
478         ));
479         
480         $from = new Tinebase_DateTime('2009-02-01 15:00:00');
481         $until = new Tinebase_DateTime('2009-12-31 14:00:00');
482         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($event, $exceptions, $from, $until);
483         
484         $this->assertEquals(3, count($recurSet), 'backward skip failed');
485         $this->assertEquals('2009-08-26 15:00:00', $recurSet[0]->dtstart->get(Tinebase_Record_Abstract::ISO8601LONG));
486         $this->assertEquals('2009-10-28 16:00:00', $recurSet[1]->dtstart->get(Tinebase_Record_Abstract::ISO8601LONG));
487         $this->assertEquals('2009-12-30 16:00:00', $recurSet[2]->dtstart->get(Tinebase_Record_Abstract::ISO8601LONG));
488     }
489     
490     /**
491      * test dtstart of base event is not dtstart of first recur instance
492      */
493     public function testCalcMonthlyByDayStart()
494     {
495         $event = new Calendar_Model_Event(array(
496             'uid'           => Tinebase_Record_Abstract::generateUID(),
497             'summary'       => 'testCalcMonthlyByDayStart',
498             'dtstart'       => '2009-07-10 10:00:00',
499             'dtend'         => '2009-07-10 11:00:00',
500             'rrule'         => 'FREQ=MONTHLY;INTERVAL=1;BYDAY=-1FR',
501             'originator_tz' => 'Europe/Berlin',
502             Tinebase_Model_Grants::GRANT_EDIT     => true,
503         ));
504         
505         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
506         
507         $from = new Tinebase_DateTime('2009-07-01 00:00:00');
508         $until = new Tinebase_DateTime('2009-08-31 23:59:59');
509         
510         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($event, $exceptions, $from, $until);
511         
512         //print_r($recurSet->toArray());
513         $this->assertEquals(2, count($recurSet));
514         $this->assertEquals('2009-07-31 10:00:00', $recurSet[0]->dtstart->get(Tinebase_Record_Abstract::ISO8601LONG));
515     }
516     
517     public function testCalcMonthlyByDayStartAllDay()
518     {
519         
520         $event = new Calendar_Model_Event(array(
521             'uid'             => Tinebase_Record_Abstract::generateUID(),
522             'summary'         => 'testCalcMonthlyByDayStartAllDay',
523             'dtstart'         => '2009-07-21 22:00:00',
524             'dtend'           => '2009-07-22 21:59:00',
525             'is_all_day_event' => true,
526             'rrule'           => 'FREQ=MONTHLY;INTERVAL=1;BYDAY=4WE',
527             'originator_tz'   => 'Europe/Berlin',
528             Tinebase_Model_Grants::GRANT_EDIT       => true,
529         ));
530         
531         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
532         
533         $from = new Tinebase_DateTime('2009-06-28 22:00:00');
534         $until = new Tinebase_DateTime('2009-09-06 21:59:59');
535         
536         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($event, $exceptions, $from, $until);
537         
538         $this->assertEquals(1, count($recurSet));
539         $this->assertEquals('2009-08-25 22:00:00', $recurSet[0]->dtstart->get(Tinebase_Record_Abstract::ISO8601LONG));
540         $this->assertEquals('2009-08-26 21:59:00', $recurSet[0]->dtend->get(Tinebase_Record_Abstract::ISO8601LONG));
541     }
542     
543     public function testCalcYearlyByMonthDay()
544     {
545         $event = new Calendar_Model_Event(array(
546             'uid'           => Tinebase_Record_Abstract::generateUID(),
547             'summary'       => 'yearly by day',
548             'dtstart'       => '2009-07-10 10:00:00',
549             'dtend'         => '2009-07-10 11:00:00',
550             'rrule'         => 'FREQ=YEARLY;INTERVAL=1;BYMONTH=7;BYMONTHDAY=10',
551             'originator_tz' => 'Europe/Berlin',
552             Tinebase_Model_Grants::GRANT_EDIT     => true,
553         ));
554         
555         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
556         
557         $from = new Tinebase_DateTime('2010-06-08 22:00:00');
558         $until = new Tinebase_DateTime('2010-07-31 22:00:00');
559         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($event, $exceptions, $from, $until);
560         $this->assertEquals(1, count($recurSet));
561         $this->assertEquals('2010-07-10 10:00:00', $recurSet[0]->dtstart->get(Tinebase_Record_Abstract::ISO8601LONG));
562         
563         $from = new Tinebase_DateTime('2010-06-10 22:00:00');
564         $until = new Tinebase_DateTime('2010-07-31 22:00:00');
565         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($event, $exceptions, $from, $until);
566         $this->assertEquals(1, count($recurSet));
567         $this->assertEquals('2010-07-10 10:00:00', $recurSet[0]->dtstart->get(Tinebase_Record_Abstract::ISO8601LONG));
568     }
569     
570     /**
571      * @see #5170
572      */
573     public function testCalcYearlyByMonthDayAllDayEvent()
574     {
575         $event = new Calendar_Model_Event(array(
576             'uid'               => Tinebase_Record_Abstract::generateUID(),
577             'summary'           => 'yearly by day',
578             'dtstart'           => '2011-10-31 23:00:00',
579             'dtend'             => '2011-11-01 22:59:00',
580             'is_all_day_event'  => 1,
581             'rrule'             => 'FREQ=YEARLY;INTERVAL=1;BYMONTH=11;BYMONTHDAY=1',
582             'originator_tz'     => 'Europe/Berlin',
583             Tinebase_Model_Grants::GRANT_EDIT     => true,
584         ));
585         
586         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
587         
588         $from = new Tinebase_DateTime('2011-10-31 23:00:00');
589         $until = new Tinebase_DateTime('2011-12-04 23:00:00');
590         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($event, $exceptions, $from, $until);
591         $this->assertEquals(0, count($recurSet));
592     }
593     
594     public function testCalcYearlyByMonthDayLeapYear()
595     {
596         $event = new Calendar_Model_Event(array(
597             'uid'           => Tinebase_Record_Abstract::generateUID(),
598             'summary'       => 'yearly 29.feb',
599             'dtstart'       => '2008-02-29 08:00:00',
600             'dtend'         => '2008-02-29 10:00:00',
601             'rrule'         => 'FREQ=YEARLY;INTERVAL=1;BYMONTH=2;BYMONTHDAY=29',
602             'originator_tz' => 'Europe/Berlin',
603             Tinebase_Model_Grants::GRANT_EDIT     => true,
604         ));
605         
606         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
607         
608         // note: 2009-03-29 Europe/Berlin switched to DST
609         $from = new Tinebase_DateTime('2008-02-25 00:00:00');
610         $until = new Tinebase_DateTime('2013-03-01 23:59:59');
611         $until->setTimezone('Europe/Berlin');
612         $from->setTimezone('Europe/Berlin');
613         
614         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($event, $exceptions, $from, $until);
615         
616         $this->assertEquals(1, count($recurSet), 'leapyear only failed');
617         $this->assertEquals('2012-02-29 08:00:00', $recurSet[0]->dtstart->get(Tinebase_Record_Abstract::ISO8601LONG));
618     }
619     
620     public function testCalcYearlyByDay()
621     {
622         $event = new Calendar_Model_Event(array(
623             'uid'           => Tinebase_Record_Abstract::generateUID(),
624             'summary'       => 'yearly last friday in february',
625             'dtstart'       => '2008-02-29 08:00:00',
626             'dtend'         => '2008-02-29 10:00:00',
627             'rrule'         => 'FREQ=YEARLY;INTERVAL=1;BYMONTH=2;BYDAY=-1FR',
628             'originator_tz' => 'Europe/Berlin',
629             Tinebase_Model_Grants::GRANT_EDIT     => true,
630         ));
631         
632         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
633         
634         $from = new Tinebase_DateTime('2008-02-25 00:00:00');
635         $until = new Tinebase_DateTime('2013-03-01 23:59:59');
636         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($event, $exceptions, $from, $until);
637         $this->assertEquals(5, count($recurSet), 'yearlybyday failed');
638     }
639     
640     /**
641      * skip BYMONTHDAY, shift BYMONTH
642      *  => respect BYMONTH
643      *  => only evaluate first month
644      */
645     public function testCalcYearlyByMonth()
646     {
647         $event = new Calendar_Model_Event(array(
648             'uid'           => Tinebase_Record_Abstract::generateUID(),
649             'summary'       => 'yearly with BYMONTH shift',
650             'dtstart'       => '2011-11-16 10:30:00',
651             'dtend'         => '2011-11-16 12:30:00',
652             'rrule'         => 'FREQ=YEARLY;INTERVAL=1;BYMONTH=6,12',
653             'originator_tz' => 'Europe/Berlin',
654             Tinebase_Model_Grants::GRANT_EDIT     => true,
655         ));
656         
657         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
658         
659         $from = new Tinebase_DateTime('2012-01-01 00:00:00');
660         $until = new Tinebase_DateTime('2012-12-31 23:59:59');
661         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($event, $exceptions, $from, $until);
662         
663         $this->assertEquals(1, count($recurSet), 'recur set failed');
664         $this->assertTrue(array_search('2012-06-16 09:30:00', $recurSet->dtstart) !== FALSE);
665     }
666     
667     public function testCalcYearlyOmmitByClause()
668     {
669         $event = new Calendar_Model_Event(array(
670             'uid'           => Tinebase_Record_Abstract::generateUID(),
671             'summary'       => 'yearly without by* clause',
672             'dtstart'       => '2011-11-16 10:30:00',
673             'dtend'         => '2011-11-16 12:30:00',
674             'rrule'         => 'FREQ=YEARLY',
675             'originator_tz' => 'Europe/Berlin',
676             Tinebase_Model_Grants::GRANT_EDIT     => true,
677         ));
678         
679         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
680         
681         $from = new Tinebase_DateTime('2012-01-01 00:00:00');
682         $until = new Tinebase_DateTime('2012-12-31 23:59:59');
683         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($event, $exceptions, $from, $until);
684         
685         $this->assertEquals(1, count($recurSet), 'recur set failed');
686         $this->assertTrue(array_search('2012-11-16 10:30:00', $recurSet->dtstart) !== FALSE);
687     }
688     
689     /**
690      * NOTE: virtual exdates are persistent exceptions -> non persistent exdates 
691      *       which might occour due to scopeing or attendee status filtering
692      */
693     //public function testCalcWithVirtualExdate()
694     //{
695         // mhh hard to test as we don't deal with persistent stuff in this test class
696         // -> move to controller tests
697     //}
698     
699     public function testMultipleTimezonesOriginatingInSeatle()
700     {
701         date_default_timezone_set('US/Pacific');
702         $event = new Calendar_Model_Event(array(
703             'uid'           => Tinebase_Record_Abstract::generateUID(),
704             'summary'       => 'conference',
705             'dtstart'       => '2003-03-28 10:00:00',
706             'dtend'         => '2003-03-28 12:00:00',
707             'rrule'         => 'FREQ=WEEKLY;INTERVAL=1;BYDAY=FR',
708             'originator_tz' => 'US/Pacific',
709             Tinebase_Model_Grants::GRANT_EDIT     => true,
710         ));
711         $event->setTimezone('UTC');
712         date_default_timezone_set('UTC');
713         
714         
715         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
716         
717         $from = new Tinebase_DateTime('2003-04-04 00:00:00');
718         $until = new Tinebase_DateTime('2003-04-11 23:59:59');
719         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($event, $exceptions, $from, $until);
720         
721         $recurSet->setTimezone('US/Pacific');
722         $this->assertEquals(10, $recurSet[0]->dtstart->get('G'), 'for orginator dtstart should be stable...');
723         $this->assertEquals(10, $recurSet[1]->dtstart->get('G'), 'for orginator dtstart should be stable...');
724         
725         $recurSet->setTimezone('US/Arizona');
726         $this->assertEquals(11, $recurSet[0]->dtstart->get('G'), 'for US/Arizona dtstart before DST should be 11');
727         $this->assertEquals(10, $recurSet[1]->dtstart->get('G'), 'for US/Arizona dtstart after DST shoud be 10');
728         
729         $recurSet->setTimezone('America/New_York');
730         $this->assertEquals(13, $recurSet[0]->dtstart->get('G'), 'for America/New_York dtstart before DST should be 13');
731         $this->assertEquals(13, $recurSet[1]->dtstart->get('G'), 'for America/New_York dtstart after DST shoud be 13');
732         
733         $recurSet->setTimezone('UTC');
734         $this->assertEquals(18, $recurSet[0]->dtstart->get('G'), 'for UTC dtstart before DST should be 18');
735         $this->assertEquals(17, $recurSet[1]->dtstart->get('G'), 'for UTC dtstart after DST shoud be 17');
736     }
737     
738     public function testMultipleTimezonesOriginatingInArizona()
739     {
740         date_default_timezone_set('US/Arizona');
741         $event = new Calendar_Model_Event(array(
742             'uid'           => Tinebase_Record_Abstract::generateUID(),
743             'summary'       => 'conference',
744             'dtstart'       => '2003-03-28 11:00:00',
745             'dtend'         => '2003-03-28 13:00:00',
746             'rrule'         => 'FREQ=WEEKLY;INTERVAL=1;BYDAY=FR',
747             'originator_tz' => 'US/Arizona',
748             Tinebase_Model_Grants::GRANT_EDIT     => true,
749         ));
750         $event->setTimezone('UTC');
751         date_default_timezone_set('UTC');
752         
753         
754         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
755         
756         $from = new Tinebase_DateTime('2003-04-04 00:00:00');
757         $until = new Tinebase_DateTime('2003-04-11 23:59:59');
758         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($event, $exceptions, $from, $until);
759         
760         $recurSet->setTimezone('US/Pacific');
761         $this->assertEquals(10, $recurSet[0]->dtstart->get('G'), 'for US/Pacific dtstart before DST should be 10');
762         $this->assertEquals(11, $recurSet[1]->dtstart->get('G'), 'for US/Pacific dtstart before DST should be 11');
763         
764         $recurSet->setTimezone('US/Arizona');
765         $this->assertEquals(11, $recurSet[0]->dtstart->get('G'), 'for orginator dtstart should be stable...');
766         $this->assertEquals(11, $recurSet[1]->dtstart->get('G'), 'for orginator dtstart should be stable...');
767         
768         $recurSet->setTimezone('America/New_York');
769         $this->assertEquals(13, $recurSet[0]->dtstart->get('G'), 'for US/Arizona dtstart before DST should be 13');
770         $this->assertEquals(14, $recurSet[1]->dtstart->get('G'), 'for US/Arizona dtstart after DST shoud be 14');
771         
772         $recurSet->setTimezone('UTC');
773         $this->assertEquals(18, $recurSet[0]->dtstart->get('G'), 'for UTC dtstart before DST should be 18');
774         $this->assertEquals(18, $recurSet[1]->dtstart->get('G'), 'for UTC dtstart after DST shoud be 18');
775     }
776     
777     public function testBrokenRrule()
778     {
779         $event = new Calendar_Model_Event(array(
780             'uid'           => Tinebase_Record_Abstract::generateUID(),
781             'summary'       => 'weekly with out interval',
782             'dtstart'       => '2009-08-28 08:00:00',
783             'dtend'         => '2009-08-28 10:00:00',
784             'rrule'         => 'FREQ=WEEKLY;BYDAY=TU',
785             'originator_tz' => 'Europe/Berlin',
786             Tinebase_Model_Grants::GRANT_EDIT     => true,
787         ));
788         
789         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
790         
791         $from = new Tinebase_DateTime('2009-08-28 00:00:00');
792         $until = new Tinebase_DateTime('2009-09-28 23:59:59');
793         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($event, $exceptions, $from, $until);
794         $this->assertEquals(4, count($recurSet));
795         
796     }
797     
798     
799     public function testComputeNextOccurrence()
800     {
801         $event = new Calendar_Model_Event(array(
802             'uid'           => Tinebase_Record_Abstract::generateUID(),
803             'summary'       => 'weekly',
804             'dtstart'       => '2009-09-09 08:00:00',
805             'dtend'         => '2009-09-09 10:00:00',
806             'rrule'         => 'FREQ=WEEKLY;BYDAY=WE,FR;INTERVAL=1;UNTIL=2009-09-27 10:00:00',
807             'originator_tz' => 'Europe/Berlin',
808         ));
809         
810         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
811         
812         $from = new Tinebase_DateTime('2008-01-21 00:00:00');
813         $nextOccurrence = Calendar_Model_Rrule::computeNextOccurrence($event, $exceptions, $from);
814         $this->assertTrue($event === $nextOccurrence, 'given event is next occurrence');
815         
816         $nextOccurrence = Calendar_Model_Rrule::computeNextOccurrence($event, $exceptions, $nextOccurrence->dtend);
817         $this->assertEquals('2009-09-11 08:00:00', $nextOccurrence->dtstart->toString(Tinebase_Record_Abstract::ISO8601LONG), 'next (dtend is exluded)');
818         
819         // assert computeRecurrenceSet computes first BYDAY=TH event
820         $nextOccurrence = Calendar_Model_Rrule::computeNextOccurrence($event, $exceptions, $nextOccurrence->dtstart);
821         $this->assertEquals('2009-09-11 08:00:00', $nextOccurrence->dtstart->toString(Tinebase_Record_Abstract::ISO8601LONG), ' same (dtstart is included)');
822         
823         $nextOccurrence = Calendar_Model_Rrule::computeNextOccurrence($event, $exceptions, $nextOccurrence->dtend);
824         $this->assertEquals('2009-09-16 08:00:00', $nextOccurrence->dtstart->toString(Tinebase_Record_Abstract::ISO8601LONG), ' second next');
825         
826     }
827     
828     public function testComputeNextOccurrenceWithWhich()
829     {
830         $event = new Calendar_Model_Event(array(
831             'uid'           => Tinebase_Record_Abstract::generateUID(),
832             'summary'       => 'weekly',
833             'dtstart'       => '2009-09-09 08:00:00',
834             'dtend'         => '2009-09-09 10:00:00',
835             'rrule'         => 'FREQ=WEEKLY;BYDAY=WE,FR;INTERVAL=1',
836             'originator_tz' => 'Europe/Berlin',
837         ));
838         
839         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
840         
841         $from = new Tinebase_DateTime('now');
842         $nextOccurrence = Calendar_Model_Rrule::computeNextOccurrence($event, $exceptions, $from, 0);
843         $this->assertEquals($event, $nextOccurrence, 'which = 0 means same event');
844         
845         $nextOccurrence = Calendar_Model_Rrule::computeNextOccurrence($event, $exceptions, $from, 4);
846         $this->assertLessThan($nextOccurrence->dtstart, $from);
847         $this->assertGreaterThan($nextOccurrence->dtstart, $from->modify('+ 3 weeks'));
848     }
849     
850     public function testComputeNextOccurrenceWithNegativeWhich()
851     {
852         $event = new Calendar_Model_Event(array(
853                 'uid'           => Tinebase_Record_Abstract::generateUID(),
854                 'summary'       => 'weekly',
855                 'dtstart'       => '2013-10-11 08:00:00',
856                 'dtend'         => '2013-10-11 10:00:00',
857                 'rrule'         => 'FREQ=WEEKLY;BYDAY=FR;INTERVAL=1',
858                 'originator_tz' => 'Europe/Berlin',
859         ));
860     
861         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
862         
863         // NOTE: ongoing event is considered as previous/next
864         $from = new Tinebase_DateTime('2013-11-01 07:59:59');
865         $previousOccurrence = Calendar_Model_Rrule::computeNextOccurrence($event, $exceptions, $from, -1);
866         $this->assertEquals('2013-10-25 08:00:00', $previousOccurrence->dtstart->toString());
867         
868         // DST switch
869         $from = new Tinebase_DateTime('2013-11-08 08:59:59');
870         $previousOccurrence = Calendar_Model_Rrule::computeNextOccurrence($event, $exceptions, $from, -1);
871         $this->assertEquals('2013-11-01 09:00:00', $previousOccurrence->dtstart->toString());
872     }
873     
874     public function testWeeklyWithCount()
875     {
876         $event = new Calendar_Model_Event(array(
877             'uid'           => Tinebase_Record_Abstract::generateUID(),
878             'summary'       => 'weekly end by count',
879             'dtstart'       => '2012-01-10 10:00:00',
880             'dtend'         => '2012-01-10 11:00:00',
881             'rrule'         => 'FREQ=WEEKLY;BYDAY=TU;INTERVAL=1;COUNT=10',
882             'originator_tz' => 'Europe/Berlin',
883         ));
884         
885         $event->setRruleUntil();
886         
887         $this->assertEquals('2012-03-13 11:00:00', $event->rrule_until->toString(Tinebase_Record_Abstract::ISO8601LONG));
888     }
889     
890     
891     /************************** date helper tests ***************************/
892     
893     public function testSkipWday()
894     {
895         // $_n == +1
896         $date = new Tinebase_DateTime('2009-04-08 00:00:00');
897         $this->assertEquals('2009-04-12 00:00:00', Calendar_Model_Rrule::skipWday($date, Calendar_Model_Rrule::WDAY_SUNDAY)->toString(Tinebase_Record_Abstract::ISO8601LONG));
898         
899         $date = new Tinebase_DateTime('2009-04-08 00:00:00');
900         $this->assertEquals('2009-04-09 00:00:00', Calendar_Model_Rrule::skipWday($date, Calendar_Model_Rrule::WDAY_THURSDAY)->toString(Tinebase_Record_Abstract::ISO8601LONG));
901         
902         $date = new Tinebase_DateTime('2009-04-08 00:00:00');
903         $this->assertEquals('2009-04-15 00:00:00', Calendar_Model_Rrule::skipWday($date, Calendar_Model_Rrule::WDAY_WEDNESDAY)->toString(Tinebase_Record_Abstract::ISO8601LONG));
904         
905         $date = new Tinebase_DateTime('2009-04-05 00:00:00');
906         $this->assertEquals('2009-04-12 00:00:00', Calendar_Model_Rrule::skipWday($date, Calendar_Model_Rrule::WDAY_SUNDAY)->toString(Tinebase_Record_Abstract::ISO8601LONG));
907         
908         $date = new Tinebase_DateTime('2009-04-05 00:00:00');
909         $this->assertEquals('2009-04-09 00:00:00', Calendar_Model_Rrule::skipWday($date, Calendar_Model_Rrule::WDAY_THURSDAY)->toString(Tinebase_Record_Abstract::ISO8601LONG));
910         
911         $date = new Tinebase_DateTime('2009-04-05 00:00:00');
912         $this->assertEquals('2009-04-08 00:00:00', Calendar_Model_Rrule::skipWday($date, Calendar_Model_Rrule::WDAY_WEDNESDAY)->toString(Tinebase_Record_Abstract::ISO8601LONG));
913         
914         $date = new Tinebase_DateTime('2009-04-04 00:00:00');
915         $this->assertEquals('2009-04-05 00:00:00', Calendar_Model_Rrule::skipWday($date, Calendar_Model_Rrule::WDAY_SUNDAY)->toString(Tinebase_Record_Abstract::ISO8601LONG));
916         
917         $date = new Tinebase_DateTime('2009-04-04 00:00:00');
918         $this->assertEquals('2009-04-09 00:00:00', Calendar_Model_Rrule::skipWday($date, Calendar_Model_Rrule::WDAY_THURSDAY)->toString(Tinebase_Record_Abstract::ISO8601LONG));
919         
920         $date = new Tinebase_DateTime('2009-04-04 00:00:00');
921         $this->assertEquals('2009-04-08 00:00:00', Calendar_Model_Rrule::skipWday($date, Calendar_Model_Rrule::WDAY_WEDNESDAY)->toString(Tinebase_Record_Abstract::ISO8601LONG));
922         
923         // $_n == +2
924         $date = new Tinebase_DateTime('2009-04-08 00:00:00');
925         $this->assertEquals('2009-04-19 00:00:00', Calendar_Model_Rrule::skipWday($date, Calendar_Model_Rrule::WDAY_SUNDAY, 2)->toString(Tinebase_Record_Abstract::ISO8601LONG));
926         
927         $date = new Tinebase_DateTime('2009-04-08 00:00:00');
928         $this->assertEquals('2009-04-21 00:00:00', Calendar_Model_Rrule::skipWday($date, Calendar_Model_Rrule::WDAY_TUESDAY, 2)->toString(Tinebase_Record_Abstract::ISO8601LONG));
929         
930         // $_n == -1
931         $date = new Tinebase_DateTime('2009-04-08 00:00:00');
932         $this->assertEquals('2009-04-06 00:00:00', Calendar_Model_Rrule::skipWday($date, Calendar_Model_Rrule::WDAY_MONDAY, -1)->toString(Tinebase_Record_Abstract::ISO8601LONG));
933         
934         $date = new Tinebase_DateTime('2009-04-08 00:00:00');
935         $this->assertEquals('2009-04-02 00:00:00', Calendar_Model_Rrule::skipWday($date, Calendar_Model_Rrule::WDAY_THURSDAY, -1)->toString(Tinebase_Record_Abstract::ISO8601LONG));
936         
937         $date = new Tinebase_DateTime('2009-04-08 00:00:00');
938         $this->assertEquals('2009-04-01 00:00:00', Calendar_Model_Rrule::skipWday($date, Calendar_Model_Rrule::WDAY_WEDNESDAY, -1)->toString(Tinebase_Record_Abstract::ISO8601LONG));
939         
940         $date = new Tinebase_DateTime('2009-04-05 00:00:00');
941         $this->assertEquals('2009-04-04 00:00:00', Calendar_Model_Rrule::skipWday($date, Calendar_Model_Rrule::WDAY_SATURDAY, -1)->toString(Tinebase_Record_Abstract::ISO8601LONG));
942         
943         $date = new Tinebase_DateTime('2009-04-04 00:00:00');
944         $this->assertEquals('2009-03-29 00:00:00', Calendar_Model_Rrule::skipWday($date, Calendar_Model_Rrule::WDAY_SUNDAY, -1)->toString(Tinebase_Record_Abstract::ISO8601LONG));
945         
946         // $_n == -2
947         $date = new Tinebase_DateTime('2009-04-08 00:00:00');
948         $this->assertEquals('2009-03-29 00:00:00', Calendar_Model_Rrule::skipWday($date, Calendar_Model_Rrule::WDAY_SUNDAY, -2)->toString(Tinebase_Record_Abstract::ISO8601LONG));
949         
950         $date = new Tinebase_DateTime('2009-04-08 00:00:00');
951         $this->assertEquals('2009-03-31 00:00:00', Calendar_Model_Rrule::skipWday($date, Calendar_Model_Rrule::WDAY_TUESDAY, -2)->toString(Tinebase_Record_Abstract::ISO8601LONG));
952     
953         // $_considerDateItself == TRUE
954         $date = new Tinebase_DateTime('2009-04-08 00:00:00');
955         $this->assertEquals('2009-04-08 00:00:00', Calendar_Model_Rrule::skipWday($date, Calendar_Model_Rrule::WDAY_WEDNESDAY, 1, TRUE)->toString(Tinebase_Record_Abstract::ISO8601LONG));
956         $this->assertEquals('2009-04-08 00:00:00', Calendar_Model_Rrule::skipWday($date, Calendar_Model_Rrule::WDAY_WEDNESDAY, -1, TRUE)->toString(Tinebase_Record_Abstract::ISO8601LONG));
957     
958         $date = new Tinebase_DateTime('2009-04-08 00:00:00');
959         $this->assertEquals('2009-04-15 00:00:00', Calendar_Model_Rrule::skipWday($date, Calendar_Model_Rrule::WDAY_WEDNESDAY, 2, TRUE)->toString(Tinebase_Record_Abstract::ISO8601LONG));
960         
961         $date = new Tinebase_DateTime('2009-04-08 00:00:00');
962         $this->assertEquals('2009-04-01 00:00:00', Calendar_Model_Rrule::skipWday($date, Calendar_Model_Rrule::WDAY_WEDNESDAY, -2, TRUE)->toString(Tinebase_Record_Abstract::ISO8601LONG));
963         
964     }
965     
966     public function testDatenArrayConverstions()
967     {
968         $date = new Tinebase_DateTime('1979-06-05 11:22:33');
969         $dateArray = array(
970             'day'       => 5 ,
971             'month'     => 6, 
972             'year'      => 1979, 
973             'hour'      => 11, 
974             'minute'    => 22, 
975             'second'    => 33
976         );
977         
978         $this->assertTrue($date->equals(Calendar_Model_Rrule::array2date($dateArray)), 'array2date failed');
979         $this->assertEquals($dateArray, Calendar_Model_Rrule::date2array($date), 'date2array failed');
980     }
981     
982     public function testGetMonthDiff()
983     {
984         $from  = new Tinebase_DateTime('1979-06-05 00:00:00');
985
986         $until = new Tinebase_DateTime('1980-06-05 00:00:00');
987         $this->assertEquals(12, Calendar_Model_Rrule::getMonthDiff($from, $until));
988         
989         $until = new Tinebase_DateTime('1982-07-05 00:00:00');
990         $this->assertEquals(37, Calendar_Model_Rrule::getMonthDiff($from, $until));
991     }
992     
993     public function testAddMonthIngnoringDay()
994     {
995         $date  = new Tinebase_DateTime('2009-01-31 00:00:00');
996         for ($i=0; $i<12; $i++) {
997             $dateArr = Calendar_Model_Rrule::addMonthIgnoringDay($date, $i);
998             $this->assertEquals(31, $dateArr['day']);
999             $this->assertEquals($i+1, $dateArr['month']);
1000             $this->assertEquals(2009, $dateArr['year']);
1001         }
1002         
1003         for ($i=12; $i<24; $i++) {
1004             $dateArr = Calendar_Model_Rrule::addMonthIgnoringDay($date, $i);
1005             $this->assertEquals(31, $dateArr['day']);
1006             $this->assertEquals($i-11, $dateArr['month']);
1007             $this->assertEquals(2010, $dateArr['year']);
1008         }
1009     }
1010     
1011     public function testIsRecurException()
1012     {
1013         $event = new Calendar_Model_Event(array(
1014             'uid'           => '839404a34a8005d2ebd0da68b6aa922460ae945a',
1015             'summary'       => 'conference exception (late)',
1016             'dtstart'       => '2003-03-28 10:00:00',
1017             'dtend'         => '2003-03-28 12:00:00',
1018             'originator_tz' => 'US/Pacific',
1019             'recurid'       => '839404a34a8005d2ebd0da68b6aa922460ae945a-2003-03-28 08:00:00'
1020         ));
1021         
1022         $this->assertTrue($event->isRecurException(), 'recur exception was not detected');
1023         $this->assertEquals('2003-03-28 08:00:00', $event->getOriginalDtStart()->format(Tinebase_Record_Abstract::ISO8601LONG));
1024     }
1025     
1026     public function testAlarmAdjust()
1027     {
1028         $event = new Calendar_Model_Event(array(
1029             'uid'           => Tinebase_Record_Abstract::generateUID(),
1030             'summary'       => 'custom alarm',
1031             'dtstart'       => '2011-12-30 10:30:00',
1032             'dtend'         => '2011-12-30 12:30:00',
1033             'rrule'         => 'FREQ=YEARLY',
1034             'originator_tz' => 'Europe/Berlin',
1035             'alarms'        => array(array(
1036                 'alarm_time'        => '2011-12-20 10:30:00',
1037                 'minutes_before'    => 'custom',
1038                 'options'           => Zend_Json::encode(array(
1039                     'minutes_before' => 14400,
1040                     'recurid'        => NULL,
1041                     'custom'         => TRUE,
1042                 ))
1043             )),
1044             Tinebase_Model_Grants::GRANT_EDIT     => true,
1045         ));
1046         
1047         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
1048         
1049         $from = new Tinebase_DateTime('2012-01-01 00:00:00');
1050         $until = new Tinebase_DateTime('2012-12-31 23:59:59');
1051         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($event, $exceptions, $from, $until);
1052         
1053         $this->assertEquals(1, count($recurSet), 'recur set failed');
1054         $this->assertEquals('2012-12-20 10:30:00', (string) $recurSet->getFirstRecord()->alarms->getFirstRecord()->alarm_time);
1055     }
1056     
1057     public function testGetTranslatedRule()
1058     {
1059         $locale = new Zend_Locale('en');
1060         $translation = Tinebase_Translation::getTranslation('Calendar', $locale);
1061         
1062         $asserts = array(
1063             'FREQ=DAILY;INTERVAL=1'                          => 'Daily',
1064             'FREQ=DAILY;INTERVAL=3'                          => 'Every 3rd day',
1065             'FREQ=WEEKLY;INTERVAL=3;WKST=SU;BYDAY=TU,WE,TH'  => 'Every 3rd week on Tuesday, Wednesday and Thursday',
1066             'FREQ=MONTHLY;INTERVAL=2;BYDAY=-1FR'             => 'Every 2nd month on the last Friday',
1067             'FREQ=MONTHLY;INTERVAL=1;BYDAY=2TH'              => 'Monthly every second Thursday',
1068             'FREQ=MONTHLY;INTERVAL=3;BYMONTHDAY=24'          => 'Every 3rd month on the 24th',
1069             'FREQ=MONTHLY;INTERVAL=1;BYMONTHDAY=5'           => 'Monthly on the 5th',
1070             'FREQ=YEARLY;INTERVAL=1;BYMONTH=7;BYMONTHDAY=10' => 'Yearly on the 10th of July',
1071             );
1072         
1073         foreach($asserts as $rruleString => $expected) {
1074             $translated = Calendar_Model_Rrule::getRruleFromString($rruleString);
1075             $this->assertEquals($translated, $translated);
1076         }
1077     }
1078 }
1079