1 /*
2 * Copyright (c) 2009-2010 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 #include <stdlib.h>
25 #include <string.h>
26 #include <Block.h>
27 #include "timer.h"
28
29 #define MINUTE 60
30 #define HOUR 3600
31 #define DAY 86400
32
33 static const uint8_t mlen[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
34
35 /*
36 * Timed events
37 *
38 * Supported event types:
39 *
40 * Oneshot
41 * Every n seconds/minutes/hours/days/weeks
42 * Specific day of the month, every n months
43 * Specific weekday following specific day of the month, every n months
44 *
45 */
46 static time_t
timer_next(timer_event_t * t,time_t now)47 timer_next(timer_event_t *t, time_t now)
48 {
49 uint32_t y, m;
50 int32_t d, x, a, b, dd, wd;
51 struct tm tmp;
52 time_t next, tt, tod;
53
54 if (t == NULL) return 0;
55
56 switch (t->type)
57 {
58 case TIME_EVENT_ONESHOT:
59 {
60 /*
61 * oneshot time event
62 */
63 if (t->start < now) return 0;
64 return t->start;
65 }
66 case TIME_EVENT_CLOCK:
67 {
68 /*
69 * event recurs every t->freq seconds
70 */
71
72 /* t->end is the cut-off. If it's in the past, return 0 */
73 if ((t->end != 0) && (t->end < now)) return 0;
74
75 /* If the start time is in the future, that's the next occurrence */
76 if (t->start >= now) return t->start;
77
78 /* shouldn't happen, as TIME_EVENT_CLOCK should always recur */
79 if (t->freq == 0) return 0;
80
81 x = ((t->freq - 1) + now - t->start) / t->freq;
82 next = t->start + (x * t->freq);
83 return next;
84 }
85 case TIME_EVENT_CAL:
86 {
87 /*
88 * event recurs every t->freq months
89 * t->base gives us the starting month and year, and the time of day
90 * t->day specifies the day of the month (negative means relative to last day of the month)
91 * t->day is > 100 or < 100, then it means a weekday
92 * 101 = first monday, 102 = first tuesday, ..., 108 = second monday, and so on
93 * -101 = last monday, -102 = last tuesday, ..., -108 = second last monday, and so on
94 */
95
96 /* t->end is the cut-off. If it's in the past, return 0 */
97 if ((t->end != 0) && (t->end < now)) return 0;
98
99 /* If the start time is in the future, that's the next occurrence */
100 if (t->start >= now) return t->start;
101
102 next = t->start;
103
104 /* If t->next is set from the last time we ran, and it is in the past, we can start there. */
105 if ((t->next > 0) && (t->next < now)) next = t->next;
106
107 while (next < now)
108 {
109 /* determine year, month, and time-of-day (clock time) of the next occurance */
110 memset(&tmp, 0, sizeof(struct tm));
111 localtime_r((const time_t *)&(next), &tmp);
112 y = tmp.tm_year;
113 m = tmp.tm_mon;
114 tod = tmp.tm_sec + (MINUTE * tmp.tm_min) + (HOUR * tmp.tm_hour);
115
116 m += t->freq;
117 if (m > 11)
118 {
119 y += (m / 12);
120 m %= 12;
121 }
122
123 /* we now have a year (y), a month (m), and a time of day (tod) */
124
125 if (t->day > 0)
126 {
127 if (t->day < 100)
128 {
129 /* easy case: day is the date of the month */
130
131 memset(&tmp, 0, sizeof(struct tm));
132 tmp.tm_year = y;
133 tmp.tm_mon = m;
134 tmp.tm_mday = t->day;
135 tmp.tm_isdst = -1;
136 next = mktime(&tmp) + tod;
137 continue;
138 }
139 else
140 {
141 /* t->day is a weekday */
142
143 wd = t->day - 100;
144
145 /* start by finding out the weekday of the first of the month */
146 memset(&tmp, 0, sizeof(struct tm));
147 tmp.tm_year = y;
148 tmp.tm_mon = m;
149 tmp.tm_mday = 1;
150 tmp.tm_isdst = -1;
151 tt = mktime(&tmp);
152 localtime_r((const time_t *)&tt, &tmp);
153
154 if (tmp.tm_wday == 0) tmp.tm_wday = 7;
155
156 x = 0;
157 if (tmp.tm_wday > (wd % 7)) x = (wd + 7) - tmp.tm_wday;
158 else x = wd - tmp.tm_wday;
159
160 tmp.tm_mday += x;
161 tmp.tm_isdst = -1;
162 next = mktime(&tmp) + tod;
163 continue;
164 }
165 }
166
167 if (t->day > -100)
168 {
169 /* nth day from the end of the month */
170 if (m == 1)
171 {
172 /* determine weekday of last day of February (== March 0) */
173 memset(&tmp, 0, sizeof(struct tm));
174 tmp.tm_year = y;
175 tmp.tm_mon = 2;
176 tmp.tm_mday = 0;
177 tmp.tm_isdst = -1;
178 tt = mktime(&tmp);
179 memset(&tmp, 0, sizeof(struct tm));
180 localtime_r((const time_t *)&(tt), &tmp);
181 d = tmp.tm_mday + t->day;
182 }
183 else
184 {
185 d = mlen[m] + t->day;
186 }
187
188 memset(&tmp, 0, sizeof(struct tm));
189 tmp.tm_year = y;
190 tmp.tm_mon = m;
191 tmp.tm_mday = d;
192 tmp.tm_isdst = -1;
193 next = mktime(&tmp) + tod;
194 continue;
195 }
196
197 /* t->day is a weekday relative to the end of the month */
198 if (m == 1)
199 {
200 /* determine weekday of last day of February (== March 0) */
201 memset(&tmp, 0, sizeof(struct tm));
202 tmp.tm_year = y;
203 tmp.tm_mon = 2;
204 tmp.tm_mday = 0;
205 tmp.tm_isdst = -1;
206 tt = mktime(&tmp);
207 memset(&tmp, 0, sizeof(struct tm));
208 localtime_r((const time_t *)&(tt), &tmp);
209 d = tmp.tm_mday;
210 }
211 else
212 {
213 d = mlen[m];
214 }
215
216 memset(&tmp, 0, sizeof(struct tm));
217 tmp.tm_year = y;
218 tmp.tm_mon = m;
219 tmp.tm_mday = d;
220 tmp.tm_isdst = -1;
221
222 dd = -1 * (t->day + 100);
223 a = dd % 7;
224 b = (dd + 6) / 7;
225 if (a <= tmp.tm_wday) b--;
226 tmp.tm_mday = ((a - tmp.tm_wday) + d) - (b * 7);
227 next = mktime(&tmp) + tod;
228 }
229
230 t->next = next;
231 return next;
232 }
233 default:
234 {
235 return 0;
236 }
237 }
238
239 return 0;
240 }
241
242 /*
243 * This does the actual free.
244 * It is dispatched on the timer's dispatch source queue to make it safe.
245 */
246 static void
timer_free(timer_event_t * t)247 timer_free(timer_event_t *t)
248 {
249 if (t == NULL) return;
250 if (t->deactivation_handler != NULL) Block_release(t->deactivation_handler);
251 if (t->contextp != NULL) free(t->contextp);
252
253 dispatch_release(t->t_src);
254 dispatch_release(t->t_queue);
255
256 memset(t, 0, sizeof(timer_event_t));
257 free(t);
258 }
259
260 void
timer_close(timer_event_t * t)261 timer_close(timer_event_t *t)
262 {
263 if (t == NULL) return;
264
265 if (t->t_src != NULL) dispatch_source_cancel(t->t_src);
266
267 /*
268 * We need to make sure that the source's event handler isn't currently running
269 * before we free the timer. We let the source's queue do the actual free.
270 */
271 dispatch_async(t->t_queue, ^{ timer_free(t); });
272 }
273
274 timer_event_t *
timer_oneshot(time_t when,dispatch_queue_t queue)275 timer_oneshot(time_t when, dispatch_queue_t queue)
276 {
277 timer_event_t *t;
278 time_t now;
279 dispatch_time_t trigger;
280
281 /* refuse a trigger time in the past */
282 now = time(0);
283 if (when <= now) return NULL;
284
285 t = calloc(1, sizeof(timer_event_t));
286 if (t == NULL) return NULL;
287
288 dispatch_retain(queue);
289
290 t->type = TIME_EVENT_ONESHOT;
291 t->start = when;
292 t->t_queue = queue;
293
294 t->t_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
295 t->src = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, queue);
296
297 trigger = dispatch_walltime(NULL, (t->start - now) * NSEC_PER_SEC);
298 dispatch_source_set_timer(t->t_src, trigger, NSEC_PER_SEC, 0);
299
300 dispatch_source_set_event_handler(t->t_src, ^{
301 dispatch_source_merge_data(t->src, 1);
302 dispatch_source_cancel(t->t_src);
303 if (t->deactivation_handler != NULL)
304 {
305 dispatch_async(t->t_queue, ^{ t->deactivation_handler(); });
306 }
307 });
308
309 dispatch_resume(t->t_src);
310 return t;
311 }
312
313 void
314 timer_set_deactivation_handler(timer_event_t *t, void(^handler)())
315 {
316 if (t == NULL) return;
317
318 if (t->deactivation_handler != NULL) Block_release(t->deactivation_handler);
319 t->deactivation_handler = Block_copy(handler);
320 }
321
322 timer_event_t *
timer_clock(time_t first,time_t freq_sec,time_t end,dispatch_queue_t queue)323 timer_clock(time_t first, time_t freq_sec, time_t end, dispatch_queue_t queue)
324 {
325 timer_event_t *t;
326 time_t now;
327 dispatch_time_t trigger;
328 int64_t x;
329
330 if (freq_sec == 0) return timer_oneshot(first, queue);
331
332 now = time(0);
333
334 t = calloc(1, sizeof(timer_event_t));
335 if (t == NULL) return NULL;
336
337 t->type = TIME_EVENT_CLOCK;
338
339 if (first < now)
340 {
341 x = ((freq_sec - 1) + now - first) / freq_sec;
342 t->start = first + (x * freq_sec);
343 }
344 else
345 {
346 t->start = first;
347 }
348
349 t->end = end;
350 t->freq = freq_sec;
351 t->t_queue = queue;
352
353 t->t_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
354 t->src = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, queue);
355
356 trigger = dispatch_walltime(NULL, (t->start - now) * NSEC_PER_SEC);
357 dispatch_source_set_timer(t->t_src, trigger, freq_sec * NSEC_PER_SEC, 0);
358
359 dispatch_source_set_event_handler(t->t_src, ^{
360 unsigned long n = dispatch_source_get_data(t->t_src);
361 dispatch_source_merge_data(t->src, n);
362
363 /* deactivate if this is the last time we want to trigger the client source */
364 if ((t->end > 0) && (t->end < (time(0) + freq_sec)))
365 {
366 dispatch_source_cancel(t->t_src);
367 if (t->deactivation_handler != NULL)
368 {
369 dispatch_async(t->t_queue, ^{ t->deactivation_handler(); });
370 }
371 }
372 });
373
374 dispatch_resume(t->t_src);
375
376 return t;
377 }
378
379 timer_event_t *
timer_calendar(time_t first,time_t freq_mth,time_t end,int day,dispatch_queue_t queue)380 timer_calendar(time_t first, time_t freq_mth, time_t end, int day, dispatch_queue_t queue)
381 {
382 timer_event_t *t;
383 time_t next, now;
384 dispatch_time_t trigger;
385
386 if (freq_mth == 0) return timer_oneshot(first, queue);
387
388 now = time(0);
389
390 t = calloc(1, sizeof(timer_event_t));
391 if (t == NULL) return NULL;
392
393 t->type = TIME_EVENT_CAL;
394 t->start = first;
395 t->day = day;
396 t->end = end;
397 t->freq = freq_mth;
398 t->t_queue = queue;
399
400 t->t_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
401 t->src = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, queue);
402
403 next = timer_next(t, now);
404 trigger = dispatch_walltime(NULL, (next - now) * NSEC_PER_SEC);
405 dispatch_source_set_timer(t->t_src, trigger, NSEC_PER_SEC, 0);
406
407 dispatch_source_set_event_handler(t->t_src, ^{
408 unsigned long n = dispatch_source_get_data(t->t_src);
409 dispatch_source_merge_data(t->src, n);
410
411 time_t _now = time(0);
412 time_t x = timer_next(t, now);
413
414 /* deactivate when there is no next time */
415 if (x == 0)
416 {
417 dispatch_source_cancel(t->t_src);
418 if (t->deactivation_handler != NULL)
419 {
420 dispatch_async(t->t_queue, ^{ t->deactivation_handler(); });
421 }
422 }
423 else
424 {
425 dispatch_source_set_timer(t->t_src, dispatch_walltime(NULL, (x - _now) * NSEC_PER_SEC), NSEC_PER_SEC, 0);
426 }
427 });
428
429 dispatch_resume(t->t_src);
430
431 return t;
432 }
433
434 timer_event_t *
timer_calendar_long(uint32_t start_year,uint32_t start_month,uint32_t start_day,uint32_t start_hour,uint32_t start_min,uint32_t start_sec,time_t freq,int day,uint32_t end_year,uint32_t end_month,uint32_t end_day,uint32_t end_hour,uint32_t end_min,uint32_t end_sec,dispatch_queue_t queue)435 timer_calendar_long(uint32_t start_year, uint32_t start_month, uint32_t start_day, uint32_t start_hour, uint32_t start_min, uint32_t start_sec, time_t freq, int day, uint32_t end_year, uint32_t end_month, uint32_t end_day, uint32_t end_hour, uint32_t end_min, uint32_t end_sec, dispatch_queue_t queue)
436 {
437 struct tm tmp;
438 time_t first, last;
439
440 memset(&tmp, 0, sizeof(struct tm));
441 tmp.tm_year = start_year - 1900;
442 tmp.tm_mon = start_month;
443 tmp.tm_mday = start_day;
444 tmp.tm_isdst = -1;
445 tmp.tm_hour = start_hour;
446 tmp.tm_min = start_min;
447 tmp.tm_sec = start_sec;
448
449 first = mktime(&tmp);
450
451 if (freq == 0) return timer_oneshot(first, queue);
452
453 memset(&tmp, 0, sizeof(struct tm));
454 tmp.tm_year = end_year;
455 tmp.tm_mon = end_month;
456 tmp.tm_mday = end_day;
457 tmp.tm_isdst = -1;
458 tmp.tm_hour = end_hour;
459 tmp.tm_min = end_min;
460 tmp.tm_sec = end_sec;
461
462 last = mktime(&tmp);
463
464 return timer_calendar(first, freq, day, last, queue);
465 }
466