Skip to content

Instantly share code, notes, and snippets.

@jippi
Created August 16, 2012 16:26
Show Gist options
  • Select an option

  • Save jippi/3371460 to your computer and use it in GitHub Desktop.

Select an option

Save jippi/3371460 to your computer and use it in GitHub Desktop.
<?php
App::uses('CalendarDate', 'Lib');
/**
* Support class for working with recurring entries
*
*/
class RecurringSupport {
protected static $_modelName;
/**
* Calculate recurrence based on a list of inputs
*
* Settings can be the following:
* - limit: The max number of expansions to do on each entry (Must be integer) - default is 10 expansions
* - skipBefore: Skip all expansions before this date (Must be a CalendarDate object)
* - dateInterval: Number of "time" to skip for each expansion test (Must be a DateInterval object) - default is 1 day
*
* @param array $entries A list of recurring entries that should be expanded
* @param array $settings A list of extra configurations for the calculations
* @return array The expanded list
*/
public static function calculateRecurrence($entries, $settings = array()) {
$defaults = array(
'limit' => 10,
'skipBefore' => null,
'dateInterval' => new DateInterval('P1D'),
);
$settings = array_merge($defaults, $settings);
$return = array();
foreach ($entries as $entry) {
if (!Hash::get($entry, static::_prefixKey('is_recurrent'))) {
$return[] = $entry;
continue;
}
$start = new CalendarDate(Hash::get($entry, static::_prefixKey('start_time')));
$stop = new CalendarDate(Hash::get($entry, static::_prefixKey('end_time')));
$expansions = 0;
for ($date = $start; $date <= $stop; $date->add($settings['dateInterval'])) {
if ($settings['skipBefore'] && $date < $settings['skipBefore']) {
continue;
}
if (!static::ruleIsTrue($entry, $date)) {
continue;
}
$expansions++;
$return[] = static::renderEventForDate($entry, clone $date);
if (is_numeric($settings['limit']) && $expansions >= $settings['limit']) {
break;
}
}
}
return $return;
}
/**
* Get the next recurring date for an entry or list of entries
*
* This method will only expand forward in time, and only find the next recurring date
*
* The list must be bare, not containing any model alias key
*
* @param array $entries
* @return array
*/
public static function getNextDate($entries, $model = null) {
static::_setModelName($model);
if (isset($entries[0])) {
$entries = array($entries);
}
$result = static::calculateRecurrence($entries, array('limit' => 1, 'skipBefore' => new CalendarDate()));
if (empty($result)) {
return $entries;
}
return $entries;
}
/**
* Renders a recurring entry for the given date. Does not check to make sure the
* event occurs on this date (use RecurrenceRule::ruleIsTrue for that).
*
* @param array $entry Contains data formatted the same as a retrieved entry from a find.
* @param DateTime $date A DateTime object in UTC time. This is the date to render the event for.
* @return array The instance rendered for this recurring event
*/
public static function renderEventForDate($entry, DateTime $date) {
$start_date = new CalendarDate(Hash::get($entry, static::_prefixKey('start_time')));
$end_date = new CalendarDate(Hash::get($entry, static::_prefixKey('end_time')));
$interval = $start_date->diff($end_date);
$interval = new DateInterval("PT{$interval->h}H{$interval->i}M");
$floating_start_hour = $start_date->format('H');
$date->setTime($floating_start_hour, $date->format('i'), $date->format('s'));
$entry = Hash::insert($entry, static::_prefixKey('start_time'), $date->format());
$date->add($interval);
$entry = Hash::insert($entry, static::_prefixKey('end_time'), $date->format());
return $entry;
}
/**
* Test if a expansion rule is true
*
* It will check the frequency key and check if should be recurring on the date provided as 2nd argument
*
* @param array $entry
* @param DateTime $date
* @return boolean
*/
public static function ruleIsTrue($entry, DateTime $date) {
$itemStart = new CalendarDate(Hash::get($entry, static::_prefixKey('start_time')));
$itemEnd = new CalendarDate(Hash::get($entry, static::_prefixKey('end_time')));
$frequency = Hash::get($entry, static::_prefixKey('frequency'));
if ($frequency == 'weekly') {
$dayOfWeek = strtolower($itemStart->format('l'));
return $dayOfWeek == strtolower($date->format('l'));
}
if ($frequency === 'daily') {
return true;
}
return false;
}
/**
* Optionally prefix a key with a modelName
*
* Returns the original key if no prefix is specified
*
* @param string $key
* @return string
*/
protected static function _prefixKey($key) {
if (empty(static::$_modelName)) {
return $key;
}
return sprintf('%s.%s', static::$_modelName, $key);
}
/**
* Set the prefix for _prefixKey calls
*
* @param string $model
* @return void
*/
protected static function _setModelName($model) {
static::$_modelName = $model;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment