summaryrefslogtreecommitdiffstats
path: root/3rdparty/when/When.php
diff options
context:
space:
mode:
Diffstat (limited to '3rdparty/when/When.php')
-rwxr-xr-x3rdparty/when/When.php725
1 files changed, 725 insertions, 0 deletions
diff --git a/3rdparty/when/When.php b/3rdparty/when/When.php
new file mode 100755
index 00000000000..5f97f0eb9bf
--- /dev/null
+++ b/3rdparty/when/When.php
@@ -0,0 +1,725 @@
+<?php
+/**
+ * Name: When
+ * Author: Thomas Planer <tplaner@gmail.com>
+ * Location: http://github.com/tplaner/When
+ * Created: September 2010
+ * Description: Determines the next date of recursion given an iCalendar "rrule" like pattern.
+ * Requirements: PHP 5.3+ - makes extensive use of the Date and Time library (http://us2.php.net/manual/en/book.datetime.php)
+ */
+class When
+{
+ protected $frequency;
+
+ protected $start_date;
+ protected $try_date;
+
+ protected $end_date;
+
+ protected $gobymonth;
+ protected $bymonth;
+
+ protected $gobyweekno;
+ protected $byweekno;
+
+ protected $gobyyearday;
+ protected $byyearday;
+
+ protected $gobymonthday;
+ protected $bymonthday;
+
+ protected $gobyday;
+ protected $byday;
+
+ protected $gobysetpos;
+ protected $bysetpos;
+
+ protected $suggestions;
+
+ protected $count;
+ protected $counter;
+
+ protected $goenddate;
+
+ protected $interval;
+
+ protected $wkst;
+
+ protected $valid_week_days;
+ protected $valid_frequency;
+
+ /**
+ * __construct
+ */
+ public function __construct()
+ {
+ $this->frequency = null;
+
+ $this->gobymonth = false;
+ $this->bymonth = range(1,12);
+
+ $this->gobymonthday = false;
+ $this->bymonthday = range(1,31);
+
+ $this->gobyday = false;
+ // setup the valid week days (0 = sunday)
+ $this->byday = range(0,6);
+
+ $this->gobyyearday = false;
+ $this->byyearday = range(0,366);
+
+ $this->gobysetpos = false;
+ $this->bysetpos = range(1,366);
+
+ $this->gobyweekno = false;
+ // setup the range for valid weeks
+ $this->byweekno = range(0,54);
+
+ $this->suggestions = array();
+
+ // this will be set if a count() is specified
+ $this->count = 0;
+ // how many *valid* results we returned
+ $this->counter = 0;
+
+ // max date we'll return
+ $this->end_date = new DateTime('9999-12-31');
+
+ // the interval to increase the pattern by
+ $this->interval = 1;
+
+ // what day does the week start on? (0 = sunday)
+ $this->wkst = 0;
+
+ $this->valid_week_days = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA');
+
+ $this->valid_frequency = array('SECONDLY', 'MINUTELY', 'HOURLY', 'DAILY', 'WEEKLY', 'MONTHLY', 'YEARLY');
+ }
+
+ /**
+ * @param DateTime|string $start_date of the recursion - also is the first return value.
+ * @param string $frequency of the recrusion, valid frequencies: secondly, minutely, hourly, daily, weekly, monthly, yearly
+ */
+ public function recur($start_date, $frequency = "daily")
+ {
+ try
+ {
+ if(is_object($start_date))
+ {
+ $this->start_date = clone $start_date;
+ }
+ else
+ {
+ // timestamps within the RFC have a 'Z' at the end of them, remove this.
+ $start_date = trim($start_date, 'Z');
+ $this->start_date = new DateTime($start_date);
+ }
+
+ $this->try_date = clone $this->start_date;
+ }
+ catch(Exception $e)
+ {
+ throw new InvalidArgumentException('Invalid start date DateTime: ' . $e);
+ }
+
+ $this->freq($frequency);
+
+ return $this;
+ }
+
+ public function freq($frequency)
+ {
+ if(in_array(strtoupper($frequency), $this->valid_frequency))
+ {
+ $this->frequency = strtoupper($frequency);
+ }
+ else
+ {
+ throw new InvalidArgumentException('Invalid frequency type.');
+ }
+
+ return $this;
+ }
+
+ // accepts an rrule directly
+ public function rrule($rrule)
+ {
+ // strip off a trailing semi-colon
+ $rrule = trim($rrule, ";");
+
+ $parts = explode(";", $rrule);
+
+ foreach($parts as $part)
+ {
+ list($rule, $param) = explode("=", $part);
+
+ $rule = strtoupper($rule);
+ $param = strtoupper($param);
+
+ switch($rule)
+ {
+ case "FREQ":
+ $this->frequency = $param;
+ break;
+ case "UNTIL":
+ $this->until($param);
+ break;
+ case "COUNT":
+ $this->count($param);
+ break;
+ case "INTERVAL":
+ $this->interval($param);
+ break;
+ case "BYDAY":
+ $params = explode(",", $param);
+ $this->byday($params);
+ break;
+ case "BYMONTHDAY":
+ $params = explode(",", $param);
+ $this->bymonthday($params);
+ break;
+ case "BYYEARDAY":
+ $params = explode(",", $param);
+ $this->byyearday($params);
+ break;
+ case "BYWEEKNO":
+ $params = explode(",", $param);
+ $this->byweekno($params);
+ break;
+ case "BYMONTH":
+ $params = explode(",", $param);
+ $this->bymonth($params);
+ break;
+ case "BYSETPOS":
+ $params = explode(",", $param);
+ $this->bysetpos($params);
+ break;
+ case "WKST":
+ $this->wkst($param);
+ break;
+ }
+ }
+
+ return $this;
+ }
+
+ //max number of items to return based on the pattern
+ public function count($count)
+ {
+ $this->count = (int)$count;
+
+ return $this;
+ }
+
+ // how often the recurrence rule repeats
+ public function interval($interval)
+ {
+ $this->interval = (int)$interval;
+
+ return $this;
+ }
+
+ // starting day of the week
+ public function wkst($day)
+ {
+ switch($day)
+ {
+ case 'SU':
+ $this->wkst = 0;
+ break;
+ case 'MO':
+ $this->wkst = 1;
+ break;
+ case 'TU':
+ $this->wkst = 2;
+ break;
+ case 'WE':
+ $this->wkst = 3;
+ break;
+ case 'TH':
+ $this->wkst = 4;
+ break;
+ case 'FR':
+ $this->wkst = 5;
+ break;
+ case 'SA':
+ $this->wkst = 6;
+ break;
+ }
+
+ return $this;
+ }
+
+ // max date
+ public function until($end_date)
+ {
+ try
+ {
+ if(is_object($end_date))
+ {
+ $this->end_date = clone $end_date;
+ }
+ else
+ {
+ // timestamps within the RFC have a 'Z' at the end of them, remove this.
+ $end_date = trim($end_date, 'Z');
+ $this->end_date = new DateTime($end_date);
+ }
+ }
+ catch(Exception $e)
+ {
+ throw new InvalidArgumentException('Invalid end date DateTime: ' . $e);
+ }
+
+ return $this;
+ }
+
+ public function bymonth($months)
+ {
+ if(is_array($months))
+ {
+ $this->gobymonth = true;
+ $this->bymonth = $months;
+ }
+
+ return $this;
+ }
+
+ public function bymonthday($days)
+ {
+ if(is_array($days))
+ {
+ $this->gobymonthday = true;
+ $this->bymonthday = $days;
+ }
+
+ return $this;
+ }
+
+ public function byweekno($weeks)
+ {
+ $this->gobyweekno = true;
+
+ if(is_array($weeks))
+ {
+ $this->byweekno = $weeks;
+ }
+
+ return $this;
+ }
+
+ public function bysetpos($days)
+ {
+ $this->gobysetpos = true;
+
+ if(is_array($days))
+ {
+ $this->bysetpos = $days;
+ }
+
+ return $this;
+ }
+
+ public function byday($days)
+ {
+ $this->gobyday = true;
+
+ if(is_array($days))
+ {
+ $this->byday = array();
+ foreach($days as $day)
+ {
+ $len = strlen($day);
+
+ $as = '+';
+
+ // 0 mean no occurence is set
+ $occ = 0;
+
+ if($len == 3)
+ {
+ $occ = substr($day, 0, 1);
+ }
+ if($len == 4)
+ {
+ $as = substr($day, 0, 1);
+ $occ = substr($day, 1, 1);
+ }
+
+ if($as == '-')
+ {
+ $occ = '-' . $occ;
+ }
+ else
+ {
+ $occ = '+' . $occ;
+ }
+
+ $day = substr($day, -2, 2);
+ switch($day)
+ {
+ case 'SU':
+ $this->byday[] = $occ . 'SU';
+ break;
+ case 'MO':
+ $this->byday[] = $occ . 'MO';
+ break;
+ case 'TU':
+ $this->byday[] = $occ . 'TU';
+ break;
+ case 'WE':
+ $this->byday[] = $occ . 'WE';
+ break;
+ case 'TH':
+ $this->byday[] = $occ . 'TH';
+ break;
+ case 'FR':
+ $this->byday[] = $occ . 'FR';
+ break;
+ case 'SA':
+ $this->byday[] = $occ . 'SA';
+ break;
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ public function byyearday($days)
+ {
+ $this->gobyyearday = true;
+
+ if(is_array($days))
+ {
+ $this->byyearday = $days;
+ }
+
+ return $this;
+ }
+
+ // this creates a basic list of dates to "try"
+ protected function create_suggestions()
+ {
+ switch($this->frequency)
+ {
+ case "YEARLY":
+ $interval = 'year';
+ break;
+ case "MONTHLY":
+ $interval = 'month';
+ break;
+ case "WEEKLY":
+ $interval = 'week';
+ break;
+ case "DAILY":
+ $interval = 'day';
+ break;
+ case "HOURLY":
+ $interval = 'hour';
+ break;
+ case "MINUTELY":
+ $interval = 'minute';
+ break;
+ case "SECONDLY":
+ $interval = 'second';
+ break;
+ }
+
+ $month_day = $this->try_date->format('j');
+ $month = $this->try_date->format('n');
+ $year = $this->try_date->format('Y');
+
+ $timestamp = $this->try_date->format('H:i:s');
+
+ if($this->gobysetpos)
+ {
+ if($this->try_date == $this->start_date)
+ {
+ $this->suggestions[] = clone $this->try_date;
+ }
+ else
+ {
+ if($this->gobyday)
+ {
+ foreach($this->bysetpos as $_pos)
+ {
+ $tmp_array = array();
+ $_mdays = range(1, date('t',mktime(0,0,0,$month,1,$year)));
+ foreach($_mdays as $_mday)
+ {
+ $date_time = new DateTime($year . '-' . $month . '-' . $_mday . ' ' . $timestamp);
+
+ $occur = ceil($_mday / 7);
+
+ $day_of_week = $date_time->format('l');
+ $dow_abr = strtoupper(substr($day_of_week, 0, 2));
+
+ // set the day of the month + (positive)
+ $occur = '+' . $occur . $dow_abr;
+ $occur_zero = '+0' . $dow_abr;
+
+ // set the day of the month - (negative)
+ $total_days = $date_time->format('t') - $date_time->format('j');
+ $occur_neg = '-' . ceil(($total_days + 1)/7) . $dow_abr;
+
+ $day_from_end_of_month = $date_time->format('t') + 1 - $_mday;
+
+ if(in_array($occur, $this->byday) || in_array($occur_zero, $this->byday) || in_array($occur_neg, $this->byday))
+ {
+ $tmp_array[] = clone $date_time;
+ }
+ }
+
+ if($_pos > 0)
+ {
+ $this->suggestions[] = clone $tmp_array[$_pos - 1];
+ }
+ else
+ {
+ $this->suggestions[] = clone $tmp_array[count($tmp_array) + $_pos];
+ }
+
+ }
+ }
+ }
+ }
+ elseif($this->gobyyearday)
+ {
+ foreach($this->byyearday as $_day)
+ {
+ if($_day >= 0)
+ {
+ $_day--;
+
+ $_time = strtotime('+' . $_day . ' days', mktime(0, 0, 0, 1, 1, $year));
+ $this->suggestions[] = new Datetime(date('Y-m-d', $_time) . ' ' . $timestamp);
+ }
+ else
+ {
+ $year_day_neg = 365 + $_day;
+ $leap_year = $this->try_date->format('L');
+ if($leap_year == 1)
+ {
+ $year_day_neg = 366 + $_day;
+ }
+
+ $_time = strtotime('+' . $year_day_neg . ' days', mktime(0, 0, 0, 1, 1, $year));
+ $this->suggestions[] = new Datetime(date('Y-m-d', $_time) . ' ' . $timestamp);
+ }
+ }
+ }
+ // special case because for years you need to loop through the months too
+ elseif($this->gobyday && $interval == "year")
+ {
+ foreach($this->bymonth as $_month)
+ {
+ // this creates an array of days of the month
+ $_mdays = range(1, date('t',mktime(0,0,0,$_month,1,$year)));
+ foreach($_mdays as $_mday)
+ {
+ $date_time = new DateTime($year . '-' . $_month . '-' . $_mday . ' ' . $timestamp);
+
+ // get the week of the month (1, 2, 3, 4, 5, etc)
+ $week = $date_time->format('W');
+
+ if($date_time >= $this->start_date && in_array($week, $this->byweekno))
+ {
+ $this->suggestions[] = clone $date_time;
+ }
+ }
+ }
+ }
+ elseif($interval == "day")
+ {
+ $this->suggestions[] = clone $this->try_date;
+ }
+ elseif($interval == "week")
+ {
+ $this->suggestions[] = clone $this->try_date;
+
+ if($this->gobyday)
+ {
+ $week_day = $this->try_date->format('w');
+
+ $days_in_month = $this->try_date->format('t');
+
+ $overflow_count = 1;
+ $_day = $month_day;
+
+ $run = true;
+ while($run)
+ {
+ $_day++;
+ if($_day <= $days_in_month)
+ {
+ $tmp_date = new DateTime($year . '-' . $month . '-' . $_day . ' ' . $timestamp);
+ }
+ else
+ {
+ //$tmp_month = $month+1;
+ $tmp_date = new DateTime($year . '-' . $month . '-' . $overflow_count . ' ' . $timestamp);
+ $tmp_date->modify('+1 month');
+ $overflow_count++;
+ }
+
+ $week_day = $tmp_date->format('w');
+
+ if($this->try_date == $this->start_date)
+ {
+ if($week_day == $this->wkst)
+ {
+ $this->try_date = clone $tmp_date;
+ $this->try_date->modify('-7 days');
+ $run = false;
+ }
+ }
+
+ if($week_day != $this->wkst)
+ {
+ $this->suggestions[] = clone $tmp_date;
+ }
+ else
+ {
+ $run = false;
+ }
+ }
+ }
+ }
+ elseif($this->gobyday || $interval == "month")
+ {
+ $_mdays = range(1, date('t',mktime(0,0,0,$month,1,$year)));
+ foreach($_mdays as $_mday)
+ {
+ $date_time = new DateTime($year . '-' . $month . '-' . $_mday . ' ' . $timestamp);
+
+ // get the week of the month (1, 2, 3, 4, 5, etc)
+ $week = $date_time->format('W');
+
+ if($date_time >= $this->start_date && in_array($week, $this->byweekno))
+ {
+ $this->suggestions[] = clone $date_time;
+ }
+ }
+ }
+ elseif($this->gobymonth)
+ {
+ foreach($this->bymonth as $_month)
+ {
+ $date_time = new DateTime($year . '-' . $_month . '-' . $month_day . ' ' . $timestamp);
+
+ if($date_time >= $this->start_date)
+ {
+ $this->suggestions[] = clone $date_time;
+ }
+ }
+ }
+ else
+ {
+ $this->suggestions[] = clone $this->try_date;
+ }
+
+ if($interval == "month")
+ {
+ $this->try_date->modify('last day of ' . $this->interval . ' ' . $interval);
+ }
+ else
+ {
+ $this->try_date->modify($this->interval . ' ' . $interval);
+ }
+ }
+
+ protected function valid_date($date)
+ {
+ $year = $date->format('Y');
+ $month = $date->format('n');
+ $day = $date->format('j');
+
+ $year_day = $date->format('z') + 1;
+
+ $year_day_neg = -366 + $year_day;
+ $leap_year = $date->format('L');
+ if($leap_year == 1)
+ {
+ $year_day_neg = -367 + $year_day;
+ }
+
+ // this is the nth occurence of the date
+ $occur = ceil($day / 7);
+
+ $week = $date->format('W');
+
+ $day_of_week = $date->format('l');
+ $dow_abr = strtoupper(substr($day_of_week, 0, 2));
+
+ // set the day of the month + (positive)
+ $occur = '+' . $occur . $dow_abr;
+ $occur_zero = '+0' . $dow_abr;
+
+ // set the day of the month - (negative)
+ $total_days = $date->format('t') - $date->format('j');
+ $occur_neg = '-' . ceil(($total_days + 1)/7) . $dow_abr;
+
+ $day_from_end_of_month = $date->format('t') + 1 - $day;
+
+ if(in_array($month, $this->bymonth) &&
+ (in_array($occur, $this->byday) || in_array($occur_zero, $this->byday) || in_array($occur_neg, $this->byday)) &&
+ in_array($week, $this->byweekno) &&
+ (in_array($day, $this->bymonthday) || in_array(-$day_from_end_of_month, $this->bymonthday)) &&
+ (in_array($year_day, $this->byyearday) || in_array($year_day_neg, $this->byyearday)))
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ // return the next valid DateTime object which matches the pattern and follows the rules
+ public function next()
+ {
+ // check the counter is set
+ if($this->count !== 0)
+ {
+ if($this->counter >= $this->count)
+ {
+ return false;
+ }
+ }
+
+ // create initial set of suggested dates
+ if(count($this->suggestions) === 0)
+ {
+ $this->create_suggestions();
+ }
+
+ // loop through the suggested dates
+ while(count($this->suggestions) > 0)
+ {
+ // get the first one on the array
+ $try_date = array_shift($this->suggestions);
+
+ // make sure the date doesn't exceed the max date
+ if($try_date > $this->end_date)
+ {
+ return false;
+ }
+
+ // make sure it falls within the allowed days
+ if($this->valid_date($try_date) === true)
+ {
+ $this->counter++;
+ return $try_date;
+ }
+ else
+ {
+ // we might be out of suggested days, so load some more
+ if(count($this->suggestions) === 0)
+ {
+ $this->create_suggestions();
+ }
+ }
+ }
+ }
+}