Add : service without host will be just droped, like Nagios.
[shinken.git] / shinken / daterange.py
blob8c9491ebf25ce52b26762e7edb47e0d44b6513c3
1 #!/usr/bin/env python
2 #Copyright (C) 2009-2010 :
3 # Gabes Jean, naparuba@gmail.com
4 # Gerhard Lausser, Gerhard.Lausser@consol.de
5 # Gregory Starck, g.starck@gmail.com
6 # Hartmut Goebel, h.goebel@goebel-consult.de
8 #This file is part of Shinken.
10 #Shinken is free software: you can redistribute it and/or modify
11 #it under the terms of the GNU Affero General Public License as published by
12 #the Free Software Foundation, either version 3 of the License, or
13 #(at your option) any later version.
15 #Shinken is distributed in the hope that it will be useful,
16 #but WITHOUT ANY WARRANTY; without even the implied warranty of
17 #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 #GNU Affero General Public License for more details.
20 #You should have received a copy of the GNU Affero General Public License
21 #along with Shinken. If not, see <http://www.gnu.org/licenses/>.
23 import time, calendar
25 from shinken.util import get_sec_from_morning, get_day, get_start_of_day, get_end_of_day
28 #Get the day number (like 27 in July tuesday 27 2010 for call:
29 #2010, july, tuesday, -1 (last tuesday of july 2010)
30 def find_day_by_weekday_offset(year, month, weekday, offset):
31 #et the id if teh weekday (1 for tuesday)
32 weekday_id = Daterange.get_weekday_id(weekday)
33 if weekday_id is None:
34 return None
36 #same for month
37 month_id = Daterange.get_month_id(month)
38 if month_id is None:
39 return None
41 #thanks calendar :)
42 cal = calendar.monthcalendar(year, month_id)
44 #If we ask for a -1 day, just reverse cal
45 if offset < 0:
46 offset = abs(offset)
47 cal.reverse()
49 #ok go for it
50 nb_found = 0
51 try:
52 for i in xrange(0, offset + 1):
53 #in cal 0 mean "there are no day here :)"
54 if cal[i][weekday_id] != 0:
55 nb_found += 1
56 if nb_found == offset:
57 return cal[i][weekday_id]
58 return None
59 except:
60 return None
63 def find_day_by_offset(year, month, offset):
64 month_id = Daterange.get_month_id(month)
65 if month_id is None:
66 return None
67 (tmp, days_in_month) = calendar.monthrange(year, month_id)
68 if offset >= 0:
69 return min(offset, days_in_month)
70 else:
71 return max(1, days_in_month + offset + 1)
74 class Timerange:
75 #entry is like 00:00-24:00
76 def __init__(self, entry):
77 entries = entry.split('-')
78 start = entries[0]
79 end = entries[1]
80 sentries = start.split(':')
81 self.hstart = int(sentries[0])
82 self.mstart = int(sentries[1])
83 eentries = end.split(':')
84 self.hend = int(eentries[0])
85 self.mend = int(eentries[1])
87 def __str__(self):
88 return str(self.__dict__)
90 def get_sec_from_morning(self):
91 return self.hstart*3600 + self.mstart*60
94 def get_first_sec_out_from_morning(self):
95 #If start at 0:0, the min out is the end
96 if self.hstart == 0 and self.mstart == 0:
97 return self.hend*3600 + self.mend*60
98 return 0
101 def is_time_valid(self, t):
102 sec_from_morning = get_sec_from_morning(t)
103 return self.hstart*3600 + self.mstart* 60 <= sec_from_morning <= self.hend*3600 + self.mend* 60
107 class Daterange:
108 weekdays = {'monday' : 0, 'tuesday' : 1, 'wednesday' : 2, 'thursday' : 3, \
109 'friday' : 4, 'saturday' : 5, 'sunday': 6 }
110 months = {'january' : 1, 'february': 2, 'march' : 3, 'april' : 4, 'may' : 5, \
111 'june' : 6, 'july' : 7, 'august' : 8, 'september' : 9, \
112 'october' : 10, 'november' : 11, 'december' : 12}
113 def __init__(self, syear, smon, smday, swday, swday_offset,
114 eyear, emon, emday, ewday, ewday_offset, skip_interval, other):
115 self.syear = int(syear)
116 self.smon = smon
117 self.smday = int(smday)
118 self.swday = swday
119 self.swday_offset = int(swday_offset)
120 self.eyear = int(eyear)
121 self.emon = emon
122 self.emday = int(emday)
123 self.ewday = ewday
124 self.ewday_offset = int(ewday_offset)
125 self.skip_interval = int(skip_interval)
126 self.other = other
127 self.timeranges = []
129 for timeinterval in other.split(','):
130 self.timeranges.append(Timerange(timeinterval.strip()))
133 def __str__(self):
134 return ''#str(self.__dict__)
137 #By default, daterange are correct
138 def is_correct(self):
139 return True
142 def get_month_id(cls, month):
143 try:
144 return Daterange.months[month]
145 except:
146 return None
147 get_month_id = classmethod(get_month_id)
150 #@memoized
151 def get_month_by_id(cls, id):
152 id = id % 12
153 for key in Daterange.months:
154 if id == Daterange.months[key]:
155 return key
156 return None
157 get_month_by_id = classmethod(get_month_by_id)
160 def get_weekday_id(cls, weekday):
161 try:
162 return Daterange.weekdays[weekday]
163 except:
164 return None
165 get_weekday_id = classmethod(get_weekday_id)
168 def get_weekday_by_id(cls, id):
169 id = id % 7
170 for key in Daterange.weekdays:
171 if id == Daterange.weekdays[key]:
172 return key
173 return None
174 get_weekday_by_id = classmethod(get_weekday_by_id)
178 def get_start_and_end_time(self, ref=None):
179 print "Not implemented"
182 def is_time_valid(self, t):
183 if self.is_time_day_valid(t):
184 for tr in self.timeranges:
185 if tr.is_time_valid(t):
186 return True
187 return False
190 def get_min_sec_from_morning(self):
191 mins = []
192 for tr in self.timeranges:
193 mins.append(tr.get_sec_from_morning())
194 return min(mins)
197 def get_min_sec_out_from_morning(self):
198 mins = []
199 for tr in self.timeranges:
200 mins.append(tr.get_first_sec_out_from_morning())
201 return min(mins)
204 def get_min_from_t(self, t):
205 if self.is_time_valid(t):
206 return t
207 t_day_epoch = get_day(t)
208 tr_mins = self.get_min_sec_from_morning()
209 return t_day_epoch + tr_mins
212 def is_time_day_valid(self, t):
213 (start_time, end_time) = self.get_start_and_end_time(t)
214 # print "My class", self.__class__
215 # print "Search for t", time.asctime(time.localtime(start_time))
216 # print "Start time, endtime", time.asctime(time.localtime(start_time)), time.asctime(time.localtime(end_time))
217 if start_time <= t <= end_time:
218 return True
219 else:
220 return False
223 def is_time_day_invalid(self, t):
224 (start_time, end_time) = self.get_start_and_end_time(t)
225 if start_time <= t <= end_time:
226 return False
227 else:
228 return True
231 #def have_future_tiremange_valid(self, t):
232 # starts = []
233 # for tr in self.timeranges:
234 # tr_start = tr.hstart * 3600 + tr.mstart * 3600
235 # if tr_start >= sec_from_morning:
236 # return True
237 # return False
240 def get_next_future_timerange_valid(self, t):
241 sec_from_morning = get_sec_from_morning(t)
242 starts = []
243 for tr in self.timeranges:
244 tr_start = tr.hstart * 3600 + tr.mstart * 60
245 if tr_start >= sec_from_morning:
246 starts.append(tr_start)
247 if starts != []:
248 return min(starts)
249 else:
250 return None
253 def get_next_future_timerange_invalid(self, t):
254 #print 'Call for get_next_future_timerange_invalid from ', time.asctime(time.localtime(t))
255 sec_from_morning = get_sec_from_morning(t)
256 #print 'sec from morning', sec_from_morning
257 ends = []
258 for tr in self.timeranges:
259 tr_start = tr.hstart * 3600 + tr.mstart * 60
260 if tr_start >= sec_from_morning:
261 ends.append(tr_start)
262 tr_end = tr.hend * 3600 + tr.mend * 60
263 if tr_end >= sec_from_morning:
264 ends.append(tr_end)
265 #print "Ends:", ends
266 #Remove the last second of the day for 00->24h"
267 if 86400 in ends:
268 ends.remove(86400)
269 if ends != []:
270 return min(ends)
271 else:
272 return None
275 def get_next_valid_day(self, t):
276 if self.get_next_future_timerange_valid(t) is None:
277 #this day is finish, we check for next period
278 (start_time, end_time) = self.get_start_and_end_time(get_day(t)+86400)
279 else:
280 (start_time, end_time) = self.get_start_and_end_time(t)
282 #print self.__class__
283 #print "Get next valid day start/end for", time.asctime(time.localtime(t))
284 #print "Start", time.asctime(time.localtime(start_time))
285 #print "End", time.asctime(time.localtime(end_time))
287 if t <= start_time:
288 return get_day(start_time)
290 if self.is_time_day_valid(t):
291 return get_day(t)
292 return None
296 def get_next_valid_time_from_t(self, t):
297 #print "DR Get next valid from:", time.asctime(time.localtime(t))
298 #print "DR Get next valid from:", t
299 if self.is_time_valid(t):
300 return t
302 #print "DR Get next valid from:", time.asctime(time.localtime(t))
303 #First we search fot the day of t
304 t_day = self.get_next_valid_day(t)
305 sec_from_morning = self.get_min_sec_from_morning()
307 #print "Search for t", time.asctime(time.localtime(t))
308 #print "DR: next day", time.asctime(time.localtime(t_day))
309 #print "DR: sec from morning", time.asctime(time.localtime(sec_from_morning))
311 #We search for the min of all tr.start > sec_from_morning
312 starts = []
313 for tr in self.timeranges:
314 tr_start = tr.hstart * 3600 + tr.mstart * 3600
315 if tr_start >= sec_from_morning:
316 starts.append(tr_start)
318 #tr can't be valid, or it will be return at the begining
319 sec_from_morning = self.get_next_future_timerange_valid(t)
320 #print "DR: sec from morning", time.asctime(time.localtime(sec_from_morning))
321 #print "Sec from morning", t_day
322 if sec_from_morning is not None:
323 if t_day is not None and sec_from_morning is not None:
324 return t_day + sec_from_morning
326 #Then we search for the next day of t
327 #The sec will be the min of the day
328 t = get_day(t)+86400
329 t_day2 = self.get_next_valid_day(t)
330 sec_from_morning = self.get_next_future_timerange_valid(t_day2)
331 if t_day2 is not None and sec_from_morning is not None:
332 return t_day2 + sec_from_morning
333 else:
334 #I'm not find any valid time
335 return None
338 def get_next_invalid_day(self, t):
339 #print 'DR: get_next_invalid_day for', time.asctime(time.localtime(t))
340 if self.is_time_day_invalid(t):
341 return t
343 next_future_timerange_invalid = self.get_next_future_timerange_invalid(t)
344 #print "next_future_timerange_invalid:", next_future_timerange_invalid
346 #If today there is no more unavalable timerange, search the next day
347 if next_future_timerange_invalid is None:
348 #print 'DR: get_next_future_timerange_invalid is None'
349 #this day is finish, we check for next period
350 (start_time, end_time) = self.get_start_and_end_time(get_day(t)+86400)
351 else:
352 #print 'DR: get_next_future_timerange_invalid is', time.asctime(time.localtime(next_future_timerange_invalid))
353 (start_time, end_time) = self.get_start_and_end_time(t)
354 #res = get_day(t) + next_future_timerange_invalid
355 #print "Early return"
356 #return res
358 #print 'DR:Start:', time.asctime(time.localtime(start_time))
359 #print 'DR:End:', time.asctime(time.localtime(end_time))
360 #The next invalid day can be t day if there a possible
361 #invalid time range (timerange is not 00->24
362 if next_future_timerange_invalid is not None:
363 if start_time <= t <= end_time:
364 #print "Early Return next invalid day:", time.asctime(time.localtime(get_day(t)))
365 return get_day(t)
366 if start_time >= t :
367 return get_day(start_time)
368 else:#Else, there is no possibility than in our start_time<->end_time we got
369 #any invalid time (full period out). So it's end_time+1 sec (tomorow of end_time)
370 #print "Full period out, got end_time", time.asctime(time.localtime(get_day(end_time +1)))
371 return get_day(end_time +1)
373 return None
376 def get_next_invalid_time_from_t(self, t):
377 #print 'DR:get_next_invalid_time_from_t', time.asctime(time.localtime(t))
378 if not self.is_time_valid(t):
379 #print "DR: cool, t is invalid", time.asctime(time.localtime(t))
380 return t
381 #else:
382 # print "DR: Arg, t is valid", time.asctime(time.localtime(t))
384 #First we search fot the day of t
385 t_day = self.get_next_invalid_day(t)
386 #print "Get next invalid day:", time.asctime(time.localtime(t_day))
387 #print "Is valid day?", self.is_time_valid(t_day)
389 #We search for the min of all tr.start > sec_from_morning
390 #starts = []
391 #for tr in self.timeranges:
392 # tr_start = tr.hstart * 3600 + tr.mstart * 3600
393 # if tr_start >= sec_from_morning:
394 # starts.append(tr_start)
396 #tr can't be valid, or it will be return at the begining
397 sec_from_morning = self.get_next_future_timerange_invalid(t)
398 #print "TOTO sec_from_morning:", sec_from_morning
399 #Ok we've got a next invalid day and a invalid possibility in
400 #timerange, so the next invalid is this day+sec_from_morning
401 #print "T_day", t_day, "Sec from morning", sec_from_morning
402 if t_day is not None and sec_from_morning is not None:
403 return t_day + sec_from_morning + 1
405 #We've got a day but no sec_from_morning : the timerange is full (0->24h)
406 #so the next invalid is this day at the day_start
407 if t_day is not None and sec_from_morning is None:
408 return t_day
410 #Then we search for the next day of t
411 #The sec will be the min of the day
412 t = get_day(t)+86400
413 t_day2 = self.get_next_invalid_day(t)
414 sec_from_morning = self.get_next_future_timerange_invalid(t_day2)
415 if t_day2 is not None and sec_from_morning is not None:
416 return t_day2 + sec_from_morning + 1
418 if t_day2 is not None and sec_from_morning is None:
419 return t_day2
420 else:
421 #I'm not find any valid time
422 return None
427 #ex: 2007-01-01 - 2008-02-01
428 class CalendarDaterange(Daterange):
429 def get_start_and_end_time(self, ref=None):
430 start_time = get_start_of_day(self.syear, int(self.smon), self.smday)
431 end_time = get_end_of_day(self.eyear, int(self.emon), self.emday)
432 return (start_time, end_time)
436 #Like tuesday 00:00-24:00
437 class StandardDaterange(Daterange):
438 def __init__(self, day, other):
439 self.other = other
440 self.timeranges = []
442 for timeinterval in other.split(','):
443 self.timeranges.append(Timerange(timeinterval.strip()))
444 self.day = day
447 #It's correct only if the weekday (sunday, etc) is a valid one
448 def is_correct(self):
449 b = self.day in Daterange.weekdays
450 if not b:
451 print "Error : %s is not a valid day" % self.day
452 return b
455 def get_start_and_end_time(self, ref=None):
456 now = time.localtime(ref)
457 self.syear = now.tm_year
458 self.month = now.tm_mon
459 #month_start_id = now.tm_mon
460 #month_start = Daterange.get_month_by_id(month_start_id)
461 self.wday = now.tm_wday
462 day_id = Daterange.get_weekday_id(self.day)
463 today_morning = get_start_of_day(now.tm_year, now.tm_mon, now.tm_mday)
464 tonight = get_end_of_day(now.tm_year, now.tm_mon, now.tm_mday)
465 day_diff = (day_id - now.tm_wday) % 7
466 return (today_morning + day_diff*86400, tonight + day_diff*86400)
468 #thusday 3 february
469 class MonthWeekDayDaterange(Daterange):
470 #It's correct only if the weekday (sunday, etc) is a valid one
471 def is_correct(self):
472 b = True
473 b &= self.swday in Daterange.weekdays
474 if not b:
475 print "Error : %s is not a valid day" % self.swday
477 b &= self.ewday in Daterange.weekdays
478 if not b:
479 print "Error : %s is not a valid day" % self.ewday
481 return b
484 def get_start_and_end_time(self, ref=None):
485 now = time.localtime(ref)
487 if self.syear == 0:
488 self.syear = now.tm_year
489 month_id = Daterange.get_month_id(self.smon)
490 day_start = find_day_by_weekday_offset(self.syear, self.smon, self.swday, self.swday_offset)
491 start_time = get_start_of_day(self.syear, month_id, day_start)
493 if self.eyear == 0:
494 self.eyear = now.tm_year
495 month_end_id = Daterange.get_month_id(self.emon)
496 day_end = find_day_by_weekday_offset(self.eyear, self.emon, self.ewday, self.ewday_offset)
497 end_time = get_end_of_day(self.eyear, month_end_id, day_end)
499 now_epoch = time.mktime(now)
500 if start_time > end_time: #the period is between years
501 if now_epoch > end_time:#check for next year
502 day_end = find_day_by_weekday_offset(self.eyear + 1, self.emon, self.ewday, self.ewday_offset)
503 end_time = get_end_of_day(self.eyear + 1, month_end_id, day_end)
504 else:#it s just that start was the last year
505 day_start = find_day_by_weekday_offset(self.syear - 1, self.smon, self.swday, self.swday_offset)
506 start_time = get_start_of_day(self.syear - 1, month_id, day_start)
507 else:
508 if now_epoch > end_time:#just have to check for next year if necessery
509 day_start = find_day_by_weekday_offset(self.syear + 1, self.smon, self.swday, self.swday_offset)
510 start_time = get_start_of_day(self.syear + 1, month_id, day_start)
511 day_end = find_day_by_weekday_offset(self.eyear + 1, self.emon, self.ewday, self.ewday_offset)
512 end_time = get_end_of_day(self.eyear + 1, month_end_id, day_end)
514 return (start_time, end_time)
518 class MonthDateDaterange(Daterange):
519 def get_start_and_end_time(self, ref=None):
520 now = time.localtime(ref)
521 if self.syear == 0:
522 self.syear = now.tm_year
523 month_start_id = Daterange.get_month_id(self.smon)
524 day_start = find_day_by_offset(self.syear, self.smon, self.smday)
525 start_time = get_start_of_day(self.syear, month_start_id, day_start)
527 if self.eyear == 0:
528 self.eyear = now.tm_year
529 month_end_id = Daterange.get_month_id(self.emon)
530 day_end = find_day_by_offset(self.eyear, self.emon, self.emday)
531 end_time = get_end_of_day(self.eyear, month_end_id, day_end)
533 now_epoch = time.mktime(now)
534 if start_time > end_time: #the period is between years
535 if now_epoch > end_time:#check for next year
536 day_end = find_day_by_offset(self.eyear + 1, self.emon, self.emday)
537 end_time = get_end_of_day(self.eyear + 1, month_end_id, day_end)
538 else:#it s just that start was the last year
539 day_start = find_day_by_offset(self.syear-1, self.smon, self.emday)
540 start_time = get_start_of_day(self.syear-1, month_start_id, day_start)
541 else:
542 if now_epoch > end_time:#just have to check for next year if necessery
543 day_start = find_day_by_offset(self.syear+1, self.smon, self.emday)
544 start_time = get_start_of_day(self.syear+1, month_start_id, day_start)
545 day_end = find_day_by_offset(self.eyear+1, self.emon, self.emday)
546 end_time = get_end_of_day(self.eyear+1, month_end_id, day_end)
548 return (start_time, end_time)
551 class WeekDayDaterange(Daterange):
552 def get_start_and_end_time(self, ref=None):
553 now = time.localtime(ref)
555 #If no year, it's our year
556 if self.syear == 0:
557 self.syear = now.tm_year
558 month_start_id = now.tm_mon
559 month_start = Daterange.get_month_by_id(month_start_id)
560 day_start = find_day_by_weekday_offset(self.syear, month_start, self.swday, self.swday_offset)
561 start_time = get_start_of_day(self.syear, month_start_id, day_start)
563 #Same for end year
564 if self.eyear == 0:
565 self.eyear = now.tm_year
566 month_end_id = now.tm_mon
567 month_end = Daterange.get_month_by_id(month_end_id)
568 day_end = find_day_by_weekday_offset(self.eyear, month_end, self.ewday, self.ewday_offset)
569 end_time = get_end_of_day(self.eyear, month_end_id, day_end)
571 #Maybe end_time is before start. So look for the
572 #next month
573 if start_time > end_time:
574 month_end_id = month_end_id + 1
575 if month_end_id > 12:
576 month_end_id = 1
577 self.eyear += 1
578 month_end = Daterange.get_month_by_id(month_end_id)
579 day_end = find_day_by_weekday_offset(self.eyear, month_end, self.ewday, self.ewday_offset)
580 end_time = get_end_of_day(self.eyear, month_end_id, day_end)
582 now_epoch = time.mktime(now)
583 #But maybe we look not ethouth far. We should add a month
584 if end_time < now_epoch:
585 month_end_id = month_end_id + 1
586 month_start_id = month_start_id + 1
587 if month_end_id > 12:
588 month_end_id = 1
589 self.eyear += 1
590 if month_start_id > 12:
591 month_start_id = 1
592 self.syear += 1
593 #First start
594 month_start = Daterange.get_month_by_id(month_start_id)
595 day_start = find_day_by_weekday_offset(self.syear, month_start, self.swday, self.swday_offset)
596 start_time = get_start_of_day(self.syear, month_start_id, day_start)
597 #Then end
598 month_end = Daterange.get_month_by_id(month_end_id)
599 day_end = find_day_by_weekday_offset(self.eyear, month_end, self.ewday, self.ewday_offset)
600 end_time = get_end_of_day(self.eyear, month_end_id, day_end)
602 return (start_time, end_time)
605 class MonthDayDaterange(Daterange):
606 def get_start_and_end_time(self, ref=None):
607 now = time.localtime(ref)
608 if self.syear == 0:
609 self.syear = now.tm_year
610 month_start_id = now.tm_mon
611 month_start = Daterange.get_month_by_id(month_start_id)
612 day_start = find_day_by_offset(self.syear, month_start, self.smday)
613 start_time = get_start_of_day(self.syear, month_start_id, day_start)
615 if self.eyear == 0:
616 self.eyear = now.tm_year
617 month_end_id = now.tm_mon
618 month_end = Daterange.get_month_by_id(month_end_id)
619 day_end = find_day_by_offset(self.eyear, month_end, self.emday)
620 end_time = get_end_of_day(self.eyear, month_end_id, day_end)
622 now_epoch = time.mktime(now)
624 if start_time > end_time:
625 month_end_id = month_end_id + 1
626 if month_end_id > 12:
627 month_end_id = 1
628 self.eyear += 1
629 day_end = find_day_by_offset(self.eyear, month_end, self.emday)
630 end_time = get_end_of_day(self.eyear, month_end_id, day_end)
632 if end_time < now_epoch:
633 month_end_id = month_end_id + 1
634 month_start_id = month_start_id + 1
635 if month_end_id > 12:
636 month_end_id = 1
637 self.eyear += 1
638 if month_start_id > 12:
639 month_start_id = 1
640 self.syear += 1
642 #For the start
643 month_start = Daterange.get_month_by_id(month_start_id)
644 day_start = find_day_by_offset(self.syear, month_start, self.smday)
645 start_time = get_start_of_day(self.syear, month_start_id, day_start)
647 #For the end
648 month_end = Daterange.get_month_by_id(month_end_id)
649 day_end = find_day_by_offset(self.eyear, month_end, self.emday)
650 end_time = get_end_of_day(self.eyear, month_end_id, day_end)
652 return (start_time, end_time)