Révision 599a39cd
Ajouté par Assos Assos il y a environ 3 ans
drupal7/sites/all/modules/date/date_repeat/date_repeat_calc.inc | ||
---|---|---|
1 | 1 |
<?php |
2 |
|
|
2 | 3 |
/** |
3 | 4 |
* @file |
4 | 5 |
* Code to compute the dates that match an iCal RRULE. |
... | ... | |
53 | 54 |
// Create a date object for the start and end dates. |
54 | 55 |
$start_date = new DateObject($start, $timezone); |
55 | 56 |
|
56 |
// Versions of PHP greater than PHP 5.3.5 require |
|
57 |
// that we set an explicit time when using date_modify() |
|
58 |
// or the time may not match the original value. |
|
59 |
// Adding this modifier gives us the same results in both older |
|
60 |
// and newer versions of PHP. |
|
57 |
// Versions of PHP greater than PHP 5.3.5 require that we set an explicit |
|
58 |
// time when using date_modify() or the time may not match the original value. |
|
59 |
// Adding this modifier gives us the same results in both older and newer |
|
60 |
// versions of PHP. |
|
61 | 61 |
$modify_time = ' ' . $start_date->format('g:ia'); |
62 | 62 |
|
63 | 63 |
// If the rule has an UNTIL, see if that is earlier than the end date. |
... | ... | |
122 | 122 |
} |
123 | 123 |
$rrule = date_repeat_adjust_rrule($rrule, $start_date); |
124 | 124 |
|
125 |
// The start date always goes into the results, whether or not it meets |
|
126 |
// the rules. RFC 2445 includes examples where the start date DOES NOT
|
|
127 |
// meet the rules, but the expected results always include the start date.
|
|
125 |
// The start date always goes into the results, whether or not it meets the
|
|
126 |
// rules. RFC 2445 includes examples where the start date DOES NOT meet the
|
|
127 |
// rules, but the expected results always include the start date. |
|
128 | 128 |
$days = array(date_format($start_date, DATE_FORMAT_DATETIME)); |
129 | 129 |
|
130 | 130 |
// BYMONTHDAY will look for specific days of the month in one or more months. |
131 | 131 |
// This process is only valid when frequency is monthly or yearly. |
132 |
|
|
133 | 132 |
if (!empty($rrule['BYMONTHDAY'])) { |
134 | 133 |
$finished = FALSE; |
135 |
$current_day = clone($start_date);
|
|
134 |
$current_day = clone $start_date;
|
|
136 | 135 |
$direction_days = array(); |
137 | 136 |
// Deconstruct the day in case it has a negative modifier. |
138 | 137 |
foreach ($rrule['BYMONTHDAY'] as $day) { |
... | ... | |
187 | 186 |
} |
188 | 187 |
} |
189 | 188 |
|
190 |
// This is the simple fallback case, not looking for any BYDAY, |
|
191 |
// just repeating the start date. Because of imputed BYDAY above, this |
|
192 |
// will only test TRUE for a DAILY or less frequency (like HOURLY). |
|
193 |
|
|
189 |
// This is the simple fallback case, not looking for any BYDAY, just |
|
190 |
// repeating the start date. Because of imputed BYDAY above, this will only |
|
191 |
// test TRUE for a DAILY or less frequency (like HOURLY). |
|
194 | 192 |
elseif (empty($rrule['BYDAY'])) { |
195 | 193 |
// $current_day will keep track of where we are in the calculation. |
196 |
$current_day = clone($start_date);
|
|
194 |
$current_day = clone $start_date;
|
|
197 | 195 |
$finished = FALSE; |
198 | 196 |
$months = !empty($rrule['BYMONTH']) ? $rrule['BYMONTH'] : array(); |
199 | 197 |
while (!$finished) { |
... | ... | |
204 | 202 |
} |
205 | 203 |
|
206 | 204 |
else { |
207 |
|
|
208 |
// More complex searches for day names and criteria |
|
209 |
// like '-1SU' or '2TU,2TH', require that we interate through |
|
210 |
// the whole time period checking each BYDAY. |
|
211 |
|
|
212 |
// Create helper array to pull day names out of iCal day strings. |
|
205 |
// More complex searches for day names and criteria like '-1SU' or |
|
206 |
// '2TU,2TH', require that we interate through the whole time period |
|
207 |
// checking each BYDAY. Create helper array to pull day names out of iCal |
|
208 |
// day strings. |
|
213 | 209 |
$day_names = date_repeat_dow_day_options(FALSE); |
214 | 210 |
$days_of_week = array_keys($day_names); |
215 | 211 |
|
216 |
// Parse out information about the BYDAYs and separate them |
|
217 |
// depending on whether they have directional parameters like -1SU or 2TH.
|
|
212 |
// Parse out information about the BYDAYs and separate them depending on
|
|
213 |
// whether they have directional parameters like -1SU or 2TH. |
|
218 | 214 |
$month_days = array(); |
219 | 215 |
$week_days = array(); |
220 | 216 |
|
... | ... | |
223 | 219 |
$week_start_rule = !empty($rrule['WKST']) ? trim($rrule['WKST']) : 'MO'; |
224 | 220 |
$week_start_day = $day_names[$week_start_rule]; |
225 | 221 |
|
226 |
// Make sure the week days array is sorted into week order, |
|
227 |
// we use the $ordered_keys to get the right values into the key
|
|
228 |
// and force the array to that order. Needed later when we
|
|
229 |
// iterate through each week looking for days so we don't
|
|
230 |
// jump to the next week when we hit a day out of order.
|
|
222 |
// Make sure the week days array is sorted into week order, we use the
|
|
223 |
// $ordered_keys to get the right values into the key and force the array
|
|
224 |
// to that order. Needed later when we iterate through each week looking
|
|
225 |
// for days so we don't jump to the next week when we hit a day out of
|
|
226 |
// order. |
|
231 | 227 |
$ordered = date_repeat_days_ordered($week_start_rule); |
232 | 228 |
$ordered_keys = array_flip($ordered); |
233 | 229 |
|
... | ... | |
236 | 232 |
foreach ($rrule['BYMONTH'] as $month) { |
237 | 233 |
foreach ($rrule['BYDAY'] as $day) { |
238 | 234 |
preg_match("@(-)?([0-9]+)?([SU|MO|TU|WE|TH|FR|SA]{2})@", trim($day), $regs); |
239 |
// Convert parameters into full day name, count, and direction. |
|
240 |
// Add leading zero to first 9 months.
|
|
235 |
// Convert parameters into full day name, count, and direction. Add
|
|
236 |
// leading zero to first 9 months. |
|
241 | 237 |
if (!empty($regs[2])) { |
242 | 238 |
$direction_days[] = array( |
243 | 239 |
'day' => $day_names[$regs[3]], |
... | ... | |
271 | 267 |
} |
272 | 268 |
ksort($week_days); |
273 | 269 |
|
274 |
// BYDAYs with parameters like -1SU (last Sun) or 2TH (second Thur) |
|
275 |
// need to be processed one month or year at a time.
|
|
270 |
// BYDAYs with parameters like -1SU (last Sun) or 2TH (second Thur) need to
|
|
271 |
// be processed one month or year at a time. |
|
276 | 272 |
if (!empty($direction_days) && in_array($rrule['FREQ'], array('MONTHLY', 'YEARLY'))) { |
277 | 273 |
$finished = FALSE; |
278 |
$current_day = clone($start_date);
|
|
274 |
$current_day = clone $start_date;
|
|
279 | 275 |
while (!$finished) { |
280 | 276 |
foreach ($direction_days as $day) { |
281 | 277 |
// Find the BYDAY date in the current month. |
... | ... | |
288 | 284 |
date_repeat_add_dates($days, $current_day, $start_date, $end_date, $exceptions, $rrule); |
289 | 285 |
} |
290 | 286 |
$finished = date_repeat_is_finished($current_day, $days, $count, $end_date); |
291 |
// Reset to beginning of period before jumping to next period. |
|
292 |
// Needed especially when working with values like 'last Saturday'
|
|
293 |
// to be sure we don't skip months like February.
|
|
287 |
// Reset to beginning of period before jumping to next period. Needed
|
|
288 |
// especially when working with values like 'last Saturday' to be sure
|
|
289 |
// we don't skip months like February. |
|
294 | 290 |
$year = date_format($current_day, 'Y'); |
295 | 291 |
$month = date_format($current_day, 'n'); |
296 | 292 |
if ($rrule['FREQ'] == 'MONTHLY') { |
... | ... | |
304 | 300 |
} |
305 | 301 |
} |
306 | 302 |
|
307 |
// For BYDAYs without parameters,like TU,TH (every Tues and Thur), |
|
308 |
// we look for every one of those days during the frequency period. |
|
309 |
// Iterate through periods of a WEEK, MONTH, or YEAR, checking for |
|
310 |
// the days of the week that match our criteria for each week in the |
|
311 |
// period, then jumping ahead to the next week, month, or year, |
|
312 |
// an INTERVAL at a time. |
|
313 |
|
|
303 |
// For BYDAYs without parameters,like TU,TH (every Tues and Thur), we look |
|
304 |
// for every one of those days during the frequency period. Iterate through |
|
305 |
// periods of a WEEK, MONTH, or YEAR, checking for the days of the week |
|
306 |
// that match our criteria for each week in the period, then jumping ahead |
|
307 |
// to the next week, month, or year, an INTERVAL at a time. |
|
314 | 308 |
if (!empty($week_days) && |
315 | 309 |
in_array($rrule['FREQ'], array('MONTHLY', 'WEEKLY', 'YEARLY'))) { |
316 | 310 |
$finished = FALSE; |
317 |
$current_day = clone($start_date);
|
|
311 |
$current_day = clone $start_date;
|
|
318 | 312 |
$format = $rrule['FREQ'] == 'YEARLY' ? 'Y' : 'n'; |
319 | 313 |
$current_period = date_format($current_day, $format); |
320 | 314 |
|
321 | 315 |
// Back up to the beginning of the week in case we are somewhere in the |
322 |
// middle of the possible week days, needed so we don't prematurely |
|
323 |
// jump to the next week. The date_repeat_add_dates() function will
|
|
324 |
// keep dates outside the range from getting added.
|
|
316 |
// middle of the possible week days, needed so we don't prematurely jump
|
|
317 |
// to the next week. The date_repeat_add_dates() function will keep dates
|
|
318 |
// outside the range from getting added. |
|
325 | 319 |
if (date_format($current_day, 'l') != $day_names[$day]) { |
326 | 320 |
date_modify($current_day, '-1 ' . $week_start_day . $modify_time); |
327 | 321 |
} |
... | ... | |
330 | 324 |
while (!$period_finished) { |
331 | 325 |
$moved = FALSE; |
332 | 326 |
foreach ($week_days as $delta => $day) { |
333 |
// Find the next occurence of each day in this week, only add it |
|
334 |
// if we are still in the current month or year. |
|
335 |
// The date_repeat_add_dates function is insufficient |
|
336 |
// to test whether to include this date |
|
337 |
// if we are using a rule like 'every other month', so we must |
|
338 |
// explicitly test it here. |
|
339 |
|
|
340 |
// If we're already on the right day, don't jump or we |
|
341 |
// will prematurely move into the next week. |
|
327 |
// Find the next occurence of each day in this week, only add it if |
|
328 |
// we are still in the current month or year. The |
|
329 |
// date_repeat_add_dates() function is insufficient to test whether |
|
330 |
// to include this date if we are using a rule like 'every other |
|
331 |
// month', so we must explicitly test it here. If we're already on |
|
332 |
// the right day, don't jump or we will prematurely move into the |
|
333 |
// next week. |
|
342 | 334 |
if (date_format($current_day, 'l') != $day) { |
343 | 335 |
date_modify($current_day, '+1 ' . $day . $modify_time); |
344 | 336 |
$moved = TRUE; |
... | ... | |
349 | 341 |
} |
350 | 342 |
$finished = date_repeat_is_finished($current_day, $days, $count, $end_date); |
351 | 343 |
|
352 |
// Make sure we don't get stuck in endless loop if the current |
|
353 |
// day never got changed above.
|
|
344 |
// Make sure we don't get stuck in endless loop if the current day
|
|
345 |
// never got changed above. |
|
354 | 346 |
if (!$moved) { |
355 | 347 |
date_modify($current_day, '+1 day' . $modify_time); |
356 | 348 |
} |
357 | 349 |
|
358 |
// If this is a WEEKLY frequency, stop after each week, |
|
359 |
// otherwise, stop when we've moved outside the current period.
|
|
360 |
// Jump to the end of the week, then test the period.
|
|
350 |
// If this is a WEEKLY frequency, stop after each week, otherwise,
|
|
351 |
// stop when we've moved outside the current period. Jump to the end
|
|
352 |
// of the week, then test the period. |
|
361 | 353 |
if ($finished || $rrule['FREQ'] == 'WEEKLY') { |
362 | 354 |
$period_finished = TRUE; |
363 | 355 |
} |
... | ... | |
370 | 362 |
continue; |
371 | 363 |
} |
372 | 364 |
|
373 |
// We'll be at the end of a week, month, or year when |
|
374 |
// we get to this point in the code. |
|
375 |
|
|
376 |
// Go back to the beginning of this period before we jump, to |
|
377 |
// ensure we jump to the first day of the next period. |
|
365 |
// We'll be at the end of a week, month, or year when we get to this |
|
366 |
// point in the code. Go back to the beginning of this period before we |
|
367 |
// jump, to ensure we jump to the first day of the next period. |
|
378 | 368 |
switch ($rrule['FREQ']) { |
379 | 369 |
case 'WEEKLY': |
380 | 370 |
date_modify($current_day, '+1 ' . $week_start_day . $modify_time); |
... | ... | |
420 | 410 |
|
421 | 411 |
// RFC 2445 says if no day or monthday is specified when creating repeats for |
422 | 412 |
// weeks, months, or years, impute the value from the start date. |
423 |
|
|
424 | 413 |
if (empty($rrule['BYDAY']) && $rrule['FREQ'] == 'WEEKLY') { |
425 | 414 |
$rrule['BYDAY'] = array(date_repeat_dow2day(date_format($start_date, 'w'))); |
426 | 415 |
} |
... | ... | |
433 | 422 |
$rrule['BYMONTH'] = array(date_format($start_date, 'n')); |
434 | 423 |
} |
435 | 424 |
} |
436 |
// If we are processing rules for period other than YEARLY or MONTHLY |
|
437 |
// and have BYDAYS like 2SU or -1SA, simplify them to SU or SA since the |
|
438 |
// position rules make no sense in other periods and just add complexity. |
|
439 |
|
|
425 |
// If we are processing rules for period other than YEARLY or MONTHLY and have |
|
426 |
// BYDAYS like 2SU or -1SA, simplify them to SU or SA since the position |
|
427 |
// rules make no sense in other periods and just add complexity. |
|
440 | 428 |
elseif (!empty($rrule['BYDAY']) && !in_array($rrule['FREQ'], array('MONTHLY', 'YEARLY'))) { |
441 | 429 |
foreach ($rrule['BYDAY'] as $delta => $by_day) { |
442 | 430 |
$rrule['BYDAY'][$delta] = substr($by_day, -2); |
... | ... | |
449 | 437 |
/** |
450 | 438 |
* Helper function to add found date to the $dates array. |
451 | 439 |
* |
452 |
* Check that the date to be added is between the start and end date |
|
453 |
* and that it is not in the $exceptions, nor already in the $days array,
|
|
454 |
* and that it meets other criteria in the RRULE.
|
|
440 |
* Check that the date to be added is between the start and end date and that it
|
|
441 |
* is not in the $exceptions, nor already in the $days array, and that it meets
|
|
442 |
* other criteria in the RRULE. |
|
455 | 443 |
*/ |
456 | 444 |
function date_repeat_add_dates(&$days, $current_day, $start_date, $end_date, $exceptions, $rrule) { |
457 | 445 |
if (isset($rrule['COUNT']) && count($days) >= $rrule['COUNT']) { |
... | ... | |
525 | 513 |
* Set a date object to a specific day of the month. |
526 | 514 |
* |
527 | 515 |
* Example, |
528 |
* date_set_month_day($date, 'Sunday', 2, '-') |
|
529 |
* will reset $date to the second to last Sunday in the month. |
|
530 |
* If $day is empty, will set to the number of days from the |
|
531 |
* beginning or end of the month. |
|
516 |
* date_set_month_day($date, 'Sunday', 2, '-') will reset $date to the second |
|
517 |
* to last Sunday in the month. If $day is empty, will set to the number of |
|
518 |
* days from the beginning or end of the month. |
|
532 | 519 |
*/ |
533 | 520 |
function date_repeat_set_month_day($date_in, $day, $count = 1, $direction = '+', $timezone = 'UTC', $modify_time = '') { |
534 | 521 |
if (is_object($date_in)) { |
535 | 522 |
$current_month = date_format($date_in, 'n'); |
536 | 523 |
|
537 |
// Reset to the start of the month. |
|
538 |
// We should be able to do this with date_date_set(), but |
|
539 |
// for some reason the date occasionally gets confused if run |
|
540 |
// through this function multiple times. It seems to work |
|
541 |
// reliably if we create a new object each time. |
|
524 |
// Reset to the start of the month. We should be able to do this with |
|
525 |
// date_date_set(), but for some reason the date occasionally gets confused |
|
526 |
// if run through this function multiple times. It seems to work reliably |
|
527 |
// if we create a new object each time. |
|
542 | 528 |
$datetime = date_format($date_in, DATE_FORMAT_DATETIME); |
543 | 529 |
$datetime = substr_replace($datetime, '01', 8, 2); |
544 | 530 |
$date = new DateObject($datetime, $timezone); |
... | ... | |
547 | 533 |
date_modify($date, '+1 month' . $modify_time); |
548 | 534 |
} |
549 | 535 |
else { |
550 |
// For positive search, back up one day to get outside the |
|
551 |
// current month, so we can catch the first of the month.
|
|
536 |
// For positive search, back up one day to get outside the current month,
|
|
537 |
// so we can catch the first of the month. |
|
552 | 538 |
date_modify($date, '-1 day' . $modify_time); |
553 | 539 |
} |
554 | 540 |
|
... | ... | |
556 | 542 |
date_modify($date, $direction . $count . ' days' . $modify_time); |
557 | 543 |
} |
558 | 544 |
else { |
559 |
// Use the English text for order, like First Sunday |
|
560 |
// instead of +1 Sunday to overcome PHP5 bug, (see #369020).
|
|
545 |
// Use the English text for order, like First Sunday instead of +1 Sunday
|
|
546 |
// to overcome PHP5 bug, (see #369020). |
|
561 | 547 |
$order = date_order(); |
562 | 548 |
$step = $count <= 5 ? $order[$direction . $count] : $count; |
563 | 549 |
date_modify($date, $step . ' ' . $day . $modify_time); |
... | ... | |
575 | 561 |
* Set a date object to a specific day of the year. |
576 | 562 |
* |
577 | 563 |
* Example, |
578 |
* date_set_year_day($date, 'Sunday', 2, '-') |
|
579 |
* will reset $date to the second to last Sunday in the year. |
|
580 |
* If $day is empty, will set to the number of days from the |
|
581 |
* beginning or end of the year. |
|
564 |
* date_set_year_day($date, 'Sunday', 2, '-') will reset $date to the second |
|
565 |
* to last Sunday in the year. If $day is empty, will set to the number of |
|
566 |
* days from the beginning or end of the year. |
|
582 | 567 |
*/ |
583 | 568 |
function date_repeat_set_year_day($date_in, $month, $day, $count = 1, $direction = '+', $timezone = 'UTC', $modify_time = '') { |
584 | 569 |
if (is_object($date_in)) { |
585 | 570 |
$current_year = date_format($date_in, 'Y'); |
586 | 571 |
|
587 |
// Reset to the start of the month. |
|
588 |
// See note above. |
|
572 |
// Reset to the start of the month. See note above. |
|
589 | 573 |
$datetime = date_format($date_in, DATE_FORMAT_DATETIME); |
590 | 574 |
$month_key = isset($month) ? $month : '01'; |
591 | 575 |
$datetime = substr_replace($datetime, $month_key . '-01', 5, 5); |
... | ... | |
597 | 581 |
$modifier = '+1 month'; |
598 | 582 |
} |
599 | 583 |
else { |
600 |
// For positive search, back up one day to get outside the |
|
601 |
// current month, so we can catch the first of the month.
|
|
584 |
// For positive search, back up one day to get outside the current
|
|
585 |
// month, so we can catch the first of the month. |
|
602 | 586 |
$modifier = '-1 day'; |
603 | 587 |
} |
604 | 588 |
} |
... | ... | |
608 | 592 |
$modifier = '+1 year'; |
609 | 593 |
} |
610 | 594 |
else { |
611 |
// For positive search, back up one day to get outside the |
|
612 |
// current year, so we can catch the first of the year.
|
|
595 |
// For positive search, back up one day to get outside the current
|
|
596 |
// year, so we can catch the first of the year. |
|
613 | 597 |
$modifier = '-1 day'; |
614 | 598 |
} |
615 | 599 |
} |
... | ... | |
620 | 604 |
date_modify($date, $direction . $count . ' days' . $modify_time); |
621 | 605 |
} |
622 | 606 |
else { |
623 |
// Use the English text for order, like First Sunday |
|
624 |
// instead of +1 Sunday to overcome PHP5 bug, (see #369020).
|
|
607 |
// Use the English text for order, like First Sunday instead of +1 Sunday
|
|
608 |
// to overcome PHP5 bug, (see #369020). |
|
625 | 609 |
$order = date_order(); |
626 | 610 |
$step = $count <= 5 ? $order[$direction . $count] : $count; |
627 | 611 |
date_modify($date, $step . ' ' . $day . $modify_time); |
Formats disponibles : Unified diff
Weekly update of contrib modules