reports: Fix id of custom date selector
[ninja.git] / application / models / old_timeperiod.php
blob33ed616fd73efca46849bfdb94ca58c8626346e6
1 <?php defined('SYSPATH') OR die('No direct access allowed.');
3 /**
4 * A model used by reports to lookup when report times and timeperiods start
5 * and stop. This sounds real easy, until you start to look at nagios'
6 * less trivial timeperiod definitions.
8 * You should call the instance() method to get a timeperiod instance,
9 * which might be shared with other users. This class will call instance()
10 * itself, so exceptions to exceptions work.
12 class Old_Timeperiod_Model extends Model
14 /** Please do not touch this outside of tests, it's a cache for performance purposes */
15 static public $precreated = array();
16 /** Definition of the regular (weekday) timeperiod definitions */
17 protected $period = false;
18 /** Report start */
19 public $start_time = false;
20 /** Report stop */
21 public $end_time = false;
23 public $tp_exceptions = array(); /**< Timeperiod exceptions */
24 public $tp_excludes = array(); /**< Timeperiod excludes */
26 const DATERANGE_CALENDAR_DATE = 0; /**< eg: 2001-01-01 - 2010-11-21 / 3 (specific complete calendar date) */
27 const DATERANGE_MONTH_DATE = 1; /**< eg: july 4 - november 15 / 3 (specific month) */
28 const DATERANGE_MONTH_DAY = 2; /**< eg: day 1 - 25 / 5 (generic month) */
29 const DATERANGE_MONTH_WEEK_DAY = 3; /**< eg: thursday 1 april - tuesday 2 may / 2 (specific month) */
30 const DATERANGE_WEEK_DAY = 4; /**< eg: thursday 3 - monday 4 (generic month) */
31 const DATERANGE_TYPES = 5; /**< FIXME: incomprehensible magic */
33 /**
34 * Return an instance from a Report_options object
35 * @param $options Report_options class, or possibly an array mock
37 public static function instance($options) {
38 $key = $options['rpttimeperiod'].$options['start_time'].$options['end_time'];
39 if (isset(self::$precreated[$key]))
40 return self::$precreated[$key];
41 $obj = new self($options);
42 self::$precreated[$key] = $obj;
43 return $obj;
46 /**
47 * Warning: you should not use this directly - see instance()
49 public function __construct($options) {
50 $this->start_time = $options['start_time'];
51 $this->end_time = $options['end_time'];
52 if (!$options['rpttimeperiod'])
53 return;
54 $result = self::get($options['rpttimeperiod'], true);
55 if (empty($result))
56 return;
57 $this->set_timeperiod_data($result);
60 /**
61 * Setup the data to base the timeperiod on
63 * You really shouldn't be using this, unless you're a test and want to mock a timeperiod
65 * @param $period A timeperiod db result
66 * @return true on success, false otherwise
68 public function set_timeperiod_data($period=NULL)
70 $valid_weekdays = reports::$valid_weekdays;
72 if (!$period) {
73 $this->period = false;
74 return true;
77 $this->period = array();
79 unset($period['id']);
80 unset($period['timeperiod_name']);
81 unset($period['alias']);
82 unset($period['instance_id']);
84 $includes = $period;
86 $errors = 0;
87 foreach ($includes as $k => $v) {
88 if (empty($v)) {
89 continue;
91 $errors += $this->set_timeperiod_variable($k, $v) === false;
94 if(!empty($period['excludes']))
96 foreach($period['excludes'] as $exclude)
98 $this->tp_excludes[] = Old_Timeperiod_Model::instance(array('start_time' => $this->start_time, 'end_time' => $this->end_time, 'rpttimeperiod' => $exclude));
102 if ($errors)
103 return false;
104 return true;
108 * Returns the number of active seconds "inside"
109 * the timeperiod during the start -> stop interval
110 * @param $start: A timestamp in the unix epoch notation
111 * @param $stop: A timestamp in the unix epoch notation
112 * @return The number of seconds included in both $stop - $start
113 * and the timeperiod set for this report as an integer
114 * in the unix epoch notation
116 public function active_time($start, $stop) {
117 # if no timeperiod is set, the entire duration is active
118 if ($this->period === false)
119 return $stop - $start;
121 # a timeperiod without entries will cause us to never
122 # find start or stop. otoh, it never has any active time,
123 # so we simply return 0
124 if ($start >= $stop)
125 return 0;
127 $nstart = $this->tp_next($start, 'start');
128 # if there is no start event inside the timeperiod, or the
129 # first ever $nstart is beyond our $stop parameter,
130 # there are no active seconds between start and stop, so
131 # break out early
132 if (!$nstart || $nstart >= $stop)
133 return 0;
135 $nstop = $this->tp_next($nstart, 'stop');
136 # If the first ever $nstop encountered is beyond our
137 # $stop parameter, we can return early, as we won't
138 # need to loop at all
139 if ($nstop > $stop)
140 return $stop - $nstart;
142 $active = $nstop - $nstart;
143 while ($nstart != 0) {
144 if (($nstart = $this->tp_next($nstop, 'start')) > $stop)
145 $nstart = $stop;
146 if (($nstop = $this->tp_next($nstart, 'stop')) > $stop)
147 $nstop = $stop;
149 $active += $nstop - $nstart;
150 if ($nstart >= $stop || $nstop >= $stop)
151 return $active;
154 # we ran out of time periods before we reached $stop, so let's
155 # show 'em what we've got, so far
156 return $active;
160 * Finds next start or stop of timeperiod from a given timestamp. If
161 * given time is in an inactive timeperiod and we're looking for a
162 * stop, current time is returned.
163 * Vice versa, if we're looking for start inside an active period,
164 * the current timestamp is returned.
166 * @param $when Current timestamp to start from.
167 * @param $what Whether to search for start or stop. Valid values are 'start' and 'stop'.
168 * @return The timestamp
170 public function tp_next($when, $what = 'start')
172 if ($this->period === false)
173 return $when;
175 # if there is a report timeperiod set that doesn't have
176 # any 'start' entry (ie, all days are empty, such as for
177 # the "none" timeperiod), we can't possibly find either
178 # start or stop, so we can break out early.
179 # No one sane will want to take a report from such a timeperiod,
180 # but in case the user misclicks, we should behave properly.
181 if (empty($this->period) && empty($this->tp_exceptions)){
182 return 0;
185 if ($what === 'start') {
186 # try to find the next valid timestamp in this timeperiod,
187 # that is not valid in any of the exceptions.
188 # if we make it through a whole loop without $when changing, we
189 # must have found next tp start.
190 $main_when = false;
191 while ($main_when !== $when && $when <= $this->end_time) {
192 $main_when = $when = $this->tp_flat_next($when, 'start');
193 foreach ($this->tp_excludes as $exclude) {
194 $tmp_when = $exclude->tp_next($when, 'stop');
195 if ($tmp_when !== 0) # 0 => no more tp entries => ignore
196 $when = $tmp_when;
199 if ($when > $this->end_time)
200 return 0;
201 return $when;
203 else if ($what === 'stop') {
204 # when this timeperiod stops, or any of the excludes start, we
205 # have a stop, whatever happens first
206 $whens = array();
207 $whens[] = $this->tp_flat_next($when, 'stop');
209 foreach ($this->tp_excludes as $exclude) {
210 $whens[] = $exclude->tp_next($when, 'start');
212 $whens = array_filter($whens); // remove any 0
214 if (empty($whens))
215 return 0;
216 return min($whens);
219 return 0;
223 * Finds the next start or stop of timeperiod, ignoring excludes, from
224 * a given timestamp. Really just a helper for the above.
226 private function tp_flat_next($when, $what)
228 $other = 'stop';
229 if ($what === 'stop')
230 $other = 'start';
232 $tm = localtime($when, true);
233 $year = $tm['tm_year'] + 1900; # stored as offsets since 1900
234 $tm_yday = $tm['tm_yday'];
235 $day = $tm['tm_wday'];
236 $day_seconds = ($tm['tm_hour'] * 3600) + ($tm['tm_min'] * 60) + $tm['tm_sec'];
238 $midnight_to_when = $when - $day_seconds;
239 $ents = array();
240 # see if we have an exception first
241 if (!empty($this->tp_exceptions[$year][$tm_yday]))
242 $ents = $this->tp_exceptions[$year][$tm_yday];
243 # if not, look for regular weekday
244 elseif (!empty($this->period[$day]))
245 $ents = $this->period[$day];
246 # we have no entries today, so if we're looking for something outside
247 # a timeperiod, everything is.
248 elseif ($what === 'stop')
249 return $when;
251 if ($what === 'start') {
252 foreach ($ents as $ent) {
253 if ($ent['start'] <= $day_seconds && $ent['stop'] > $day_seconds)
254 return $when;
256 if ($ent['start'] > $day_seconds)
257 return $midnight_to_when + $ent['start'];
259 } else {
260 foreach ($ents as $ent) {
261 if ($ent['start'] > $day_seconds)
262 return $when;
263 if ($ent['start'] <= $day_seconds && $ent['stop'] > $day_seconds)
264 return $midnight_to_when + $ent['stop'];
268 $orig_day = $day;
269 $loops = 0;
270 for ($day = $orig_day + 1; $when + ($loops * 86400) < $this->end_time; $day++) {
271 $loops++;
272 $ents = false;
273 if ($day > 6)
274 $day = 0;
276 $midnight_to_when += 86400;
278 if (!empty($this->tp_exceptions[$year][$tm_yday + $loops]))
279 $ents = $this->tp_exceptions[$year][$tm_yday + $loops];
280 elseif (!empty($this->period[$day]))
281 $ents = $this->period[$day];
283 # no exceptions, no timeperiod entry
284 if (!$ents)
285 continue;
287 foreach ($ents as $ent)
288 return $midnight_to_when + $ent[$what];
291 return 0;
295 * Returns whether the given timestamp is inside timeperiod
296 * @param $timestamp: A timestamp in the unix epoch notation
297 * @return TRUE if the timestamp is inside the timeperiod, FALSE otherwise
299 public function inside($timestamp) {
300 return ($this->tp_next($timestamp, 'start') === $timestamp);
304 * Resolve timeperiods, both the actual timeperiods and the exclusions
306 public function resolve_timeperiods()
308 if ($this->start_time === false || $this->end_time === false) {
309 throw new Exception("Timeperiods cannot be resolved unless report start and end time is set");
312 if ($this->end_time < $this->start_time) {
313 throw new Exception("Report time set to end before start");
316 $start_year = date('Y', $this->start_time);
317 $end_year = date('Y', $this->end_time);
319 if (!isset($this->tp_exceptions['unresolved']))
320 return true;
322 for($day_time = $this->start_time ; $day_time <= $this->end_time ; $day_time += 86400)
324 $check_exception = true;
326 $day_shift = strtotime(date("Y-m-d", $day_time));
327 $day = date('z', $day_time);
328 $day_year = date('Y', $day_time);
329 $day_month = date('n', $day_time);
331 for($i=0,$n=count($this->tp_exceptions['unresolved']) ; $i<$n ; $i++)
333 $x =& $this->tp_exceptions['unresolved'][$i];
335 if($x['syear'] > date('Y', $this->end_time))
336 continue;
339 # find out if there is an exception during this day
340 switch($x['type'])
342 case self::DATERANGE_CALENDAR_DATE:/* eg: 2008-12-25 */
343 # set fields: syear, smon, smday, eyear, emon, emday, skip_interval
345 $exp_start = mktime(0,0,0, $x['smon'], $x['smday'], $x['syear']);
347 # unspecified end date - two possibilities
348 if(self::is_daterange_single_day($x))
349 $exp_end = $exp_start;
350 else
351 $exp_end = mktime(0,0,0, $x['emon'], $x['emday'], $x['eyear']);
353 break;
354 case self::DATERANGE_MONTH_DATE:
355 /* eg: july 4 (specific month) */
356 # set fields: smon, emon, smday, emday
358 $exp_start = self::calculate_time_from_day_of_month($start_year, $x['smon'], $x['smday']);
359 $exp_end = self::calculate_time_from_day_of_month($end_year, $x['emon'], $x['emday']);
361 # XXX: can *both* be zero here?
362 if($exp_end < $exp_start)
364 $x['eyear'] = $day_year + 1;
365 $exp_end = self::calculate_time_from_day_of_month($x['eyear'], $x['emon'], $x['emday']);
366 $exp_end += 86400;
369 if($exp_end == 0) {
370 echo "php is broken. goodie....\n";
371 if($x['emday'] < 0) {
372 $check_exception = false;
374 else {
375 $exp_end = self::calculate_time_from_day_of_month($x['eyear'], $x['emon'], -1);
379 assert($exp_end != 0);
381 break;
382 case self::DATERANGE_MONTH_DAY:
383 /* eg: day 21 (generic month) */
384 # set field: smday, emday
385 $exp_start = self::calculate_time_from_day_of_month($day_year, $day_month, $x['smday']);
386 $exp_end = self::calculate_time_from_day_of_month($day_year, $day_month, $x['emday']);
388 # get midnight at end of day
389 $exp_end += 86400;
390 break;
392 case self::DATERANGE_MONTH_WEEK_DAY:/* eg: 3rd thursday (specific month) */
393 # set field: smon, swday, swday_offset, emon, ewday, ewday_offset, skip_interval
394 $exp_start = self::calculate_time_from_weekday_of_month($start_year, $x['smon'], $x['swday'], $x['swday_offset']);
395 $exp_end = self::calculate_time_from_weekday_of_month($end_year, $x['emon'], $x['ewday'], $x['ewday_offset']);
396 break;
398 case self::DATERANGE_WEEK_DAY:
399 # eg: 3rd thursday (generic month)
400 # set fields: swday, swday_offset, ewday, ewday_offset, skip_interval
401 $exp_start = self::calculate_time_from_weekday_of_month($day_year, $day_month, $x['swday'], $x['swday_offset']);
402 $exp_end = self::calculate_time_from_weekday_of_month($day_year, $day_month, $x['ewday'], $x['ewday_offset']);
403 break;
406 # This day might be totally uninteresting, in which case
407 # we just ignore it
408 if($x['skip_interval'] > 1) {
409 $days_since_start = ($day_time - $exp_start) / 86400;
410 $check_exception = !($days_since_start % $x['skip_interval']);
413 # day shift is "midnight, today", $exp_start and $exp_end are always whole days
414 if (!$check_exception || $exp_start < $day_shift || $exp_end > $day_shift)
415 continue;
417 if(!isset($this->tp_exceptions[$day_year]))
418 $this->tp_exceptions[$day_year] = array();
420 if(!isset($this->tp_exceptions[$day_year][$day]))
421 $this->tp_exceptions[$day_year][$day] = array();
423 # if so, merge timeranges with existing for this day
424 $this->tp_exceptions[$day_year][$day] = self::merge_timerange_sets($this->tp_exceptions[$day_year][$day], $x['timeranges']);
427 unset($this->tp_exceptions['unresolved']);
431 * Parses a timerange string
432 * FIXME: add more validation
433 * @param $str string
434 * @return An array of timeranges
435 * E.g:
436 * $str="08:00-12:00,13:00-17:00" gives:
437 * array
439 * array('start' => 28800, 'stop' => 43200),
440 * array('start' => 46800, 'stop' => 61200)
441 * );
443 protected function tp_parse_day($str)
445 if (!$str)
446 return 0;
448 $i = 0;
449 $ret = array();
451 $ents = explode(',', $str);
452 foreach ($ents as $ent) {
453 $start_stop = explode('-', $ent);
454 $start_hour_minute = explode(':', $start_stop[0]);
455 $start_hour = $start_hour_minute[0];
456 $start_minute = $start_hour_minute[1];
457 $stop_hour_minute = explode(':', $start_stop[1]);
458 $stop_hour = $stop_hour_minute[0];
459 $stop_minute = $stop_hour_minute[1];
460 $stop_hour_minute = $start_hour_minute = $stop = $start = false;
461 $start_second = ($start_hour * 3600) + ($start_minute * 60);
462 $stop_second = ($stop_hour * 3600) + ($stop_minute * 60);
463 if($start_second >= $stop_second)
465 # @@@FIXME: no print statements in models!
466 print "Error: Skipping timerange $str, stop time is before start time<br>";
467 continue;
469 $ret[$i]['start'] = $start_second;
470 $ret[$i]['stop'] = $stop_second;
471 $i++;
474 return $ret;
478 * Adds a timeperiod exception to the report.
479 * FIXME: should probably validate more
480 * @param $dateperiod_type Indicates the type of exception. Se timeperiod_class.php for valid values.
481 * @param $syear Start year
482 * @param $smon Start month
483 * @param $smday Start day of month
484 * @param $swday Start weekday
485 * @param $swday_offset Start weekday offset
486 * @param $eyear End year
487 * @param $emon End month
488 * @param $emday End day of month
489 * @param $ewday End weekday
490 * @param $ewday_offset End weekday offset
491 * @param $skip_interval Interval to skip, such as: "every 3 weeks" etc
492 * @param $timeranges Array of timeranges.
493 * Throws Exception if any parameter has bogus values.
495 protected function add_timeperiod_exception($dateperiod_type,
496 $syear, $smon, $smday, $swday, $swday_offset,
497 $eyear, $emon, $emday, $ewday, $ewday_offset,
498 $skip_interval, $timeranges)
500 $days_per_month = reports::$days_per_month;
502 if (!isset($this->tp_exceptions['unresolved']))
503 $this->tp_exceptions['unresolved'] = array();
505 assert($dateperiod_type >= 0 && $dateperiod_type < self::DATERANGE_TYPES); # can only fail if programmer messed up
506 $timeranges = $this->tp_parse_day($timeranges);
508 $this->tp_exceptions['unresolved'][] = array
510 'type' => $dateperiod_type,
511 'syear' => $syear,
512 'smon' => $smon,
513 'smday' => $smday,
514 'swday' => $swday,
515 'swday_offset' => $swday_offset,
516 'eyear' => $eyear,
517 'emon' => $emon,
518 'emday' => $emday,
519 'ewday' => $ewday,
520 'ewday_offset' => $ewday_offset,
521 'skip_interval' => $skip_interval,
522 'timeranges' => $timeranges,
527 * Parses given input as a nagios 3 timeperiod variable. If valid,
528 * it is added to the report.
529 * Code is derived from the nagios 3 sources (xdata/xodtemplate.c)
530 * FIXME: find better way of adding 24h to end date
532 * @param $name The timeperiod style variable we want to parse
533 * @param $value The value of the timeperiod variable
534 * @return boolean
536 public function set_timeperiod_variable($name, $value)
538 $valid_weekdays = reports::$valid_weekdays;
539 $valid_months = reports::$valid_months;
541 $weekday_numbers = array_flip($valid_weekdays);
542 $month_numbers = array_flip($valid_months);
544 if(in_array($name, $valid_weekdays)) # add regular weekday include time
546 $this->period[array_search($name, $valid_weekdays)] = $this->tp_parse_day($value);
547 return true;
550 $input = "$name $value";
552 # you could put this in one line but that will be too messy
553 $items = array_filter(sscanf($input,"%4d-%2d-%2d - %4d-%2d-%2d / %d %[0-9:, -]"));
554 if(count($items) == 8)
556 list($syear, $smon, $smday, $eyear, $emon, $emday, $skip_interval, $timeranges) = $items;
558 /* add timerange exception */
559 $this->add_timeperiod_exception(self::DATERANGE_CALENDAR_DATE,
560 $syear, $smon, $smday, 0, 0, $eyear, $emon, $emday, 0, 0, $skip_interval, $timeranges);
561 return true;
564 $items = array_filter(sscanf($input,"%4d-%2d-%2d / %d %[0-9:, -]"));
565 if(count($items) == 5)
567 list($syear,$smon, $smday, $skip_interval, $timeranges) = $items;
568 $eyear = $syear;
569 $emon = $smon;
570 $emday = $smday;
572 /* add timerange exception */
573 $this->add_timeperiod_exception(self::DATERANGE_CALENDAR_DATE,
574 $syear, $smon, $smday, 0, 0, $eyear, $emon, $emday, 0, 0, $skip_interval, $timeranges);
575 return true;
578 $items = array_filter(sscanf($input,"%4d-%2d-%2d - %4d-%2d-%2d %[0-9:, -]"));
579 if(count($items) == 7)
581 list($syear, $smon, $smday, $eyear, $emon, $emday, $timeranges) = $items;
583 /* add timerange exception */
584 $this->add_timeperiod_exception(self::DATERANGE_CALENDAR_DATE,
585 $syear, $smon, $smday, 0, 0, $eyear, $emon, $emday, 0, 0, 0, $timeranges);
586 return true;
589 $items=array_filter(sscanf($input,"%4d-%2d-%2d %[0-9:, -]"));
590 if(count($items)==4)
592 list($syear, $smon, $smday, $timeranges) = $items;
593 $eyear = $syear;
594 $emon = $smon;
595 $emday = $smday;
596 /* add timerange exception */
597 $this->add_timeperiod_exception(self::DATERANGE_CALENDAR_DATE,
598 $syear, $smon, $smday, 0, 0, $eyear, $emon, $emday, 0, 0, 0, $timeranges);
599 return true;
602 /* other types... */
603 $items = array_filter(sscanf($input,"%[a-z] %d %[a-z] - %[a-z] %d %[a-z] / %d %[0-9:, -]"));
604 if(count($items) == 8)
606 list($str1, $swday_offset, $str2, $str3, $ewday_offset, $str4, $skip_interval, $timeranges) = $items;
607 /* wednesday 1 january - thursday 2 july / 3 */
609 if(in_array($str1, $valid_weekdays) &&
610 in_array($str2, $valid_months) &&
611 in_array($str3, $valid_weekdays) &&
612 in_array($str4, $valid_months))
614 $swday = $weekday_numbers[$str1];
615 $smon = $month_numbers[$str2];
616 $ewday = $weekday_numbers[$str3];
617 $emon = $month_numbers[$str4];
619 $this->add_timeperiod_exception(self::DATERANGE_MONTH_WEEK_DAY,
620 0, $smon, 0, $swday, $swday_offset, 0, $emon, 0, $ewday, $ewday_offset, $skip_interval, $timeranges);
621 return true;
623 return false;
626 $items = array_filter(sscanf($input,"%[a-z] %d - %[a-z] %d / %d %[0-9:, -]"));
627 if(count($items) == 6)
629 list($str1, $smday, $str2, $emday, $skip_interval, $timeranges) = $items;
630 /* february 1 - march 15 / 3 */
631 /* monday 2 - thursday 3 / 2 */
632 /* day 4 - day 6 / 2 */
633 if(in_array($str1, $valid_weekdays) && in_array($str2, $valid_weekdays))
635 /* monday 2 - thursday 3 / 2 */
636 $swday = $weekday_numbers[$str1];
637 $ewday = $weekday_numbers[$str2];
638 $swday_offset = $smday;
639 $ewday_offset = $emday;
641 /* add timeperiod exception */
642 $this->add_timeperiod_exception(self::DATERANGE_WEEK_DAY,
643 0, 0, 0, $swday, $swday_offset, 0, 0, 0, $ewday, $ewday_offset, $skip_interval, $timeranges);
644 return true;
646 elseif(in_array($str1, $valid_months) && in_array($str2, $valid_months))
648 $smon = $month_numbers[$str1];
649 $emon = $month_numbers[$str2];
650 /* february 1 - march 15 / 3 */
651 $this->add_timeperiod_exception(self::DATERANGE_MONTH_DATE,
652 0, $smon, $smday, 0, 0,
653 0, $emon, $emday, 0, 0, $skip_interval, $timeranges);
654 return true;
656 else if(!strcmp($str1,"day") && !strcmp($str2,"day"))
658 /* day 4 - 6 / 2 */
659 $this->add_timeperiod_exception(self::DATERANGE_MONTH_DAY,
660 0, 0, $smday, 0, 0, 0, 0, $emday, 0, 0, $skip_interval, $timeranges);
661 return true;
663 return false;
666 $items = array_filter(sscanf($input,"%[a-z] %d - %d / %d %[0-9:, -]"));
667 if(count($items) == 5)
669 list($str1, $smday, $emday, $skip_interval, $timeranges) = $items;
671 /* february 1 - 15 / 3 */
672 /* monday 2 - 3 / 2 */
673 /* day 1 - 25 / 4 */
674 if(in_array($str1, $valid_weekdays))
676 $swday = $weekday_numbers[$str1];
677 /* thursday 2 - 4 */
678 $swday_offset = $smday;
679 $ewday = $swday;
680 $ewday_offset = $emday;
681 $this->add_timeperiod_exception(self::DATERANGE_WEEK_DAY,
682 0, 0, 0, $swday, $swday_offset, 0, 0, 0, $ewday, $ewday_offset, $skip_interval, $timeranges);
683 return true;
685 else if(in_array($str1, $valid_months))
687 $smon = $month_numbers[$str1];
688 $emon = $smon;
689 /* february 3 - 5 */
690 $this->add_timeperiod_exception(self::DATERANGE_MONTH_DATE,
691 0, $smon, $smday, 0, 0,
692 0, $emon, $emday, 0, 0, $skip_interval, $timeranges);
693 return true;
695 else if(!strcmp($str1, "day"))
697 /* day 1 - 4 */
698 $this->add_timeperiod_exception(self::DATERANGE_MONTH_DAY,
699 0, 0, $smday, 0, 0, 0, 0, $emday, 0, 0, $skip_interval, $timeranges);
700 return true;
702 return false;
705 $items = array_filter(sscanf($input,"%[a-z] %d %[a-z] - %[a-z] %d %[a-z] %[0-9:, -]"));
706 if(count($items) == 7)
708 list($str1, $swday_offset, $str2, $str3, $ewday_offset, $str4, $timeranges) = $items;
710 /* wednesday 1 january - thursday 2 july */
711 if(in_array($str1, $valid_weekdays) && in_array($str2, $valid_months) &&
712 in_array($str3, $valid_weekdays) && in_array($str4, $valid_months))
714 $swday = $weekday_numbers[$str1];
715 $smon = $month_numbers[$str2];
716 $ewday = $weekday_numbers[$str3];
717 $emon = $month_numbers[$str4];
718 $this->add_timeperiod_exception(self::DATERANGE_MONTH_WEEK_DAY,
719 0, $smon, 0, $swday, $swday_offset, 0, $emon, 0, $ewday, $ewday_offset, 0, $timeranges);
720 return true;
722 return false;
725 $items=array_filter(sscanf($input,"%[a-z] %d - %d %[0-9:, -]"));
726 if(count($items) == 4)
728 list($str1, $smday, $emday, $timeranges) = $items;
730 /* february 3 - 5 */
731 /* thursday 2 - 4 */
732 /* day 1 - 4 */
733 if(in_array($str1, $valid_weekdays))
735 /* thursday 2 - 4 */
736 $swday = $weekday_numbers[$str1];
737 $swday_offset = $smday;
738 $ewday = $weekday_numbers[$swday];
739 $ewday_offset = $emday;
740 $this->add_timeperiod_exception(self::DATERANGE_WEEK_DAY,
741 0, 0, 0, $swday, $swday_offset, 0, 0, 0, $ewday, $ewday_offset, 0, $timeranges);
742 return true;
744 else if(in_array($str1, $valid_months))
746 /* february 3 - 5 */
747 $smon = $month_numbers[$str1];
748 $emon = $smon;
749 $this->add_timeperiod_exception(self::DATERANGE_MONTH_DATE,
750 0, $smon, $smday, 0, 0, 0, $emon, $emday, 0, 0, 0, $timeranges);
751 return true;
753 else if(!strcmp($str1,"day"))
755 /* day 1 - 4 */
756 $this->add_timeperiod_exception(self::DATERANGE_MONTH_DAY,
757 0, 0, $smday, 0, 0, 0, 0, $emday, 0, 0, 0, $timeranges);
758 return true;
760 return false;
763 $items = array_filter(sscanf($input,"%[a-z] %d - %[a-z] %d %[0-9:, -]"));
764 if(count($items) == 5)
766 list($str1, $smday, $str2, $emday, $timeranges) = $items;
767 /* february 1 - march 15 */
768 /* monday 2 - thursday 3 */
769 /* day 1 - day 5 */
770 if(in_array($str1, $valid_weekdays) && in_array($str2, $valid_weekdays))
772 /* monday 2 - thursday 3 */
773 $swday = $weekday_numbers[$str1];
774 $ewday = $weekday_numbers[$str2];
775 $swday_offset = $smday;
776 $ewday_offset = $emday;
777 $this->add_timeperiod_exception(self::DATERANGE_WEEK_DAY,
778 0, 0, 0, $swday, $swday_offset, 0, 0, 0, $ewday, $ewday_offset, 0, $timeranges);
779 return true;
781 elseif(in_array($str1, $valid_months) && in_array($str2, $valid_months))
783 /* february 1 - march 15 */
784 $smon = $month_numbers[$str1];
785 $emon = $month_numbers[$str2];
786 $this->add_timeperiod_exception(self::DATERANGE_MONTH_DATE,
787 0, $smon, $smday, 0, 0, 0, $emon, $emday, 0, 0, 0, $timeranges);
788 return true;
790 else if(!strcmp($str1,"day") && !strcmp($str2,"day"))
792 /* day 1 - day 5 */
793 $this->add_timeperiod_exception(self::DATERANGE_MONTH_DAY,
794 0, 0, $smday, 0, 0, 0, 0, $emday, 0, 0, 0, $timeranges);
795 return true;
797 return false;
800 $items = array_filter(sscanf($input,"%[a-z] %d%*[ \t]%[0-9:, -]"));
801 if(count($items) == 3)
803 list($str1, $smday, $timeranges) = $items;
804 /* february 3 */
805 /* thursday 2 */
806 /* day 1 */
807 if(in_array($str1, $valid_weekdays))
809 /* thursday 2 */
810 $swday = $weekday_numbers[$str1];
811 $swday_offset = $smday;
812 $ewday = $swday;
813 $ewday_offset = $swday_offset;
814 $this->add_timeperiod_exception(self::DATERANGE_WEEK_DAY,
815 0, 0, 0, $swday, $swday_offset, 0, 0, 0, $ewday, $ewday_offset, 0, $timeranges);
816 return true;
818 elseif(in_array($str1, $valid_months))
820 /* february 3 */
821 $smon = $month_numbers[$str1];
822 $emon = $smon;
823 $emday = $smday;
824 $this->add_timeperiod_exception(self::DATERANGE_MONTH_DATE,
825 0, $smon, $smday, 0, 0, 0, $emon, $emday, 0, 0, 0, $timeranges);
826 return true;
828 else if(!strcmp($str1,"day"))
830 /* day 1 */
831 $emday = $smday;
832 $this->add_timeperiod_exception(self::DATERANGE_MONTH_DAY,
833 0, 0, $smday, 0, 0, 0, 0, $emday, 0, 0, 0, $timeranges);
834 return true;
836 return false;
839 $items = array_filter(sscanf($input,"%[a-z] %d %[a-z] %[0-9:, -]"));
840 if(count($items) == 4)
842 list($str1, $swday_offset, $str2, $timeranges) = $items;
844 /* thursday 3 february */
845 if(in_array($str1, $valid_weekdays) && in_array($str2, $valid_months))
847 $swday = $weekday_numbers[$str1];
848 $smon = $month_numbers[$str2];
849 $emon = $smon;
850 $ewday = $swday;
851 $ewday_offset = $swday_offset;
852 $this->add_timeperiod_exception(self::DATERANGE_MONTH_WEEK_DAY,
853 0, $smon, 0, $swday, $swday_offset, 0, $emon, 0, $ewday, $ewday_offset, 0, $timeranges);
854 return true;
856 // return false;
859 # syntactically incorrect variable
860 return false;
864 * Return true if both the start date and end date is the same day
866 * @param $dr A daterange
867 * @return true if condition holds
869 public function is_daterange_single_day(&$dr)
871 if($dr['syear'] != $dr['eyear'])
872 return false;
873 if($dr['smon'] != $dr['emon'])
874 return false;
875 if($dr['smday'] != $dr['emday'])
876 return false;
877 if($dr['swday'] != $dr['ewday'])
878 return false;
879 if($dr['swday_offset'] != $dr['ewday_offset'])
880 return false;
882 return true;
886 * Print the timerange $r
887 * @param $r A timerange
889 public function print_timerange(&$r)
891 print "$r[start]-$r[stop]";
895 * Converts a date into timestamp, with some extra features such as
896 * negative days of month to use days from the end of the month.
897 * As for time, 00:00:00 of the day is used.
899 * @param $year Year.
900 * @param $month Month.
901 * @param $monthday - Day of month, can be negative.
902 * @return The resulting timestamp.
904 public function calculate_time_from_day_of_month($year, $month, $monthday)
906 $day = 0;
908 /* positive day (3rd day) */
909 if($monthday > 0)
911 $midnight = mktime(0,0,0, $month, $monthday, $year);
913 /* if we rolled over to the next month, time is invalid */
914 /* assume the user's intention is to keep it in the current month */
915 if(date("n", $midnight) != $month)
916 $midnight = 0;
917 } else {/* negative offset (last day, 3rd to last day) */
918 /* find last day in the month */
919 $day = 32;
922 /* back up a day */
923 $day--;
925 /* make the new time */
926 $midnight = mktime(0,0,0, $month, $day, $year);
928 } while(date("n", $midnight) != $month);
930 /* now that we know the last day, back up more */
932 /* -1 means last day of month, so add one to to make this correct - Mike Bird */
933 $d = date("d", $midnight) + (($monthday < -30) ? -30 : $monthday + 1);
934 $midnight = mktime(0,0,0, $month, $d, $year);
936 /* if we rolled over to the previous month, time is invalid */
937 /* assume the user's intention is to keep it in the current month */
938 if(date("n", $midnight) != $month)
939 $midnight = 0;
942 return $midnight;
946 * Nagios supports exceptions such as "third monday in november 2010" - this
947 * converts such statements to unix timestamps.
949 * @param $year The year
950 * @param $month The month number
951 * @param $weekday The weekday's numeric presentation (0=sunday, 6=saturday)
952 * @param $weekday_offset Which occurence of the weekday, can be negative
954 public function calculate_time_from_weekday_of_month($year, $month, $weekday, $weekday_offset)
956 $weeks = 0;
957 $midnight = mktime(0,0,0, $month, 1, $year);
958 /* how many days must we advance to reach the first instance of the weekday this month? */
959 $days = $weekday - date("w", $midnight);
960 if($days < 0)
961 $days += 7;
962 /* positive offset (3rd thursday) */
963 if($weekday_offset > 0)
965 /* how many weeks must we advance (no more than 5 possible) */
966 $weeks = ($weekday_offset > 5) ? 5 : $weekday_offset;
967 $days += (($weeks - 1) * 7);
969 /* make the new time */
970 $midnight = mktime(0,0,0, $month, $days + 1, $year);
971 /* if we rolled over to the next month, time is invalid */
972 /* assume the user's intention is to keep it in the current month */
973 if(date("n", $midnight) != $month)
974 $midnight = 0;
975 } else { /* negative offset (last thursday, 3rd to last tuesday) */
976 /* find last instance of weekday in the month */
977 $days += (5*7);
980 /* back up a week */
981 $days -= 7;
983 /* make the new time */
984 $midnight = mktime(0,0,0, $month, $days + 1, $year);
986 } while(date("n", $midnight) != $month);
988 /* now that we know the last instance of the weekday, back up more */
989 $weeks = ($weekday_offset < -5) ? -5 : $weekday_offset;
990 $days = (($weeks + 1) * 7);
992 $midnight = mktime(0,0,0, $month, $days + date("d", $midnight), $year);
994 /* if we rolled over to the previous month, time is invalid */
995 /* assume the user's intention is to keep it in the current month */
996 if(date("n", $midnight) != $month)
997 $midnight = 0;
999 return $midnight;
1003 * Determines if two timeranges overlap
1004 * Note: stop time equal to start time in other range is NOT considered an overlap
1006 * @param $range1 array('start'=> {timestamp}, 'stop' => {timestamp})
1007 * @param $range2 array('start'=> {timestamp}, 'stop' => {timestamp})
1008 * @param $inclusive Whether to count "straddling" periods as ovelapping,
1009 * (Eg: start1 == stop2 or start2 == stop1)
1010 * @return boolean
1012 public function timeranges_overlap(&$range1, &$range2, $inclusive=false)
1014 if($inclusive)
1015 return ($range1['start'] <= $range2['stop'] && $range2['start'] <= $range1['stop']) ||
1016 ($range2['start'] <= $range1['stop'] && $range1['start'] <= $range2['stop']);
1018 return ($range1['start'] < $range2['stop'] && $range2['start'] < $range1['stop']) ||
1019 ($range2['start'] < $range1['stop'] && $range1['start'] < $range2['stop']);
1023 * Merges two timeranges into one.
1025 * Assumes timeranges actually overlap and timeranges are correct (stop time after start time)
1027 * @param $src_range array
1028 * @param $dst_range array
1029 * @return array
1031 public function merge_timeranges(&$src_range, &$dst_range)
1033 return array('start' => min($src_range['start'], $dst_range['start']),
1034 'stop' => max($src_range['stop'], $dst_range['stop']));
1038 * Add a new timerange to a set of timeranges.
1039 * If new range overlaps an existing range, the two are merged to one.
1041 * Assumes timeranges contain only valid values (eg: stop time after start time)
1042 * Assumes the timerange set does not contain overlapping periods itself
1044 * @param $range Array of range(s) to add
1045 * @param $timerange_set The timerange set to add to
1047 public function add_timerange_to_set($range, &$timerange_set)
1049 for($i=0 ; $i<count($timerange_set) ; $i++)
1051 $testrange = $timerange_set[$i];
1052 if(self::timeranges_overlap($range, $testrange, true)) {
1053 # if range overlaps with current item, merge them and continue
1054 $range = self::merge_timeranges($range, $testrange);
1056 # remove the existing range, to later re-add it in the end
1057 unset($timerange_set[$i]);
1059 # to get the numerical indices back into sequence:
1060 $timerange_set = array_values($timerange_set);
1062 # Restart so we don't miss any element
1063 $i = 0;
1066 $timerange_set[] = $range;
1068 # recombobulate the indices
1069 $timerange_set = array_values($timerange_set);
1073 * Merge two sets of timeranges into one, with no overlapping ranges.
1074 * Assumption: The argument sets may contain overlapping timeranges,
1075 * which are wrong in principle, but we'll manage anyway.
1077 * @param $timerange_set1 (of structure array( array('start' => 1203120, 'stop' => 120399), aray('start' => 140104, 'stop') ....)
1078 * @param $timerange_set2 (of structure array( array('start' => 1203120, 'stop' => 120399), aray('start' => 140104, 'stop') ....)
1079 * @return array
1081 public function merge_timerange_sets(&$timerange_set1, &$timerange_set2)
1083 $resulting_timerange_set = array();
1085 # plan: add both ranges to third set, merging as we go along
1087 foreach($timerange_set1 as $range)
1089 self::add_timerange_to_set($range, $resulting_timerange_set);
1092 foreach($timerange_set2 as $range)
1094 self::add_timerange_to_set($range, $resulting_timerange_set);
1096 return $resulting_timerange_set;
1100 * Fetch info on a timeperiod
1102 * @param $period string: Timeperiod name
1103 * @return an array of the timeperiod's properties
1105 public static function get($period)
1107 $db = Database::instance();
1108 $query = 'SELECT * FROM timeperiod ' .
1109 'WHERE timeperiod_name = ' .$db->escape($period);
1110 $res = $db->query($query);
1111 if (!$res) {
1112 return false;
1115 $res = $res->result(false);
1116 $res = $res->current();
1118 if ($res) {
1119 $query = "SELECT variable, value FROM custom_vars WHERE obj_type = 'timeperiod' AND obj_id = {$res['id']}";
1120 $exception_res = $db->query($query);
1121 foreach ($exception_res as $exception) {
1122 $res[$exception->variable] = $exception->value;
1125 $query = "SELECT tp.timeperiod_name FROM timeperiod tp".
1126 " JOIN timeperiod_exclude ON exclude = id ".
1127 " WHERE timeperiod = {$res['id']}";
1129 $exclude_res = $db->query($query);
1130 // it might seem appropriate to be all recursive about this,
1131 // but the recursiveness is already done on a model-level, so
1132 // we have no use for anything but name
1133 foreach ($exclude_res as $exclude) {
1134 $res['excludes'][] = $exclude->timeperiod_name;
1137 return $res;
1140 private static $timeperiods_all = false;
1143 * Fetch all timperiods
1144 * @return db result
1146 public static function get_all()
1148 if( self::$timeperiods_all !== false )
1149 return self::$timeperiods_all;
1150 $result = array();
1151 $res = Livestatus::instance()->getTimeperiods(array('columns'=>array('name')));
1152 foreach ($res as $row) {
1153 $result[$row['name']] = $row['name'];
1155 self::$timeperiods_all = $result;
1156 return $result;