0b2e8fcd2e13daf979283bcab0581b184cc17580
[libi2ncommon] / src / cron.cpp
1
2 /** @file
3  * @brief repeating time-points and intervals 
4  *
5  * @copyright Copyright © 2009 by Intra2net AG
6  * @license commercial
7  * @contact info@intra2net.com
8  *
9  */
10 #include <time.h>
11 #include <limits.h>
12 #include <stdexcept>
13 #include <iostream>
14
15 #include <cron.hpp>
16
17 namespace I2n {
18 namespace Time {
19
20 const time_t WeekCron::StNimmerleinsDay = static_cast<time_t>(INT_MAX);
21
22 /**
23     * Constructor, leaves object in invalid state
24     */
25 WeekCron::WeekCron()
26     : Begin(-1)
27     , End(0)
28     , Every(-1)
29     , Week()
30 {
31 }
32
33 /**
34     * Constructor
35     * @param daystring String representing the active weekdays as numbers. 0 is Sunday.
36     * @param begin Start point of time in seconds since the start of the day
37     */
38 WeekCron::WeekCron(const std::string& daystring, const int begin)
39     : Begin(begin)
40     , End(0)
41     , Every(-1)
42     , Week(daystring)
43 {
44 }
45
46 /**
47     * Constructor
48     * @param daystring String representing the active weekdays as numbers. 0 is Sunday.
49     * @param begin Start point of time in seconds since the start of the day
50     * @param end End point of time in seconds since the start of the day. Only used when every != -1
51     * @param every Repeat event every xxx seconds in the half-open interval of begin and end. -1 is disabled
52     */
53 WeekCron::WeekCron(const std::string& daystring, const int begin, const int end, const int every)
54     : Begin(begin)
55     , End(end)
56     , Every(every)
57     , Week(daystring)
58 {
59 }
60
61 /**
62     * Constructor
63     * @param week I2n::Time::Week object representing the active days
64     * @param begin Start point of time in seconds since the start of the day
65     */
66 WeekCron::WeekCron(const I2n::Time::Week& week, const int begin)
67     : Begin(begin)
68     , End(0)
69     , Every(-1)
70     , Week(week)
71 {
72 }
73
74 /**
75     * Constructor
76     * @param week I2n::Time::Week object representing the active days
77     * @param begin Start point of time in seconds since the start of the day
78     * @param end End point of time in seconds since the start of the day. Only used when every != -1
79     * @param every Repeat event every xxx seconds in the half-open interval of begin and end. -1 is disabled
80     */
81 WeekCron::WeekCron(const I2n::Time::Week& week, const int begin, const int end, const int every)
82     : Begin(begin)
83     , End(end)
84     , Every(every)
85     , Week(week)
86 {
87 }
88
89 /**
90  * Checks if the input values are sane
91  * @return True if sane, false otherweise
92  */
93 bool WeekCron::is_sane() const
94 {
95     if (Begin < 0 || Begin > 86399)
96         return false;
97
98     if (Every != -1)
99     {
100         if (End < 0 || End > 86400 ||
101             Every < 1 || Every > 86400 ||
102             Begin > End)
103             return false;
104     }
105
106     return true;
107 }
108
109 /**
110  * Returns the next point in time the item is scheduled for.
111  * Handles the full possibilities of WeekCron.
112  * @note if it is scheduled for calc_from we return the next schedule, not now!
113  * @param calc_from unix-time to start calculating from (0 means now)
114  * @return Next point in time the item is scheduled for
115  *         returns constant #StNimmerleinsDay if it is scheduled never
116  */
117 time_t WeekCron::get_next_run(time_t calc_from) const
118 {
119     if (!calc_from)
120         calc_from=time(NULL);
121
122     if (!is_sane())
123         throw std::runtime_error("illegal cron value");
124
125     if (calc_from <= 86400*14)
126         throw std::runtime_error("WeekCron doesn't work for timestamps near 0");
127
128     if (Week.none_set())
129         return StNimmerleinsDay;
130
131     if (Every == -1)
132     {
133         // point in time
134         return get_next_point(calc_from,Begin,true);
135     }
136     else
137     {
138         // interval
139         time_t next_begin = get_next_point(calc_from,Begin,true);
140         if (next_begin > get_next_point(calc_from,End-1,true))
141         {
142             // next begin > next end means we are at the begin or within the interval
143
144             time_t interval_begin=get_previousnow_point(calc_from,Begin,true);
145
146             time_t within_interval=calc_from - interval_begin;
147             time_t since_lastrun=within_interval % Every;
148             time_t next_exec=calc_from+(Every-since_lastrun);
149
150             // next step at or after end?
151             if (next_exec > get_next_point(calc_from,End-1,true))
152                 return get_next_point(calc_from,Begin,true);
153             else
154                 return next_exec;
155         }
156         else
157         {
158             // next begin < next end means we are out of the interval: next begin is next run
159             return next_begin;
160         }
161     }
162 }
163
164 /**
165  * Returns the next point in time the item is scheduled for. Does not care about intervals.
166  * Returns next point if scheduled for #calc_from.
167  * @param calc_from Point of time to start calculations from
168  * @param daysec Start point of time in seconds since the start of the day
169  * @param todaycheck If true we check if the calculated time point is before
170                                   our #calc_from calcucation start point.
171                                   If yes, we will advance to the next day.
172  * @return Next point in time
173  */
174 time_t WeekCron::get_next_point(const time_t calc_from, const int daysec, const bool todaycheck) const
175 {
176     struct tm ft;
177     if (localtime_r(&calc_from,&ft) == NULL)
178         return calc_from;
179
180     // take care of the weekday
181     ft.tm_mday+=Week.days_till_set(static_cast<Week::WeekDay>(ft.tm_wday));                     //lint !e737 !e713
182
183     fill_tm_with_wallclock(&ft,daysec);
184
185     // tm_isdst means to use the dst in use at the given time
186     ft.tm_isdst=-1;
187
188     time_t target=mktime(&ft);
189
190     // check for completely illegal time, should not happen without bug in this func,
191     // return calc_from as safeguard
192     if (target == (time_t)-1)
193         return calc_from;
194
195     // todays schedule could already been through or now
196     if (todaycheck && target <= calc_from)
197     {
198         // not today but the next matching weekday
199         ft.tm_mday++;
200         ft.tm_isdst=-1;
201         target=get_next_point(mktime(&ft),daysec,false);
202     }
203
204     return target;
205 }
206
207 /**
208  * Returns the previous or current point in time the item was scheduled for.
209  * Does not care about intervals.
210  * Returns #calc_from if scheduled for #calc_from.
211  * @param calc_from Point of time to start calculations from
212  * @param daysec Start point of time in seconds since the start of the day
213  * @param todaycheck If true we check if the calculated time point is after
214                                   our #calc_from calcucation start point.
215                                   If yes, we will go back to the previos day
216  * @return Previous point in time
217  */
218 time_t WeekCron::get_previousnow_point(const time_t calc_from, const int daysec, const bool todaycheck) const
219 {
220     struct tm ft;
221     if (localtime_r(&calc_from,&ft) == NULL)
222         return calc_from;
223
224     // take care of the weekday
225     ft.tm_mday-=Week.days_since_set(static_cast<Week::WeekDay>(ft.tm_wday));                            //lint !e737 !e713
226
227     fill_tm_with_wallclock(&ft,daysec);
228
229     // tm_isdst means to use the dst in use at the given time
230     ft.tm_isdst=-1;
231
232     time_t target=mktime(&ft);
233
234     // check for completely illegal time, should not happen without bug in this func,
235     // return calc_from as safeguard
236     if (target == (time_t)-1)
237         return calc_from;
238
239     // target later than we are looking for
240     // target==calc_from is ok (that's why it is called lastnow...)
241     if (todaycheck && target > calc_from)
242     {
243         // not today but the previous matching weekday
244         ft.tm_mday--;
245         ft.tm_isdst=-1;
246         target=get_previousnow_point(mktime(&ft),daysec,false);
247     }
248
249     return target;
250 }
251
252 /**
253  * Converts #daysec into hour/minute/second and fills it into the provided struct tm
254  * @param ft struct tm to fill wallclock time into
255  * @param daysec Start point of time in seconds since the start of the day
256  */
257 void WeekCron::fill_tm_with_wallclock(struct tm *ft, const int daysec) const
258 {
259     int remain=daysec;
260
261     ft->tm_hour=remain/3600;
262     remain-=ft->tm_hour*3600;
263
264     ft->tm_min=remain/60;
265     remain-=ft->tm_min*60;
266
267     ft->tm_sec=remain;
268 }
269
270 }
271 }