1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
|
4
|
|
|
if (!function_exists('dbg_error_log')) { |
5
|
|
|
|
6
|
|
|
function dbg_error_log() |
7
|
|
|
{ |
8
|
|
|
} |
9
|
|
|
|
10
|
|
|
} |
11
|
|
|
|
12
|
|
|
/** |
13
|
|
|
* Class for parsing RRule and getting us the dates |
14
|
|
|
* |
15
|
|
|
* @package awl |
16
|
|
|
* @subpackage caldav |
17
|
|
|
* @author Andrew McMillan <[email protected]> |
18
|
|
|
* @copyright Catalyst .Net Ltd |
19
|
|
|
* @license http://gnu.org/copyleft/gpl.html GNU GPL v2 |
20
|
|
|
*/ |
21
|
|
|
|
22
|
|
|
$GLOBALS['ical_weekdays'] = array( 'SU' => 0, 'MO' => 1, 'TU' => 2, 'WE' => 3, 'TH' => 4, 'FR' => 5, 'SA' => 6 ); |
23
|
|
|
|
24
|
|
|
if (!class_exists('iCalDate')) { |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* A Class for handling dates in iCalendar format. We do make the simplifying assumption |
28
|
|
|
* that all date handling in here is normalised to GMT. One day we might provide some |
29
|
|
|
* functions to do that, but for now it is done externally. |
30
|
|
|
* |
31
|
|
|
* @package awl |
32
|
|
|
*/ |
33
|
|
|
class iCalDate { |
34
|
|
|
/**#@+ |
35
|
|
|
* @access private |
36
|
|
|
*/ |
37
|
|
|
|
38
|
|
|
/** Text version */ |
39
|
|
|
var $_text; |
40
|
|
|
|
41
|
|
|
/** Epoch version */ |
42
|
|
|
var $_epoch; |
43
|
|
|
|
44
|
|
|
/** Fragmented parts */ |
45
|
|
|
var $_yy; |
46
|
|
|
var $_mo; |
47
|
|
|
var $_dd; |
48
|
|
|
var $_hh; |
49
|
|
|
var $_mi; |
50
|
|
|
var $_ss; |
51
|
|
|
var $_tz; |
52
|
|
|
|
53
|
|
|
/** Which day of the week does the week start on */ |
54
|
|
|
var $_wkst; |
55
|
|
|
|
56
|
|
|
/**#@-*/ |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* The constructor takes either an iCalendar date, a text string formatted as |
60
|
|
|
* an iCalendar date, or epoch seconds. |
61
|
|
|
*/ |
62
|
|
|
function iCalDate( $input ) { |
|
|
|
|
63
|
|
|
if ( gettype($input) == 'object' ) { |
64
|
|
|
$this->_text = $input->_text; |
65
|
|
|
$this->_epoch = $input->_epoch; |
66
|
|
|
$this->_yy = $input->_yy; |
67
|
|
|
$this->_mo = $input->_mo; |
68
|
|
|
$this->_dd = $input->_dd; |
69
|
|
|
$this->_hh = $input->_hh; |
70
|
|
|
$this->_mi = $input->_mi; |
71
|
|
|
$this->_ss = $input->_ss; |
72
|
|
|
$this->_tz = $input->_tz; |
73
|
|
|
return; |
74
|
|
|
} |
75
|
|
|
|
76
|
|
|
$this->_wkst = 1; // Monday |
77
|
|
|
if ( preg_match( '/^\d{8}[T ]\d{6}$/', $input ) ) { |
78
|
|
|
$this->SetLocalDate($input); |
79
|
|
|
} |
80
|
|
|
else if ( preg_match( '/^\d{8}[T ]\d{6}Z$/', $input ) ) { |
81
|
|
|
$this->SetGMTDate($input); |
82
|
|
|
} |
83
|
|
|
else if ( intval($input) == 0 ) { |
84
|
|
|
$this->SetLocalDate(strtotime($input)); |
85
|
|
|
return; |
86
|
|
|
} |
87
|
|
|
else { |
88
|
|
|
$this->SetEpochDate($input); |
89
|
|
|
} |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
|
93
|
|
|
/** |
94
|
|
|
* Set the date from a text string |
95
|
|
|
*/ |
96
|
|
|
function SetGMTDate( $input ) { |
|
|
|
|
97
|
|
|
$this->_text = $input; |
98
|
|
|
$this->_PartsFromText(); |
99
|
|
|
$this->_GMTEpochFromParts(); |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
|
103
|
|
|
/** |
104
|
|
|
* Set the date from a text string |
105
|
|
|
*/ |
106
|
|
|
function SetLocalDate( $input ) { |
|
|
|
|
107
|
|
|
$this->_text = $input; |
108
|
|
|
$this->_PartsFromText(); |
109
|
|
|
$this->_EpochFromParts(); |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
|
113
|
|
|
/** |
114
|
|
|
* Set the date from an epoch |
115
|
|
|
*/ |
116
|
|
|
function SetEpochDate( $input ) { |
|
|
|
|
117
|
|
|
$this->_epoch = intval($input); |
118
|
|
|
$this->_TextFromEpoch(); |
119
|
|
|
$this->_PartsFromText(); |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
|
123
|
|
|
/** |
124
|
|
|
* Given an epoch date, convert it to text |
125
|
|
|
*/ |
126
|
|
|
function _TextFromEpoch() { |
|
|
|
|
127
|
|
|
$this->_text = date('Ymd\THis', $this->_epoch ); |
128
|
|
|
dbg_error_log( "RRule", " Text %s from epoch %d", $this->_text, $this->_epoch ); |
|
|
|
|
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
/** |
132
|
|
|
* Given a GMT epoch date, convert it to text |
133
|
|
|
*/ |
134
|
|
|
function _GMTTextFromEpoch() { |
|
|
|
|
135
|
|
|
$this->_text = gmdate('Ymd\THis', $this->_epoch ); |
136
|
|
|
dbg_error_log( "RRule", " Text %s from epoch %d", $this->_text, $this->_epoch ); |
|
|
|
|
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
/** |
140
|
|
|
* Given a text date, convert it to parts |
141
|
|
|
*/ |
142
|
|
|
function _PartsFromText() { |
|
|
|
|
143
|
|
|
$this->_yy = intval(substr($this->_text,0,4)); |
144
|
|
|
$this->_mo = intval(substr($this->_text,4,2)); |
145
|
|
|
$this->_dd = intval(substr($this->_text,6,2)); |
146
|
|
|
$this->_hh = intval(substr($this->_text,9,2)); |
147
|
|
|
$this->_mi = intval(substr($this->_text,11,2)); |
148
|
|
|
$this->_ss = intval(substr($this->_text,13,2)); |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
|
152
|
|
|
/** |
153
|
|
|
* Given a GMT text date, convert it to an epoch |
154
|
|
|
*/ |
155
|
|
|
function _GMTEpochFromParts() { |
|
|
|
|
156
|
|
|
$this->_epoch = gmmktime ( $this->_hh, $this->_mi, $this->_ss, $this->_mo, $this->_dd, $this->_yy ); |
157
|
|
|
dbg_error_log( "RRule", " Epoch %d from %04d-%02d-%02d %02d:%02d:%02d", $this->_epoch, $this->_yy, $this->_mo, $this->_dd, $this->_hh, $this->_mi, $this->_ss ); |
|
|
|
|
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
|
161
|
|
|
/** |
162
|
|
|
* Given a local text date, convert it to an epoch |
163
|
|
|
*/ |
164
|
|
|
function _EpochFromParts() { |
|
|
|
|
165
|
|
|
$this->_epoch = mktime ( $this->_hh, $this->_mi, $this->_ss, $this->_mo, $this->_dd, $this->_yy ); |
166
|
|
|
dbg_error_log( "RRule", " Epoch %d from %04d-%02d-%02d %02d:%02d:%02d", $this->_epoch, $this->_yy, $this->_mo, $this->_dd, $this->_hh, $this->_mi, $this->_ss ); |
|
|
|
|
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
|
170
|
|
|
/** |
171
|
|
|
* Set the day of week used for calculation of week starts |
172
|
|
|
* |
173
|
|
|
* @param string $weekstart The day of the week which is the first business day. |
174
|
|
|
*/ |
175
|
|
|
function SetWeekStart($weekstart) { |
|
|
|
|
176
|
|
|
global $ical_weekdays; |
177
|
|
|
$this->_wkst = $ical_weekdays[$weekstart]; |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
|
181
|
|
|
/** |
182
|
|
|
* Set the day of week used for calculation of week starts |
183
|
|
|
*/ |
184
|
|
|
function Render( $fmt = 'Y-m-d H:i:s' ) { |
|
|
|
|
185
|
|
|
return date( $fmt, $this->_epoch ); |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
|
189
|
|
|
/** |
190
|
|
|
* Render the date as GMT |
191
|
|
|
*/ |
192
|
|
|
function RenderGMT( $fmt = 'Ymd\THis\Z' ) { |
|
|
|
|
193
|
|
|
return gmdate( $fmt, $this->_epoch ); |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
|
197
|
|
|
/** |
198
|
|
|
* No of days in a month 1(Jan) - 12(Dec) |
199
|
|
|
*/ |
200
|
|
|
function DaysInMonth( $mo=false, $yy=false ) { |
|
|
|
|
201
|
|
|
if ( $mo === false ) $mo = $this->_mo; |
202
|
|
|
switch( $mo ) { |
203
|
|
|
case 1: // January |
204
|
|
|
case 3: // March |
205
|
|
|
case 5: // May |
206
|
|
|
case 7: // July |
207
|
|
|
case 8: // August |
208
|
|
|
case 10: // October |
209
|
|
|
case 12: // December |
210
|
|
|
return 31; |
211
|
|
|
break; |
|
|
|
|
212
|
|
|
|
213
|
|
|
case 4: // April |
214
|
|
|
case 6: // June |
215
|
|
|
case 9: // September |
216
|
|
|
case 11: // November |
217
|
|
|
return 30; |
218
|
|
|
break; |
|
|
|
|
219
|
|
|
|
220
|
|
|
case 2: // February |
221
|
|
|
if ( $yy === false ) $yy = $this->_yy; |
222
|
|
|
if ( (($yy % 4) == 0) && ((($yy % 100) != 0) || (($yy % 400) == 0) ) ) return 29; |
223
|
|
|
return 28; |
224
|
|
|
break; |
|
|
|
|
225
|
|
|
|
226
|
|
|
default: |
227
|
|
|
dbg_error_log( "ERROR"," Invalid month of '%s' passed to DaysInMonth", $mo ); |
|
|
|
|
228
|
|
|
break; |
229
|
|
|
|
230
|
|
|
} |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
|
234
|
|
|
/** |
235
|
|
|
* Set the day in the month to what we have been given |
236
|
|
|
*/ |
237
|
|
|
function SetMonthDay( $dd ) { |
|
|
|
|
238
|
|
|
if ( $dd == $this->_dd ) return; // Shortcut |
239
|
|
|
$dd = min($dd,$this->DaysInMonth()); |
240
|
|
|
$this->_dd = $dd; |
241
|
|
|
$this->_EpochFromParts(); |
242
|
|
|
$this->_TextFromEpoch(); |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
|
246
|
|
|
/** |
247
|
|
|
* Add some number of months to a date |
248
|
|
|
*/ |
249
|
|
|
function AddMonths( $mo ) { |
|
|
|
|
250
|
|
|
dbg_error_log( "RRule", " Adding %d months to %s", $mo, $this->_text ); |
|
|
|
|
251
|
|
|
$this->_mo += $mo; |
252
|
|
|
while ( $this->_mo < 1 ) { |
253
|
|
|
$this->_mo += 12; |
254
|
|
|
$this->_yy--; |
255
|
|
|
} |
256
|
|
|
while ( $this->_mo > 12 ) { |
257
|
|
|
$this->_mo -= 12; |
258
|
|
|
$this->_yy++; |
259
|
|
|
} |
260
|
|
|
|
261
|
|
|
if ( ($this->_dd > 28 && $this->_mo == 2) || $this->_dd > 30 ) { |
262
|
|
|
// Ensure the day of month is still reasonable and coerce to last day of month if needed |
263
|
|
|
$dim = $this->DaysInMonth(); |
264
|
|
|
if ( $this->_dd > $dim ) { |
265
|
|
|
$this->_dd = $dim; |
266
|
|
|
} |
267
|
|
|
} |
268
|
|
|
$this->_EpochFromParts(); |
269
|
|
|
$this->_TextFromEpoch(); |
270
|
|
|
dbg_error_log( "RRule", " Added %d months and got %s", $mo, $this->_text ); |
|
|
|
|
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
|
274
|
|
|
/** |
275
|
|
|
* Add some integer number of days to a date |
276
|
|
|
*/ |
277
|
|
|
function AddDays( $dd ) { |
|
|
|
|
278
|
|
|
$at_start = $this->_text; |
279
|
|
|
$this->_dd += $dd; |
280
|
|
|
while ( 1 > $this->_dd ) { |
281
|
|
|
$this->_mo--; |
282
|
|
|
if ( $this->_mo < 1 ) { |
283
|
|
|
$this->_mo += 12; |
284
|
|
|
$this->_yy--; |
285
|
|
|
} |
286
|
|
|
$this->_dd += $this->DaysInMonth(); |
287
|
|
|
} |
288
|
|
|
while ( ($dim = $this->DaysInMonth($this->_mo)) < $this->_dd ) { |
|
|
|
|
289
|
|
|
$this->_dd -= $dim; |
290
|
|
|
$this->_mo++; |
291
|
|
|
if ( $this->_mo > 12 ) { |
292
|
|
|
$this->_mo -= 12; |
293
|
|
|
$this->_yy++; |
294
|
|
|
} |
295
|
|
|
} |
296
|
|
|
$this->_EpochFromParts(); |
297
|
|
|
$this->_TextFromEpoch(); |
298
|
|
|
dbg_error_log( "RRule", " Added %d days to %s and got %s", $dd, $at_start, $this->_text ); |
|
|
|
|
299
|
|
|
} |
300
|
|
|
|
301
|
|
|
|
302
|
|
|
/** |
303
|
|
|
* Add duration |
304
|
|
|
*/ |
305
|
|
|
function AddDuration( $duration ) { |
|
|
|
|
306
|
|
|
if ( strstr($duration,'T') === false ) $duration .= 'T'; |
307
|
|
|
list( $sign, $days, $time ) = preg_split( '/[PT]/', $duration ); |
308
|
|
|
$sign = ( $sign == "-" ? -1 : 1); |
309
|
|
|
dbg_error_log( "RRule", " Adding duration to '%s' of sign: %d, days: %s, time: %s", $this->_text, $sign, $days, $time ); |
|
|
|
|
310
|
|
|
if ( preg_match( '/(\d+)(D|W)/', $days, $matches ) ) { |
311
|
|
|
$days = intval($matches[1]); |
312
|
|
|
if ( $matches[2] == 'W' ) $days *= 7; |
313
|
|
|
$this->AddDays( $days * $sign ); |
314
|
|
|
} |
315
|
|
|
$hh = 0; $mi = 0; $ss = 0; |
316
|
|
|
if ( preg_match( '/(\d+)(H)/', $time, $matches ) ) $hh = $matches[1]; |
317
|
|
|
if ( preg_match( '/(\d+)(M)/', $time, $matches ) ) $mi = $matches[1]; |
318
|
|
|
if ( preg_match( '/(\d+)(S)/', $time, $matches ) ) $ss = $matches[1]; |
319
|
|
|
|
320
|
|
|
dbg_error_log( "RRule", " Adding %02d:%02d:%02d * %d to %02d:%02d:%02d", $hh, $mi, $ss, $sign, $this->_hh, $this->_mi, $this->_ss ); |
|
|
|
|
321
|
|
|
$this->_hh += ($hh * $sign); |
322
|
|
|
$this->_mi += ($mi * $sign); |
323
|
|
|
$this->_ss += ($ss * $sign); |
324
|
|
|
|
325
|
|
View Code Duplication |
if ( $this->_ss < 0 ) { $this->_mi -= (intval(abs($this->_ss/60))+1); $this->_ss += ((intval(abs($this->_mi/60))+1) * 60); } |
|
|
|
|
326
|
|
View Code Duplication |
if ( $this->_ss > 59) { $this->_mi += (intval(abs($this->_ss/60))+1); $this->_ss -= ((intval(abs($this->_mi/60))+1) * 60); } |
|
|
|
|
327
|
|
View Code Duplication |
if ( $this->_mi < 0 ) { $this->_hh -= (intval(abs($this->_mi/60))+1); $this->_mi += ((intval(abs($this->_mi/60))+1) * 60); } |
|
|
|
|
328
|
|
View Code Duplication |
if ( $this->_mi > 59) { $this->_hh += (intval(abs($this->_mi/60))+1); $this->_mi -= ((intval(abs($this->_mi/60))+1) * 60); } |
|
|
|
|
329
|
|
View Code Duplication |
if ( $this->_hh < 0 ) { $this->AddDays( -1 * (intval(abs($this->_hh/24))+1) ); $this->_hh += ((intval(abs($this->_hh/24))+1)*24); } |
|
|
|
|
330
|
|
View Code Duplication |
if ( $this->_hh > 23) { $this->AddDays( (intval(abs($this->_hh/24))+1) ); $this->_hh -= ((intval(abs($this->_hh/24))+1)*24); } |
|
|
|
|
331
|
|
|
|
332
|
|
|
$this->_EpochFromParts(); |
333
|
|
|
$this->_TextFromEpoch(); |
334
|
|
|
} |
335
|
|
|
|
336
|
|
|
|
337
|
|
|
/** |
338
|
|
|
* Produce an iCalendar format DURATION for the difference between this an another iCalDate |
339
|
|
|
* |
340
|
|
|
* @param date $from The start of the period |
341
|
|
|
* @return string The date difference, as an iCalendar duration format |
342
|
|
|
*/ |
343
|
|
|
function DateDifference( $from ) { |
|
|
|
|
344
|
|
|
if ( !is_object($from) ) { |
345
|
|
|
$from = new iCalDate($from); |
346
|
|
|
} |
347
|
|
|
if ( $from->_epoch < $this->_epoch ) { |
348
|
|
|
/** One way to simplify is to always go for positive differences */ |
349
|
|
|
return( "-". $from->DateDifference( $this ) ); |
|
|
|
|
350
|
|
|
} |
351
|
|
|
// if ( $from->_yy == $this->_yy && $from->_mo == $this->_mo ) { |
352
|
|
|
/** Also somewhat simpler if we can use seconds */ |
353
|
|
|
$diff = $from->_epoch - $this->_epoch; |
354
|
|
|
$result = ""; |
355
|
|
|
if ( $diff >= 86400) { |
356
|
|
|
$result = intval($diff / 86400); |
357
|
|
|
$diff = $diff % 86400; |
358
|
|
|
if ( $diff == 0 && (($result % 7) == 0) ) { |
359
|
|
|
// Duration is an integer number of weeks. |
360
|
|
|
$result .= intval($result / 7) . "W"; |
361
|
|
|
return $result; |
362
|
|
|
} |
363
|
|
|
$result .= "D"; |
364
|
|
|
} |
365
|
|
|
$result = "P".$result."T"; |
366
|
|
|
if ( $diff >= 3600) { |
367
|
|
|
$result .= intval($diff / 3600) . "H"; |
368
|
|
|
$diff = $diff % 3600; |
369
|
|
|
} |
370
|
|
|
if ( $diff >= 60) { |
371
|
|
|
$result .= intval($diff / 60) . "M"; |
372
|
|
|
$diff = $diff % 60; |
373
|
|
|
} |
374
|
|
|
if ( $diff > 0) { |
375
|
|
|
$result .= intval($diff) . "S"; |
376
|
|
|
} |
377
|
|
|
return $result; |
378
|
|
|
// } |
379
|
|
|
|
380
|
|
|
/** |
381
|
|
|
* From an intense reading of RFC2445 it appears that durations which are not expressible |
382
|
|
|
* in Weeks/Days/Hours/Minutes/Seconds are invalid. |
383
|
|
|
* ==> This code is not needed then :-) |
384
|
|
|
$yy = $from->_yy - $this->_yy; |
385
|
|
|
$mo = $from->_mo - $this->_mo; |
386
|
|
|
$dd = $from->_dd - $this->_dd; |
387
|
|
|
$hh = $from->_hh - $this->_hh; |
388
|
|
|
$mi = $from->_mi - $this->_mi; |
389
|
|
|
$ss = $from->_ss - $this->_ss; |
390
|
|
|
|
391
|
|
|
if ( $ss < 0 ) { $mi -= 1; $ss += 60; } |
392
|
|
|
if ( $mi < 0 ) { $hh -= 1; $mi += 60; } |
393
|
|
|
if ( $hh < 0 ) { $dd -= 1; $hh += 24; } |
394
|
|
|
if ( $dd < 0 ) { $mo -= 1; $dd += $this->DaysInMonth(); } // Which will use $this->_(mo|yy) - seemingly sensible |
395
|
|
|
if ( $mo < 0 ) { $yy -= 1; $mo += 12; } |
396
|
|
|
|
397
|
|
|
$result = ""; |
398
|
|
|
if ( $yy > 0) { $result .= $yy."Y"; } |
399
|
|
|
if ( $mo > 0) { $result .= $mo."M"; } |
400
|
|
|
if ( $dd > 0) { $result .= $dd."D"; } |
401
|
|
|
$result .= "T"; |
402
|
|
|
if ( $hh > 0) { $result .= $hh."H"; } |
403
|
|
|
if ( $mi > 0) { $result .= $mi."M"; } |
404
|
|
|
if ( $ss > 0) { $result .= $ss."S"; } |
405
|
|
|
return $result; |
406
|
|
|
*/ |
407
|
|
|
} |
408
|
|
|
|
409
|
|
|
/** |
410
|
|
|
* Test to see if our _mo matches something in the list of months we have received. |
411
|
|
|
* @param string $monthlist A comma-separated list of months. |
412
|
|
|
* @return boolean Whether this date falls within one of those months. |
413
|
|
|
*/ |
414
|
|
|
function TestByMonth( $monthlist ) { |
|
|
|
|
415
|
|
|
dbg_error_log( "RRule", " Testing BYMONTH %s against month %d", (isset($monthlist) ? $monthlist : "no month list"), $this->_mo ); |
|
|
|
|
416
|
|
|
if ( !isset($monthlist) ) return true; // If BYMONTH is not specified any month is OK |
417
|
|
|
$months = array_flip(split( ',',$monthlist )); |
418
|
|
|
return isset($months[$this->_mo]); |
419
|
|
|
} |
420
|
|
|
|
421
|
|
|
/** |
422
|
|
|
* Applies any BYDAY to the month to return a set of days |
423
|
|
|
* @param string $byday The BYDAY rule |
424
|
|
|
* @return array An array of the day numbers for the month which meet the rule. |
425
|
|
|
*/ |
426
|
|
|
function GetMonthByDay($byday) { |
|
|
|
|
427
|
|
|
dbg_error_log( "RRule", " Applying BYDAY %s to month", $byday ); |
|
|
|
|
428
|
|
|
$days_in_month = $this->DaysInMonth(); |
429
|
|
|
$dayrules = split(',',$byday); |
430
|
|
|
$set = array(); |
431
|
|
|
$first_dow = (date('w',$this->_epoch) - $this->_dd + 36) % 7; |
432
|
|
|
foreach( $dayrules AS $k => $v ) { |
433
|
|
|
$days = $this->MonthDays($first_dow,$days_in_month,$v); |
434
|
|
|
foreach( $days AS $k2 => $v2 ) { |
435
|
|
|
$set[$v2] = $v2; |
436
|
|
|
} |
437
|
|
|
} |
438
|
|
|
asort( $set, SORT_NUMERIC ); |
439
|
|
|
return $set; |
440
|
|
|
} |
441
|
|
|
|
442
|
|
|
/** |
443
|
|
|
* Applies any BYMONTHDAY to the month to return a set of days |
444
|
|
|
* @param string $bymonthday The BYMONTHDAY rule |
445
|
|
|
* @return array An array of the day numbers for the month which meet the rule. |
446
|
|
|
*/ |
447
|
|
|
function GetMonthByMonthDay($bymonthday) { |
|
|
|
|
448
|
|
|
dbg_error_log( "RRule", " Applying BYMONTHDAY %s to month", $bymonthday ); |
|
|
|
|
449
|
|
|
$days_in_month = $this->DaysInMonth(); |
450
|
|
|
$dayrules = split(',',$bymonthday); |
451
|
|
|
$set = array(); |
452
|
|
|
foreach( $dayrules AS $k => $v ) { |
453
|
|
|
$v = intval($v); |
454
|
|
|
if ( $v > 0 && $v <= $days_in_month ) $set[$v] = $v; |
455
|
|
|
} |
456
|
|
|
asort( $set, SORT_NUMERIC ); |
457
|
|
|
return $set; |
458
|
|
|
} |
459
|
|
|
|
460
|
|
|
|
461
|
|
|
/** |
462
|
|
|
* Applies any BYDAY to the week to return a set of days |
463
|
|
|
* @param string $byday The BYDAY rule |
464
|
|
|
* @param string $increasing When we are moving by months, we want any day of the week, but when by day we only want to increase. Default false. |
465
|
|
|
* @return array An array of the day numbers for the week which meet the rule. |
466
|
|
|
*/ |
467
|
|
|
function GetWeekByDay($byday, $increasing = false) { |
|
|
|
|
468
|
|
|
global $ical_weekdays; |
469
|
|
|
dbg_error_log( "RRule", " Applying BYDAY %s to week", $byday ); |
|
|
|
|
470
|
|
|
$days = explode(',',$byday); |
471
|
|
|
$dow = date('w',$this->_epoch); |
472
|
|
|
$set = array(); |
473
|
|
|
foreach( $days AS $k => $v ) { |
474
|
|
|
|
475
|
|
|
if (!isset($ical_weekdays[$v])) |
476
|
|
|
{ |
477
|
|
|
continue; |
478
|
|
|
} |
479
|
|
|
|
480
|
|
|
$daynum = $ical_weekdays[$v]; |
481
|
|
|
$dd = $this->_dd - $dow + $daynum; |
482
|
|
|
if ( $daynum < $this->_wkst ) $dd += 7; |
483
|
|
|
if ( $dd > $this->_dd || !$increasing ) $set[$dd] = $dd; |
|
|
|
|
484
|
|
|
} |
485
|
|
|
asort( $set, SORT_NUMERIC ); |
486
|
|
|
|
487
|
|
|
return $set; |
488
|
|
|
} |
489
|
|
|
|
490
|
|
|
|
491
|
|
|
/** |
492
|
|
|
* Test if $this is greater than the date parameter |
493
|
|
|
* @param string $lesser The other date, as a local time string |
494
|
|
|
* @return boolean True if $this > $lesser |
495
|
|
|
*/ |
496
|
|
|
function GreaterThan($lesser) { |
|
|
|
|
497
|
|
|
if ( is_object($lesser) ) { |
498
|
|
|
// dbg_error_log( "RRule", " Comparing %s with %s", $this->_text, $lesser->_text ); |
499
|
|
|
return ( $this->_text > $lesser->_text ); |
500
|
|
|
} |
501
|
|
|
// dbg_error_log( "RRule", " Comparing %s with %s", $this->_text, $lesser ); |
502
|
|
|
return ( $this->_text > $lesser ); // These sorts of dates are designed that way... |
503
|
|
|
} |
504
|
|
|
|
505
|
|
|
|
506
|
|
|
/** |
507
|
|
|
* Test if $this is less than the date parameter |
508
|
|
|
* @param string $greater The other date, as a local time string |
509
|
|
|
* @return boolean True if $this < $greater |
510
|
|
|
*/ |
511
|
|
|
function LessThan($greater) { |
|
|
|
|
512
|
|
|
if ( is_object($greater) ) { |
513
|
|
|
// dbg_error_log( "RRule", " Comparing %s with %s", $this->_text, $greater->_text ); |
514
|
|
|
return ( $this->_text < $greater->_text ); |
515
|
|
|
} |
516
|
|
|
// dbg_error_log( "RRule", " Comparing %s with %s", $this->_text, $greater ); |
517
|
|
|
return ( $this->_text < $greater ); // These sorts of dates are designed that way... |
518
|
|
|
} |
519
|
|
|
|
520
|
|
|
|
521
|
|
|
/** |
522
|
|
|
* Given a MonthDays string like "1MO", "-2WE" return an integer day of the month. |
523
|
|
|
* |
524
|
|
|
* @param string $dow_first The day of week of the first of the month. |
525
|
|
|
* @param string $days_in_month The number of days in the month. |
526
|
|
|
* @param string $dayspec The specification for a month day (or days) which we parse. |
527
|
|
|
* |
528
|
|
|
* @return array An array of the day numbers for the month which meet the rule. |
529
|
|
|
*/ |
530
|
|
|
function &MonthDays($dow_first, $days_in_month, $dayspec) { |
531
|
|
|
global $ical_weekdays; |
532
|
|
|
dbg_error_log( "RRule", "MonthDays: Getting days for '%s'. %d days starting on a %d", $dayspec, $days_in_month, $dow_first ); |
|
|
|
|
533
|
|
|
$set = array(); |
534
|
|
|
preg_match( '/([0-9-]*)(MO|TU|WE|TH|FR|SA|SU)/', $dayspec, $matches); |
535
|
|
|
$numeric = intval($matches[1]); |
536
|
|
|
$dow = $ical_weekdays[$matches[2]]; |
537
|
|
|
|
538
|
|
|
$first_matching_day = 1 + ($dow - $dow_first); |
539
|
|
|
while ( $first_matching_day < 1 ) $first_matching_day += 7; |
540
|
|
|
|
541
|
|
|
dbg_error_log( "RRule", " MonthDays: Looking at %d for first match on (%s/%s), %d for numeric", $first_matching_day, $matches[1], $matches[2], $numeric ); |
|
|
|
|
542
|
|
|
|
543
|
|
|
while( $first_matching_day <= $days_in_month ) { |
544
|
|
|
$set[] = $first_matching_day; |
545
|
|
|
$first_matching_day += 7; |
546
|
|
|
} |
547
|
|
|
|
548
|
|
|
if ( $numeric != 0 ) { |
549
|
|
|
if ( $numeric < 0 ) { |
550
|
|
|
$numeric += count($set); |
551
|
|
|
} |
552
|
|
|
else { |
553
|
|
|
$numeric--; |
554
|
|
|
} |
555
|
|
|
$answer = $set[$numeric]; |
556
|
|
|
$set = array( $answer => $answer ); |
557
|
|
|
} |
558
|
|
|
else { |
559
|
|
|
$answers = $set; |
560
|
|
|
$set = array(); |
561
|
|
|
foreach( $answers AS $k => $v ) { |
562
|
|
|
$set[$v] = $v; |
563
|
|
|
} |
564
|
|
|
} |
565
|
|
|
|
566
|
|
|
// dbg_log_array( "RRule", 'MonthDays', $set, false ); |
567
|
|
|
|
568
|
|
|
return $set; |
569
|
|
|
} |
570
|
|
|
|
571
|
|
|
|
572
|
|
|
/** |
573
|
|
|
* Given set position descriptions like '1', '3', '11', '-3' or '-1' and a set, |
574
|
|
|
* return the subset matching the list of set positions. |
575
|
|
|
* |
576
|
|
|
* @param string $bysplist The list of set positions. |
577
|
|
|
* @param string $set The set of days that we will apply the positions to. |
578
|
|
|
* |
579
|
|
|
* @return array The subset which matches. |
580
|
|
|
*/ |
581
|
|
|
function &ApplyBySetPos($bysplist, $set) { |
582
|
|
|
dbg_error_log( "RRule", " ApplyBySetPos: Applying set position '%s' to set of %d days", $bysplist, count($set) ); |
|
|
|
|
583
|
|
|
$subset = array(); |
584
|
|
|
sort( $set, SORT_NUMERIC ); |
585
|
|
|
$max = count($set); |
586
|
|
|
$positions = split( '[^0-9-]', $bysplist ); |
587
|
|
|
foreach( $positions AS $k => $v ) { |
588
|
|
|
if ( $v < 0 ) { |
589
|
|
|
$v += $max; |
590
|
|
|
} |
591
|
|
|
else { |
592
|
|
|
$v--; |
593
|
|
|
} |
594
|
|
|
$subset[$set[$v]] = $set[$v]; |
595
|
|
|
} |
596
|
|
|
return $subset; |
597
|
|
|
} |
598
|
|
|
} |
599
|
|
|
|
600
|
|
|
} |
601
|
|
|
|
602
|
|
|
|
603
|
|
|
if (!class_exists('RRule')) { |
604
|
|
|
|
605
|
|
|
/** |
606
|
|
|
* A Class for handling Events on a calendar which repeat |
607
|
|
|
* |
608
|
|
|
* Here's the spec, from RFC2445: |
609
|
|
|
* |
610
|
|
|
recur = "FREQ"=freq *( |
611
|
|
|
|
612
|
|
|
; either UNTIL or COUNT may appear in a 'recur', |
613
|
|
|
; but UNTIL and COUNT MUST NOT occur in the same 'recur' |
614
|
|
|
|
615
|
|
|
( ";" "UNTIL" "=" enddate ) / |
616
|
|
|
( ";" "COUNT" "=" 1*DIGIT ) / |
617
|
|
|
|
618
|
|
|
; the rest of these keywords are optional, |
619
|
|
|
; but MUST NOT occur more than once |
620
|
|
|
|
621
|
|
|
( ";" "INTERVAL" "=" 1*DIGIT ) / |
622
|
|
|
( ";" "BYSECOND" "=" byseclist ) / |
623
|
|
|
( ";" "BYMINUTE" "=" byminlist ) / |
624
|
|
|
( ";" "BYHOUR" "=" byhrlist ) / |
625
|
|
|
( ";" "BYDAY" "=" bywdaylist ) / |
626
|
|
|
( ";" "BYMONTHDAY" "=" bymodaylist ) / |
627
|
|
|
( ";" "BYYEARDAY" "=" byyrdaylist ) / |
628
|
|
|
( ";" "BYWEEKNO" "=" bywknolist ) / |
629
|
|
|
( ";" "BYMONTH" "=" bymolist ) / |
630
|
|
|
( ";" "BYSETPOS" "=" bysplist ) / |
631
|
|
|
( ";" "WKST" "=" weekday ) / |
632
|
|
|
( ";" x-name "=" text ) |
633
|
|
|
) |
634
|
|
|
|
635
|
|
|
freq = "SECONDLY" / "MINUTELY" / "HOURLY" / "DAILY" |
636
|
|
|
/ "WEEKLY" / "MONTHLY" / "YEARLY" |
637
|
|
|
|
638
|
|
|
enddate = date |
639
|
|
|
enddate =/ date-time ;An UTC value |
640
|
|
|
|
641
|
|
|
byseclist = seconds / ( seconds *("," seconds) ) |
642
|
|
|
|
643
|
|
|
seconds = 1DIGIT / 2DIGIT ;0 to 59 |
644
|
|
|
|
645
|
|
|
byminlist = minutes / ( minutes *("," minutes) ) |
646
|
|
|
|
647
|
|
|
minutes = 1DIGIT / 2DIGIT ;0 to 59 |
648
|
|
|
|
649
|
|
|
byhrlist = hour / ( hour *("," hour) ) |
650
|
|
|
|
651
|
|
|
hour = 1DIGIT / 2DIGIT ;0 to 23 |
652
|
|
|
|
653
|
|
|
bywdaylist = weekdaynum / ( weekdaynum *("," weekdaynum) ) |
654
|
|
|
|
655
|
|
|
weekdaynum = [([plus] ordwk / minus ordwk)] weekday |
656
|
|
|
|
657
|
|
|
plus = "+" |
658
|
|
|
|
659
|
|
|
minus = "-" |
660
|
|
|
|
661
|
|
|
ordwk = 1DIGIT / 2DIGIT ;1 to 53 |
662
|
|
|
|
663
|
|
|
weekday = "SU" / "MO" / "TU" / "WE" / "TH" / "FR" / "SA" |
664
|
|
|
;Corresponding to SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, |
665
|
|
|
;FRIDAY, SATURDAY and SUNDAY days of the week. |
666
|
|
|
|
667
|
|
|
bymodaylist = monthdaynum / ( monthdaynum *("," monthdaynum) ) |
668
|
|
|
|
669
|
|
|
monthdaynum = ([plus] ordmoday) / (minus ordmoday) |
670
|
|
|
|
671
|
|
|
ordmoday = 1DIGIT / 2DIGIT ;1 to 31 |
672
|
|
|
|
673
|
|
|
byyrdaylist = yeardaynum / ( yeardaynum *("," yeardaynum) ) |
674
|
|
|
|
675
|
|
|
yeardaynum = ([plus] ordyrday) / (minus ordyrday) |
676
|
|
|
|
677
|
|
|
ordyrday = 1DIGIT / 2DIGIT / 3DIGIT ;1 to 366 |
678
|
|
|
|
679
|
|
|
bywknolist = weeknum / ( weeknum *("," weeknum) ) |
680
|
|
|
|
681
|
|
|
weeknum = ([plus] ordwk) / (minus ordwk) |
682
|
|
|
|
683
|
|
|
bymolist = monthnum / ( monthnum *("," monthnum) ) |
684
|
|
|
|
685
|
|
|
monthnum = 1DIGIT / 2DIGIT ;1 to 12 |
686
|
|
|
|
687
|
|
|
bysplist = setposday / ( setposday *("," setposday) ) |
688
|
|
|
|
689
|
|
|
setposday = yeardaynum |
690
|
|
|
* |
691
|
|
|
* At this point we are going to restrict ourselves to parts of the RRULE specification |
692
|
|
|
* seen in the wild. And by "in the wild" I don't include within people's timezone |
693
|
|
|
* definitions. We always convert time zones to canonical names and assume the lower |
694
|
|
|
* level libraries can do a better job with them than we can. |
695
|
|
|
* |
696
|
|
|
* We will concentrate on: |
697
|
|
|
* FREQ=(YEARLY|MONTHLY|WEEKLY|DAILY) |
698
|
|
|
* UNTIL= |
699
|
|
|
* COUNT= |
700
|
|
|
* INTERVAL= |
701
|
|
|
* BYDAY= |
702
|
|
|
* BYMONTHDAY= |
703
|
|
|
* BYSETPOS= |
704
|
|
|
* WKST= |
705
|
|
|
* BYYEARDAY= |
706
|
|
|
* BYWEEKNO= |
707
|
|
|
* BYMONTH= |
708
|
|
|
* |
709
|
|
|
* |
710
|
|
|
* @package awl |
711
|
|
|
*/ |
712
|
|
|
class RRule { |
713
|
|
|
/**#@+ |
714
|
|
|
* @access private |
715
|
|
|
*/ |
716
|
|
|
|
717
|
|
|
/** The first instance */ |
718
|
|
|
var $_first; |
719
|
|
|
|
720
|
|
|
/** The current instance pointer */ |
721
|
|
|
var $_current; |
722
|
|
|
|
723
|
|
|
/** An array of all the dates so far */ |
724
|
|
|
var $_dates; |
725
|
|
|
|
726
|
|
|
/** Whether we have calculated any of the dates */ |
727
|
|
|
var $_started; |
728
|
|
|
|
729
|
|
|
/** Whether we have calculated all of the dates */ |
730
|
|
|
var $_finished; |
731
|
|
|
|
732
|
|
|
/** The rule, in all it's glory */ |
733
|
|
|
var $_rule; |
734
|
|
|
|
735
|
|
|
/** The rule, in all it's parts */ |
736
|
|
|
var $_part; |
737
|
|
|
|
738
|
|
|
/**#@-*/ |
739
|
|
|
|
740
|
|
|
/** |
741
|
|
|
* The constructor takes a start date and an RRULE definition. Both of these |
742
|
|
|
* follow the iCalendar standard. |
743
|
|
|
*/ |
744
|
|
|
function RRule( $start, $rrule ) { |
|
|
|
|
745
|
|
|
$this->_first = new iCalDate($start); |
746
|
|
|
$this->_finished = false; |
747
|
|
|
$this->_started = false; |
748
|
|
|
$this->_dates = array(); |
749
|
|
|
$this->_current = -1; |
750
|
|
|
|
751
|
|
|
$this->_rule = preg_replace( '/\s/m', '', $rrule); |
752
|
|
|
if ( substr($this->_rule, 0, 6) == 'RRULE:' ) { |
753
|
|
|
$this->_rule = substr($this->_rule, 6); |
754
|
|
|
} |
755
|
|
|
|
756
|
|
|
dbg_error_log( "RRule", " new RRule: Start: %s, RRULE: %s", $start->Render(), $this->_rule ); |
|
|
|
|
757
|
|
|
|
758
|
|
|
$parts = explode(';',$this->_rule); |
759
|
|
|
$this->_part = array( 'INTERVAL' => 1 ); |
760
|
|
|
foreach( $parts AS $k => $v ) { |
761
|
|
|
$arr = explode( '=', $v, 2); |
762
|
|
|
if (2 !== count($arr)) |
763
|
|
|
{ |
764
|
|
|
trigger_error(sprintf('Error for parameter %s in RRULE : %s', $v, $this->_rule)); |
765
|
|
|
continue; |
766
|
|
|
} |
767
|
|
|
list( $type, $value ) = $arr; |
768
|
|
|
dbg_error_log( "RRule", " Parts of %s split into %s and %s", $v, $type, $value ); |
|
|
|
|
769
|
|
|
$this->_part[$type] = $value; |
770
|
|
|
} |
771
|
|
|
|
772
|
|
|
// A little bit of validation |
773
|
|
|
if ( !isset($this->_part['FREQ']) ) { |
774
|
|
|
dbg_error_log( "ERROR", " RRULE MUST have FREQ=value (%s)", $rrule ); |
|
|
|
|
775
|
|
|
} |
776
|
|
View Code Duplication |
if ( isset($this->_part['COUNT']) && isset($this->_part['UNTIL']) ) { |
|
|
|
|
777
|
|
|
dbg_error_log( "ERROR", " RRULE MUST NOT have both COUNT=value and UNTIL=value (%s)", $rrule ); |
|
|
|
|
778
|
|
|
} |
779
|
|
View Code Duplication |
if ( isset($this->_part['COUNT']) && intval($this->_part['COUNT']) < 1 ) { |
|
|
|
|
780
|
|
|
dbg_error_log( "ERROR", " RRULE MUST NOT have both COUNT=value and UNTIL=value (%s)", $rrule ); |
|
|
|
|
781
|
|
|
} |
782
|
|
|
if ( !preg_match( '/(YEAR|MONTH|WEEK|DAI)LY/', $this->_part['FREQ']) ) { |
783
|
|
|
dbg_error_log( "ERROR", " RRULE Only FREQ=(YEARLY|MONTHLY|WEEKLY|DAILY) are supported at present (%s)", $rrule ); |
|
|
|
|
784
|
|
|
} |
785
|
|
|
if ( $this->_part['FREQ'] == "YEARLY" ) { |
786
|
|
|
$this->_part['INTERVAL'] *= 12; |
787
|
|
|
$this->_part['FREQ'] = "MONTHLY"; |
788
|
|
|
} |
789
|
|
|
} |
790
|
|
|
|
791
|
|
|
|
792
|
|
|
/** |
793
|
|
|
* Processes the array of $relative_days to $base and removes any |
794
|
|
|
* which are not within the scope of our rule. |
795
|
|
|
*/ |
796
|
|
|
function WithinScope( $base, $relative_days ) { |
|
|
|
|
797
|
|
|
|
798
|
|
|
$ok_days = array(); |
799
|
|
|
|
800
|
|
|
$ptr = $this->_current; |
801
|
|
|
|
802
|
|
|
// dbg_error_log( "RRule", " WithinScope: Processing list of %d days relative to %s", count($relative_days), $base->Render() ); |
803
|
|
|
foreach( $relative_days AS $day => $v ) { |
804
|
|
|
|
805
|
|
|
$test = new iCalDate($base); |
806
|
|
|
$days_in_month = $test->DaysInMonth(); |
807
|
|
|
|
808
|
|
|
// dbg_error_log( "RRule", " WithinScope: Testing for day %d based on %s, with %d days in month", $day, $test->Render(), $days_in_month ); |
809
|
|
|
if ( $day > $days_in_month ) { |
810
|
|
|
$test->SetMonthDay($days_in_month); |
811
|
|
|
$test->AddDays(1); |
812
|
|
|
$day -= $days_in_month; |
813
|
|
|
$test->SetMonthDay($day); |
814
|
|
|
} |
815
|
|
|
else if ( $day < 1 ) { |
816
|
|
|
$test->SetMonthDay(1); |
817
|
|
|
$test->AddDays(-1); |
818
|
|
|
$days_in_month = $test->DaysInMonth(); |
819
|
|
|
$day += $days_in_month; |
820
|
|
|
$test->SetMonthDay($day); |
821
|
|
|
} |
822
|
|
|
else { |
823
|
|
|
$test->SetMonthDay($day); |
824
|
|
|
} |
825
|
|
|
|
826
|
|
|
dbg_error_log( "RRule", " WithinScope: Testing if %s is within scope", count($relative_days), $test->Render() ); |
|
|
|
|
827
|
|
|
|
828
|
|
|
if ( isset($this->_part['UNTIL']) && $test->GreaterThan($this->_part['UNTIL']) ) { |
829
|
|
|
$this->_finished = true; |
830
|
|
|
return $ok_days; |
831
|
|
|
} |
832
|
|
|
|
833
|
|
|
// if ( $this->_current >= 0 && $test->LessThan($this->_dates[$this->_current]) ) continue; |
834
|
|
|
|
835
|
|
|
if ( !$test->LessThan($this->_first) ) { |
|
|
|
|
836
|
|
|
dbg_error_log( "RRule", " WithinScope: Looks like %s is within scope", $test->Render() ); |
|
|
|
|
837
|
|
|
$ok_days[$day] = $test; |
838
|
|
|
$ptr++; |
839
|
|
|
} |
840
|
|
|
|
841
|
|
|
if ( isset($this->_part['COUNT']) && $ptr >= $this->_part['COUNT'] ) { |
842
|
|
|
$this->_finished = true; |
843
|
|
|
return $ok_days; |
844
|
|
|
} |
845
|
|
|
|
846
|
|
|
} |
847
|
|
|
|
848
|
|
|
return $ok_days; |
849
|
|
|
} |
850
|
|
|
|
851
|
|
|
|
852
|
|
|
/** |
853
|
|
|
* This is most of the meat of the RRULE processing, where we find the next date. |
854
|
|
|
* We maintain an |
855
|
|
|
*/ |
856
|
|
|
function &GetNext( ) { |
857
|
|
|
|
858
|
|
|
if ( $this->_current < 0 ) { |
859
|
|
|
$next = new iCalDate($this->_first); |
860
|
|
|
$this->_current++; |
861
|
|
|
} |
862
|
|
|
else { |
863
|
|
|
$next = new iCalDate($this->_dates[$this->_current]); |
864
|
|
|
$this->_current++; |
865
|
|
|
|
866
|
|
|
/** |
867
|
|
|
* If we have already found some dates we may just be able to return one of those. |
868
|
|
|
*/ |
869
|
|
|
if ( isset($this->_dates[$this->_current]) ) { |
870
|
|
|
dbg_error_log( "RRule", " GetNext: Returning %s, (%d'th)", $this->_dates[$this->_current]->Render(), $this->_current ); |
|
|
|
|
871
|
|
|
return $this->_dates[$this->_current]; |
872
|
|
|
} |
873
|
|
|
else { |
874
|
|
|
if ( isset($this->_part['COUNT']) && $this->_current >= $this->_part['COUNT'] ) // >= since _current is 0-based and COUNT is 1-based |
875
|
|
|
$this->_finished = true; |
876
|
|
|
} |
877
|
|
|
} |
878
|
|
|
|
879
|
|
|
if ( $this->_finished ) { |
880
|
|
|
$next = null; |
881
|
|
|
return $next; |
882
|
|
|
} |
883
|
|
|
|
884
|
|
|
$days = array(); |
885
|
|
|
if ( isset($this->_part['WKST']) ) $next->SetWeekStart($this->_part['WKST']); |
886
|
|
|
if ( $this->_part['FREQ'] == "MONTHLY" ) { |
887
|
|
|
dbg_error_log( "RRule", " GetNext: Calculating more dates for MONTHLY rule" ); |
|
|
|
|
888
|
|
|
$limit = 200; |
889
|
|
|
do { |
890
|
|
|
$limit--; |
891
|
|
|
do { |
892
|
|
|
$limit--; |
893
|
|
|
if ( $this->_started ) { |
894
|
|
|
$next->AddMonths($this->_part['INTERVAL']); |
895
|
|
|
} |
896
|
|
|
else { |
897
|
|
|
$this->_started = true; |
898
|
|
|
} |
899
|
|
|
} |
900
|
|
|
while ( isset($this->_part['BYMONTH']) && $limit > 0 && ! $next->TestByMonth($this->_part['BYMONTH']) ); |
901
|
|
|
|
902
|
|
|
if ( isset($this->_part['BYDAY']) ) { |
903
|
|
|
$days = $next->GetMonthByDay($this->_part['BYDAY']); |
904
|
|
|
} |
905
|
|
|
else if ( isset($this->_part['BYMONTHDAY']) ) { |
906
|
|
|
$days = $next->GetMonthByMonthDay($this->_part['BYMONTHDAY']); |
907
|
|
|
} |
908
|
|
|
else |
909
|
|
|
$days[$next->_dd] = $next->_dd; |
910
|
|
|
|
911
|
|
|
if ( isset($this->_part['BYSETPOS']) ) { |
912
|
|
|
$days = $next->ApplyBySetpos($this->_part['BYSETPOS'], $days); |
|
|
|
|
913
|
|
|
} |
914
|
|
|
|
915
|
|
|
$days = $this->WithinScope( $next, $days); |
916
|
|
|
} |
917
|
|
|
while( $limit && count($days) < 1 && ! $this->_finished ); |
918
|
|
|
dbg_error_log( "RRule", " GetNext: Found %d days for MONTHLY rule", count($days) ); |
|
|
|
|
919
|
|
|
|
920
|
|
|
} |
921
|
|
|
else if ( $this->_part['FREQ'] == "WEEKLY" ) { |
922
|
|
|
dbg_error_log( "RRule", " GetNext: Calculating more dates for WEEKLY rule" ); |
|
|
|
|
923
|
|
|
$limit = 200; |
924
|
|
View Code Duplication |
do { |
|
|
|
|
925
|
|
|
$limit--; |
926
|
|
|
if ( $this->_started ) { |
927
|
|
|
$next->AddDays($this->_part['INTERVAL'] * 7); |
928
|
|
|
} |
929
|
|
|
else { |
930
|
|
|
$this->_started = true; |
931
|
|
|
} |
932
|
|
|
|
933
|
|
|
if ( isset($this->_part['BYDAY']) ) { |
934
|
|
|
$days = $next->GetWeekByDay($this->_part['BYDAY'], false ); |
935
|
|
|
} |
936
|
|
|
else |
937
|
|
|
$days[$next->_dd] = $next->_dd; |
938
|
|
|
|
939
|
|
|
if ( isset($this->_part['BYSETPOS']) ) { |
940
|
|
|
$days = $next->ApplyBySetpos($this->_part['BYSETPOS'], $days); |
|
|
|
|
941
|
|
|
} |
942
|
|
|
|
943
|
|
|
$days = $this->WithinScope( $next, $days); |
944
|
|
|
} |
945
|
|
|
while( $limit && count($days) < 1 && ! $this->_finished ); |
946
|
|
|
|
947
|
|
|
dbg_error_log( "RRule", " GetNext: Found %d days for WEEKLY rule", count($days) ); |
|
|
|
|
948
|
|
|
} |
949
|
|
|
else if ( $this->_part['FREQ'] == "DAILY" ) { |
950
|
|
|
dbg_error_log( "RRule", " GetNext: Calculating more dates for DAILY rule" ); |
|
|
|
|
951
|
|
|
$limit = 100; |
952
|
|
View Code Duplication |
do { |
|
|
|
|
953
|
|
|
$limit--; |
954
|
|
|
if ( $this->_started ) { |
955
|
|
|
$next->AddDays($this->_part['INTERVAL']); |
956
|
|
|
} |
957
|
|
|
|
958
|
|
|
if ( isset($this->_part['BYDAY']) ) { |
959
|
|
|
$days = $next->GetWeekByDay($this->_part['BYDAY'], $this->_started ); |
|
|
|
|
960
|
|
|
} |
961
|
|
|
else |
962
|
|
|
$days[$next->_dd] = $next->_dd; |
963
|
|
|
|
964
|
|
|
if ( isset($this->_part['BYSETPOS']) ) { |
965
|
|
|
$days = $next->ApplyBySetpos($this->_part['BYSETPOS'], $days); |
|
|
|
|
966
|
|
|
} |
967
|
|
|
|
968
|
|
|
$days = $this->WithinScope( $next, $days); |
969
|
|
|
$this->_started = true; |
970
|
|
|
} |
971
|
|
|
while( $limit && count($days) < 1 && ! $this->_finished ); |
972
|
|
|
|
973
|
|
|
dbg_error_log( "RRule", " GetNext: Found %d days for DAILY rule", count($days) ); |
|
|
|
|
974
|
|
|
} |
975
|
|
|
|
976
|
|
|
$ptr = $this->_current; |
977
|
|
|
foreach( $days AS $k => $v ) { |
978
|
|
|
$this->_dates[$ptr++] = $v; |
979
|
|
|
} |
980
|
|
|
|
981
|
|
|
if ( isset($this->_dates[$this->_current]) ) { |
982
|
|
|
dbg_error_log( "RRule", " GetNext: Returning %s, (%d'th)", $this->_dates[$this->_current]->Render(), $this->_current ); |
|
|
|
|
983
|
|
|
return $this->_dates[$this->_current]; |
984
|
|
|
} |
985
|
|
|
else { |
986
|
|
|
dbg_error_log( "RRule", " GetNext: Returning null date" ); |
|
|
|
|
987
|
|
|
$next = null; |
988
|
|
|
return $next; |
989
|
|
|
} |
990
|
|
|
} |
991
|
|
|
|
992
|
|
|
} |
993
|
|
|
|
994
|
|
|
} |
995
|
|
|
|
Adding explicit visibility (
private
,protected
, orpublic
) is generally recommend to communicate to other developers how, and from where this method is intended to be used.