#include <stdlib.h>
#include <string.h>
#include <Block.h>
#include "timer.h"
#define MINUTE 60
#define HOUR 3600
#define DAY 86400
static const uint8_t mlen[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
static time_t
timer_next(timer_t *t, time_t now)
{
uint32_t y, m;
int32_t d, x, a, b, dd, wd;
struct tm tmp;
time_t next, tt, tod;
if (t == NULL) return 0;
switch (t->type)
{
case TIME_EVENT_ONESHOT:
{
if (t->start < now) return 0;
return t->start;
}
case TIME_EVENT_CLOCK:
{
if ((t->end != 0) && (t->end < now)) return 0;
if (t->start >= now) return t->start;
if (t->freq == 0) return 0;
x = (int32_t)(((t->freq - 1) + now - t->start) / t->freq);
next = t->start + (x * t->freq);
return next;
}
case TIME_EVENT_CAL:
{
if ((t->end != 0) && (t->end < now)) return 0;
if (t->start >= now) return t->start;
next = t->start;
if ((t->next > 0) && (t->next < now)) next = t->next;
while (next < now)
{
memset(&tmp, 0, sizeof(struct tm));
localtime_r((const time_t *)&(next), &tmp);
y = tmp.tm_year;
m = tmp.tm_mon;
tod = tmp.tm_sec + (MINUTE * tmp.tm_min) + (HOUR * tmp.tm_hour);
m += t->freq;
if (m > 11)
{
y += (m / 12);
m %= 12;
}
if (t->day > 0)
{
if (t->day < 100)
{
memset(&tmp, 0, sizeof(struct tm));
tmp.tm_year = y;
tmp.tm_mon = m;
tmp.tm_mday = t->day;
tmp.tm_isdst = -1;
next = mktime(&tmp) + tod;
continue;
}
else
{
wd = t->day - 100;
memset(&tmp, 0, sizeof(struct tm));
tmp.tm_year = y;
tmp.tm_mon = m;
tmp.tm_mday = 1;
tmp.tm_isdst = -1;
tt = mktime(&tmp);
localtime_r((const time_t *)&tt, &tmp);
if (tmp.tm_wday == 0) tmp.tm_wday = 7;
x = 0;
if (tmp.tm_wday > (wd % 7)) x = (wd + 7) - tmp.tm_wday;
else x = wd - tmp.tm_wday;
tmp.tm_mday += x;
tmp.tm_isdst = -1;
next = mktime(&tmp) + tod;
continue;
}
}
if (t->day > -100)
{
if (m == 1)
{
memset(&tmp, 0, sizeof(struct tm));
tmp.tm_year = y;
tmp.tm_mon = 2;
tmp.tm_mday = 0;
tmp.tm_isdst = -1;
tt = mktime(&tmp);
memset(&tmp, 0, sizeof(struct tm));
localtime_r((const time_t *)&(tt), &tmp);
d = tmp.tm_mday + t->day;
}
else
{
d = mlen[m] + t->day;
}
memset(&tmp, 0, sizeof(struct tm));
tmp.tm_year = y;
tmp.tm_mon = m;
tmp.tm_mday = d;
tmp.tm_isdst = -1;
next = mktime(&tmp) + tod;
continue;
}
if (m == 1)
{
memset(&tmp, 0, sizeof(struct tm));
tmp.tm_year = y;
tmp.tm_mon = 2;
tmp.tm_mday = 0;
tmp.tm_isdst = -1;
tt = mktime(&tmp);
memset(&tmp, 0, sizeof(struct tm));
localtime_r((const time_t *)&(tt), &tmp);
d = tmp.tm_mday;
}
else
{
d = mlen[m];
}
memset(&tmp, 0, sizeof(struct tm));
tmp.tm_year = y;
tmp.tm_mon = m;
tmp.tm_mday = d;
tmp.tm_isdst = -1;
dd = -1 * (t->day + 100);
a = dd % 7;
b = (dd + 6) / 7;
if (a <= tmp.tm_wday) b--;
tmp.tm_mday = ((a - tmp.tm_wday) + d) - (b * 7);
next = mktime(&tmp) + tod;
}
t->next = next;
return next;
}
default:
{
return 0;
}
}
}
static void
timer_free(timer_t *t)
{
if (t == NULL) return;
if (t->contextp != NULL) free(t->contextp);
dispatch_release(t->t_src);
dispatch_release(t->t_queue);
memset(t, 0, sizeof(timer_t));
free(t);
}
void
timer_close(timer_t *t)
{
if (t == NULL) return;
if (t->t_src != NULL) dispatch_source_cancel(t->t_src);
dispatch_async(t->t_queue, ^{ timer_free(t); });
}
timer_t *
timer_oneshot(time_t when, dispatch_queue_t queue)
{
timer_t *t;
time_t now;
dispatch_time_t trigger;
now = time(0);
if (when <= now) return NULL;
t = calloc(1, sizeof(timer_t));
if (t == NULL) return NULL;
dispatch_retain(queue);
t->type = TIME_EVENT_ONESHOT;
t->start = when;
t->t_queue = queue;
t->t_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
t->src = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, queue);
trigger = dispatch_walltime(NULL, (t->start - now) * NSEC_PER_SEC);
dispatch_source_set_timer(t->t_src, trigger, NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(t->t_src, ^{
dispatch_source_merge_data(t->src, 1);
dispatch_source_cancel(t->t_src);
});
dispatch_activate(t->t_src);
return t;
}
timer_t *
timer_clock(time_t first, time_t freq_sec, time_t end, dispatch_queue_t queue)
{
timer_t *t;
time_t now;
dispatch_time_t trigger;
int64_t x;
if (freq_sec == 0) return timer_oneshot(first, queue);
now = time(0);
t = calloc(1, sizeof(timer_t));
if (t == NULL) return NULL;
t->type = TIME_EVENT_CLOCK;
if (first < now)
{
x = ((freq_sec - 1) + now - first) / freq_sec;
t->start = first + (x * freq_sec);
}
else
{
t->start = first;
}
t->end = end;
t->freq = (uint32_t)freq_sec;
t->t_queue = queue;
t->t_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
t->src = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, queue);
trigger = dispatch_walltime(NULL, (t->start - now) * NSEC_PER_SEC);
dispatch_source_set_timer(t->t_src, trigger, freq_sec * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(t->t_src, ^{
unsigned long n = dispatch_source_get_data(t->t_src);
dispatch_source_merge_data(t->src, n);
if ((t->end > 0) && (t->end < (time(0) + freq_sec)))
{
dispatch_source_cancel(t->t_src);
}
});
dispatch_activate(t->t_src);
return t;
}
timer_t *
timer_calendar(time_t first, time_t freq_mth, time_t end, int day, dispatch_queue_t queue)
{
timer_t *t;
time_t next, now;
dispatch_time_t trigger;
if (freq_mth == 0) return timer_oneshot(first, queue);
now = time(0);
t = calloc(1, sizeof(timer_t));
if (t == NULL) return NULL;
t->type = TIME_EVENT_CAL;
t->start = first;
t->day = day;
t->end = end;
t->freq = (uint32_t)freq_mth;
t->t_queue = queue;
t->t_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
t->src = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, queue);
next = timer_next(t, now);
trigger = dispatch_walltime(NULL, (next - now) * NSEC_PER_SEC);
dispatch_source_set_timer(t->t_src, trigger, NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(t->t_src, ^{
unsigned long n = dispatch_source_get_data(t->t_src);
dispatch_source_merge_data(t->src, n);
time_t now = time(0);
time_t x = timer_next(t, now);
if (x == 0)
{
dispatch_source_cancel(t->t_src);
}
else
{
dispatch_source_set_timer(t->t_src, dispatch_walltime(NULL, (x - now) * NSEC_PER_SEC), NSEC_PER_SEC, 0);
}
});
dispatch_activate(t->t_src);
return t;
}