|
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.