scheduling class can do intervals now, some fixes
[libi2ncommon] / src / timefunc.cpp
1 /** @file
2  * @brief time related functions.
3  *
4  * @copyright Copyright © 2001-2008 by Intra2net AG
5  * @license commercial
6  * @contact info@intra2net.com
7  *
8  */
9
10
11 #include <string>
12 #include <sstream>
13 #include <iostream>
14 #include <iomanip>
15 #include <bitset>
16 #include <stdexcept>
17 #include <iterator>
18 #include <algorithm>
19
20 #include <time.h>
21 #include <unistd.h>
22 #include <string.h>
23 #include <sys/timeb.h>
24 #include <sys/syscall.h>
25
26 #include <timefunc.hxx>
27 #include <i18n.h>
28
29
30 // define missing POSIX.1b constants...
31
32 #ifndef CLOCK_REALTIME
33 #define CLOCK_REALTIME 0
34 #endif
35 #ifndef CLOCK_MONOTONIC
36 #define CLOCK_MONOTONIC 1
37 #endif
38
39
40
41 using namespace std;
42
43 double prec_time(void)
44 {
45     struct timeb tb;
46     double ret;
47
48     ftime(&tb);
49
50     ret=tb.time+(static_cast<float>(tb.millitm)/1000);
51
52     return ret;
53 }
54
55 // converts ISO-DATE: 2003-06-13
56 int date_to_seconds(const std::string &date)
57 {
58     int rtn = -1, year = -1, month = -1, day = -1;
59     
60     string::size_type pos = date.find("-");
61     if (pos == string::npos)
62         return rtn;
63     
64     istringstream in(string(date,0,pos));
65     in >> year;
66     year -= 1900;
67         
68     string dstr(date, pos+1);
69     if ((pos = dstr.find("-")) == string::npos)
70         return rtn;
71     
72     in.clear();
73     in.str(string(dstr, 0, pos));
74     in >> month;
75     month -= 1;
76         
77     in.clear();
78     in.str(string(dstr, pos+1));
79     in >> day;
80     
81     if (year < 0 || month == -1 || day == -1)
82         return rtn;
83     
84     struct tm tm_struct;
85     bzero (&tm_struct, sizeof(struct tm));
86     tm_struct.tm_year = year;
87     tm_struct.tm_mon = month;
88     tm_struct.tm_mday = day;
89     tm_struct.tm_isdst = -1;
90     
91     rtn = mktime (&tm_struct);
92     return rtn;
93 }
94
95 string make_nice_time(int seconds)
96 {
97     ostringstream out;
98
99     int days=seconds/86400;
100     seconds%=86400;
101
102     int hours=seconds/3600;
103     seconds%=3600;
104
105     int minutes=seconds/60;
106     seconds%=60;
107
108     if (days==1)
109         out << i18n("1 day") << ", ";
110     else if (days>1)
111         out << days << ' ' << i18n("days") << ", ";
112
113     out << setfill('0');
114     out << setw(2) << hours << ':' << setw(2) << minutes << ':' << setw(2) << seconds;
115
116     return out.str();
117 }
118
119 string format_full_time(int seconds)
120 {
121     char buf[50];
122     memset (buf, 0, 50);
123     struct tm *ta = localtime ((time_t *)&seconds);
124
125     strftime (buf, 49, "%d.%m.%Y %H:%M", ta);
126     return string(buf);
127 }
128
129 void seconds_to_hour_minute(int seconds, int *hour, int *minute)
130 {
131     if (hour != NULL) {
132         *hour = 0;
133         while (seconds >= 3600) {
134             seconds-=3600;
135             (*hour)++;
136         }
137     }
138
139     if (minute != NULL) {
140         *minute = 0;
141         while (seconds >= 60) {
142             seconds-=60;
143             (*minute)++;
144         }
145     }
146 }
147
148 std::string output_hour_minute(int hour, int minute, bool h_for_00)
149 {
150     ostringstream out;
151     
152     if (hour >= 0 && hour < 10)
153         out << '0';
154     out << hour;
155     
156     if (!h_for_00 || minute != 0)
157     {
158         out << ':';
159         if (minute >= 0 && minute < 10)
160             out << '0';
161         out << minute;
162     }
163     else
164         out << 'h';
165
166     return out.str();
167 }
168
169 void WEEK::set(const std::string& daystring)
170 {
171     int len=daystring.length();
172     for (int p=0; p < len; p++)
173     {
174         char nr[2];
175         nr[0]=daystring[p];
176         nr[1]=0;
177         istringstream c(nr);
178         int wnr=-1;
179         if (!(c >> wnr) || wnr<0 || wnr >6)
180             throw range_error("illegal weekday >"+string(nr)+"< in "+daystring);
181
182         days.set(wnr);
183     }
184 }
185
186 /**
187     @brief returns the number of days till the next weekday which is set
188     @param start weekday to start checking
189     @note returns 0 if the start-day is set
190 */
191 int WEEK::days_till_set(WEEKDAY start)
192 {
193     if (none_set())
194         return -1;
195
196     for (unsigned int days=0; days < 8; days++)
197     {
198         unsigned int check=start+days;
199         if (check > 6)
200             check-=7;
201         if (is_set(static_cast<WEEKDAY>(check)))
202             return days;
203     }
204
205     throw logic_error("can't find next weekday");
206
207     // fake
208     return -1;
209 }
210
211 /**
212     @brief returns the number of days since the previous weekday which is set
213     @param start weekday to start checking
214     @note returns 0 if the start-day is set
215 */
216 int WEEK::days_since_set(WEEKDAY start)
217 {
218     if (none_set())
219         return -1;
220
221     for (unsigned int days=0; days < 8; days++)
222     {
223         int check=start-days;
224         if (check < 0)
225             check+=7;
226         if (is_set(static_cast<WEEKDAY>(check)))
227             return days;
228     }
229
230     throw logic_error("can't find next weekday");
231
232     // fake
233     return -1;
234 }
235
236 std::string WEEK::get_daystring() const
237 {
238     ostringstream out;
239     for (int i = 0; i < 7; i++)
240         if (days[i])
241             out << i;
242
243     return out.str();
244 }
245
246 std::string WEEK::get_displaystring() const
247 {
248     string weekdays_str;
249
250     // From Monday to Saturday
251     int j;
252     for (int i = 1; i < 7; i++)
253     {
254         if (days[i])
255         {
256             if (!weekdays_str.empty())
257                 weekdays_str += ", ";
258
259             weekdays_str += get_day_display(static_cast<WEEKDAY>(i));
260
261             // check if we can group two or more days
262             j = i;
263             while (days[j] && j < 7)
264                 j++;
265             j--;
266
267             // Sunday end of week? j -> 7
268             if (j-i > 0 && j == 6 && days[0])
269                 j++;
270
271             if (j-i > 1) 
272             {
273                 if (j == 7)
274                     weekdays_str += "-" + get_day_display(SU);
275                 else
276                     weekdays_str += "-" + get_day_display(static_cast<WEEKDAY>(j));
277
278                 i = j;
279             }
280         }
281     }
282
283     // special: sunday
284     if (days[0] && j != 7) 
285     {
286         if (!weekdays_str.empty())
287             weekdays_str += ", ";
288
289         weekdays_str += get_day_display(SU);
290     }
291
292     return weekdays_str;
293 }
294
295 std::string WEEK::get_netfilterstring() const
296 {
297     string out;
298     for (int i = 0; i < 7; i++)
299         if (days[i])
300         {
301             if (!out.empty())
302                 out+=","; 
303             out+=get_english_display(static_cast<WEEKDAY>(i));;
304         }
305             
306     return out;
307 }
308
309 std::string WEEK::get_day_display(WEEKDAY day)
310 {
311     string weekday_str;
312
313     switch (day) {
314         case MO:
315             weekday_str = i18n("Mon");
316             break;
317         case TU:
318             weekday_str = i18n("Tue");
319             break;
320         case WE:
321             weekday_str = i18n("Wed");
322             break;
323         case TH:
324             weekday_str = i18n("Thu");
325             break;
326         case FR:
327             weekday_str = i18n("Fri");
328             break;
329         case SA:
330             weekday_str = i18n("Sat");
331             break;
332         case SU:
333             weekday_str = i18n("Sun");
334             break;
335         default:
336             break;
337     }
338
339     return weekday_str;
340 }
341
342 std::string WEEK::get_english_display(WEEKDAY day)
343 {
344     string weekday_str;
345
346     switch (day) {
347         case MO:
348             weekday_str = "Mon";
349             break;
350         case TU:
351             weekday_str = "Tue";
352             break;
353         case WE:
354             weekday_str = "Wed";
355             break;
356         case TH:
357             weekday_str = "Thu";
358             break;
359         case FR:
360             weekday_str = "Fri";
361             break;
362         case SA:
363             weekday_str = "Sat";
364             break;
365         case SU:
366             weekday_str = "Sun";
367             break;
368         default:
369             break;
370     }
371
372     return weekday_str;
373 }
374
375 string get_month_name(unsigned char month)
376 {
377     string rtn;
378     switch(month) {
379         case 1:
380             rtn = i18n("January");
381             break;
382         case 2:
383             rtn = i18n("February");
384             break;
385         case 3:
386             rtn = i18n("March");
387             break;
388         case 4:
389             rtn = i18n("April");
390             break;
391         case 5:
392             rtn = i18n("May");
393             break;
394         case 6:
395             rtn = i18n("June");
396             break;
397         case 7:
398             rtn = i18n("July");
399             break;
400         case 8:
401             rtn = i18n("August");
402             break;
403         case 9:
404             rtn = i18n("September");
405             break;
406         case 10:
407             rtn = i18n("October");
408             break;
409         case 11:
410             rtn = i18n("November");
411             break;
412         case 12:
413             rtn = i18n("December");
414             break;
415         default:
416             {
417                 ostringstream out;
418                 out << i18n("Illegal month:") << " " << month;
419                 rtn = out.str();
420             }
421     }
422
423     return rtn;
424 }
425
426
427 /*
428 ** implementaion of Interval
429 */
430
431
432 /**
433  * @brief clears the interval (make it empty).
434  */
435 void Interval::clear()
436 {
437     m_lower_bound = m_upper_bound = 0;
438 } // eo Interval::clear()
439
440
441 /**
442  * @brief tests if there is some overlapping with another interval
443  * @param other the other interval
444  * @return @a true if the two intervals have a non empty intersection.
445  */
446 bool Interval::intersects(const Interval& other) const
447 {
448     return
449         //  // other start within this:
450         (other.m_lower_bound >= m_lower_bound and other.m_lower_bound < m_upper_bound )
451         // // other end within this:
452         or (other.m_upper_bound > m_lower_bound and other.m_upper_bound <= m_upper_bound )
453         //  // other contains this
454         or (other.m_lower_bound <= m_lower_bound and other.m_upper_bound >= m_upper_bound )
455     ;
456 } // eo Interval::intersects(const Interval&)
457
458
459 /**
460  * @brief tests if the current interval (fully) contains another one.
461  * @param other the other interval.
462  * @return @a true if the current interval fully contains the other interval.
463  */
464 bool Interval::contains(const Interval& other) const
465 {
466     return  (other.m_lower_bound >= m_lower_bound)
467         and (other.m_upper_bound <= m_upper_bound)
468     ;
469 } // eo Interval::contains(const Interval& other) const
470
471
472 /*
473 ** implementation of Intervals:
474 */
475
476
477 Intervals::Intervals()
478 {
479 } // eo Intervals::Intervals
480
481
482 void Intervals::clear()
483 {
484     m_intervals.clear();
485 } // eo Intervals::clear()
486
487 /**
488  * @brief tests if one of the intervals of the list intersects with the given interval.
489  * @param other the interval to check for intersection.
490  * @return @a true if there is an intersection.
491  */
492 bool Intervals::intersects(const Interval& other) const
493 {
494     for(const_iterator it= begin();
495         it != end();
496         ++it)
497     {
498         if ( it->intersects(other) )
499         {
500             return true;
501         }
502     }
503     return false;
504 } // eo Intervals::intersects(const Interval&) const
505
506
507 /**
508  * @brief tests if we have at least one intersection with another Intervals instance.
509  * @param other the other instance.
510  * @return @a true if there is an intersection.
511  */
512 bool Intervals::intersects(const Intervals& other) const
513 {
514     for(const_iterator it= begin();
515         it != end();
516         ++it)
517     {
518         if ( other.intersects( *it ) )
519         {
520             return true;
521         }
522     }
523     return false;
524 } // eo Intervals::intersects(const Intervals&) const
525
526
527 /**
528  * @brief adds a new interval to the list.
529  * @param new_frame the new interval.
530  *
531  * Adds the interval to the list and joins overlapping intervals.
532  *
533  * @internal complexity O(n).
534  */
535 void Intervals::add(const Interval& new_frame)
536 {
537     if (not new_frame.is_valid() or new_frame.empty())
538     {
539         // well... we will not insert invalid or empty frames!
540         return;
541     }
542     for (IntervalList::iterator it= m_intervals.begin();
543          it != m_intervals.end();
544          ++it)
545     {
546         Interval& current_frame = *it;
547         if ( new_frame.m_lower_bound > current_frame.m_upper_bound )
548         {
549             // new_frame begins later than current end; go on:
550             continue;
551         }
552         // at this point: the begin of the new frame is less then the current end.
553         // now let's determine how we can insert the new frame:
554
555         if ( new_frame.m_upper_bound < current_frame.m_lower_bound )
556         {
557             // new disjoint frame; insert it before the current frame:
558             m_intervals.insert( it, new_frame );
559             // and we are done.
560             return;
561         }
562         // at this point: the end of the new frame is >= current begin. 
563         if ( new_frame.m_upper_bound <= current_frame.m_upper_bound )
564         {
565             // the end of the new frame is within our current frame; we need to combine
566             if (new_frame.m_lower_bound < current_frame.m_lower_bound)
567             {
568                 // the new interval starts earlier; we need to adjust our current frame:
569                 current_frame.m_lower_bound = new_frame.m_lower_bound;
570                 current_frame.m_changed = true;
571             }
572             // NOTE no "else" part needed since in that case our current frame already
573             // contains the new one!
574
575             // we are done:
576             return;
577         }
578         // at this point: end of new frame > end of current frame
579         // so we need to extend the current frame; at least the end.
580         // But we need to deal with intersects of following frames... *sigh*
581
582         // first the simple part: let's see if we need to move the start:
583         if ( new_frame.m_lower_bound < current_frame.m_lower_bound)
584         {
585             // yes, we need to move the start:
586             current_frame.m_lower_bound = new_frame.m_lower_bound;
587             current_frame.m_changed= true;
588         }
589
590         // now let's extend the end:
591         current_frame.m_upper_bound = new_frame.m_upper_bound;
592         current_frame.m_changed = true;
593
594         // well... let's walk through the following frames; looking for more joins...:
595         IntervalList::iterator it2 = it;
596         while(      ++(it2=it) != m_intervals.end()
597                 and current_frame.m_upper_bound >= it2->m_lower_bound
598            )
599         {
600             Interval next_frame= *it2;
601             if ( current_frame.m_upper_bound < next_frame.m_upper_bound )
602             {
603                 // in this case our end is within the next frame.
604                 // adjust our end.
605                 current_frame.m_upper_bound = next_frame.m_upper_bound;
606             }
607             // and remove the next frame since the current frame contains it (now):
608             m_intervals.erase(it2);
609         }
610         // we are done!
611         return;
612     }
613     // at this point: new frame starts later than the last frame ends
614     // append the new frame:
615     m_intervals.push_back( new_frame );
616 } // eo Intervals::add(const Interval&)
617
618
619 /**
620  * @brief subtracts a time interval from the list.
621  * @param del_frame the time interval to subtract.
622  *
623  * removes the time interval from the list; cut off parts from or remove existing
624  * intervals if they overlap.
625  *
626  * @internal complexity O(n).
627  */
628 void Intervals::sub(const Interval& del_frame)
629 {
630     if (not del_frame.is_valid() or del_frame.empty() )
631     {
632         return;
633     }
634     for (IntervalList::iterator it= m_intervals.begin();
635          it != m_intervals.end();
636          )
637     {
638         Interval& current_frame = *it;
639         if ( del_frame.m_lower_bound >= current_frame.m_upper_bound )
640         {
641             // del_frame begins later than current end; go on:
642             ++it;
643             continue;
644         }
645         // at this point: the begin of the del frame is less then the current end.
646         if ( del_frame.m_upper_bound < current_frame.m_lower_bound )
647         {
648             // end is before our start; nothing to do.
649             return;
650         }
651         // at this point: the end of the del frame is >= current begin.
652         if ( del_frame.m_upper_bound < current_frame.m_upper_bound )
653         {
654             // del frame end point is within our interval.
655             if ( del_frame.m_lower_bound > current_frame.m_lower_bound)
656             {
657                 // the del frame is within our interval... we need to split:
658                 m_intervals.insert(it, Interval( current_frame.m_lower_bound, del_frame.m_lower_bound ) );
659             }
660             // adjust start of current frame:
661             if (current_frame.m_lower_bound < del_frame.m_upper_bound)
662             {
663                 current_frame.m_lower_bound= del_frame.m_upper_bound;
664                 current_frame.m_changed= true;
665             }
666             // and we are done!
667             return;
668         }
669         // at this point the end of the del frame is >= current end
670         if ( del_frame.m_lower_bound > current_frame.m_lower_bound )
671         {
672             // a part of the current interval needs to be preserved..
673             // move the end.
674             current_frame.m_upper_bound= del_frame.m_lower_bound;
675             current_frame.m_changed= true;
676             // and continue with the next interval:
677             ++it;
678             continue;
679         }
680         // at this point; the whole frame needs to be deleted..
681         if ( it == m_intervals.begin())
682         {
683             m_intervals.erase(it);
684             it= m_intervals.begin();
685         }
686         else
687         {
688             IntervalList::iterator it2= it++;
689             m_intervals.erase(it2);
690         }
691     }
692 } // eo Intervals::sub(const Interval&)
693
694
695 /**
696  * @brief returns if we contain an interval.
697  * @param other the interval to check.
698  * @return @a true if we cover the given interval, too.
699  */
700 bool Intervals::contains(const Interval& other) const
701 {
702     for(const_iterator it= begin();
703         it != end();
704         ++it)
705     {
706         if ( it->contains( other ))
707         {
708             return true;
709         }
710     }
711     return false;
712 } // eo Intervals::contains(const Interval&) const
713
714
715 /**
716  * @brief returns if we contain an exact interval.
717  * @param other the interval to check.
718  * @return @a true if we axactly contains the given interval.
719  *
720  * @note thsi differs from contain in the way, that we return only @a true
721  * iff we have the given interval in our list; not only cover it.
722  */
723 bool Intervals::contains_exact(const Interval& other) const
724 {
725     for(const_iterator it= begin();
726         it != end();
727         ++it)
728     {
729         if ( *it == other)
730         {
731             return true;
732         }
733     }
734     return false;
735 } // eo Intervals::contains_exact(const Interval&)const
736
737
738 /**
739  * @brief returns if we contain another interval combination.
740  * @param other the intervals to check.
741  * @return @a true if we cover the given intervals, too.
742  *
743  * @internal we rely on the fact that the lists are sorted and contain
744  * disjoint intervals.
745  *
746  * So this method has a complexity of O(n).
747  */
748 bool Intervals::contains(const Intervals& other) const
749 {
750     const_iterator my_it= begin();
751     const_iterator other_it= other.begin();
752     while( my_it != end() and other_it!= other.end() )
753     {
754         // seek the first interval which contains the lower bound of the current other interval
755         while (my_it != end()
756                and my_it->m_lower_bound > other_it->m_lower_bound
757                and other_it->m_lower_bound >= my_it->m_upper_bound
758               )
759         {
760             ++my_it;
761         }
762         if (my_it == end())
763         {
764             break;
765         }
766         if (not my_it->contains( *other_it ))
767         {
768             // if we don't contain the current other; we're done:
769             return false;
770         }
771         //else check the next other interval:
772         ++other_it;
773     }
774     return (other_it == other.end());
775 } // eo Intervals::contains(const Intervals&) const
776
777
778 /**
779  * @brief combines to interval combinates for equality
780  * @param other the other instance.
781  * @return @a true if the other is equal to the current.
782  *
783  * @internal since the lists are sorted, we compare the interval lists.
784  * Thus we have a complexity of O(n).
785  */
786 bool Intervals::operator==(const Intervals& other) const
787 {
788     // since we keep sorted lists: just compare the lists :-)
789     return m_intervals == other.m_intervals;
790 } // eo Intervals::operator==(const Intervals&)
791
792
793 Intervals& Intervals::operator+=(const Interval& other)
794 {
795     add(other);
796     return *this;
797 } // eo operator+=(const Interval&)
798
799
800 Intervals& Intervals::operator-=(const Interval& other)
801 {
802     sub(other);
803     return *this;
804 } // eo operator-=(const Interval&)
805
806
807 /**
808  * @brief adds the intervals of a second instance to us.
809  * @param other the other instance.
810  * @return self reference (allow chaining).
811  *
812  * @internal since we do simple loops over the other and our intervals
813  * we have a complexity of O(n^2).
814  *
815  * @todo optimize if complexity becomes a problem.
816  */
817 Intervals& Intervals::operator+=(const Intervals& other)
818 {
819     for(const_iterator it= other.begin();
820         it != other.end();
821         ++it)
822     {
823         add( *it );
824     }
825     return *this;
826 } // eo operator+=(const Intervals&)
827
828
829 /**
830  * @brief subtracts the intervals of a second instance from us.
831  * @param other the other instance.
832  * @return self reference (allow chaining).
833  *
834  * @internal since we do simple loops over the other and our intervals
835  * we have a complexity of O(n^2).
836  *
837  * @todo optimize if complexity becomes a problem.
838  */
839 Intervals& Intervals::operator-=(const Intervals& other)
840 {
841     if (&other == this)
842     {
843         m_intervals.clear();
844     }
845     else
846     {
847         for(const_iterator it= other.begin();
848             it != other.end();
849             ++it)
850         {
851             sub( *it );
852         }
853     }
854     return *this;
855 } // eo operator-=(const Intervals&)
856
857
858
859 /*
860 ** clock funcs:
861 */
862
863
864 /**
865  * @brief fetches the value from the monotonic clock source.
866  * @param[out] seconds the seconds.
867  * @param[out] nano_seconds the nano seconds.
868  * @return @a true if the clock was successfully read.
869  */
870 bool monotonic_clock_gettime(long int& seconds, long int& nano_seconds)
871 {
872     struct timespec tp[1];
873     int res= ::syscall(__NR_clock_gettime, CLOCK_MONOTONIC, tp);
874     if (0 == res)
875     {
876         seconds= tp->tv_sec;
877         nano_seconds= tp->tv_nsec;
878     }
879     return (res==0);
880 } // eo monotonic_clock_gettime(long int&,long int&)
881
882
883 /**
884  * @brief fetches the value from the monotonic clock source.
885  * @return the time since system start in nanoseconds, 0 if read was unsuccessful
886  */
887 long long monotonic_clock_gettime_nano()
888 {
889     long int seconds;
890     long int nano_seconds;
891     long long nano=0;
892
893     if (monotonic_clock_gettime(seconds,nano_seconds))
894     {
895         nano=seconds;
896         nano*=1000000000LL;
897         nano+=nano_seconds;
898     }
899
900     return nano;
901 }
902
903 /**
904  * @brief fetches the value from the monotonic clock source.
905  * @param[out] seconds the seconds.
906  * @param[out] nano_seconds the nano seconds.
907  * @return @a true if the clock was successfully read.
908  */
909 bool realtime_clock_gettime(long int& seconds, long int& nano_seconds)
910 {
911     struct timespec tp[1];
912     int res= ::syscall(__NR_clock_gettime, CLOCK_REALTIME, tp);
913     if (0 == res)
914     {
915         seconds= tp->tv_sec;
916         nano_seconds= tp->tv_nsec;
917     }
918     return (res==0);
919 } // eo realtime_clock_gettime(long int&,long int&)
920
921