/* * Calcurse - text-based organizer * * Copyright (c) 2004-2020 calcurse Development Team * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * Send your feedback or comments to : misc@calcurse.org * Calcurse home page : http://calcurse.org * */ #include #include #include #include #include #include "calcurse.h" #include "sha1.h" llist_ts_t recur_alist_p; llist_t recur_elist; static void free_int(int *i) { mem_free(i); } void recur_free_int_list(llist_t *ilist) { LLIST_FREE_INNER(ilist, free_int); LLIST_FREE(ilist); } void recur_int_list_dup(llist_t *l, llist_t *ilist) { llist_item_t *i; int *o, *p; LLIST_INIT(l); if (ilist->head) { LLIST_FOREACH(ilist, i) { p = LLIST_GET_DATA(i); o = mem_malloc(sizeof(int)); *o = *p; LLIST_ADD(l, o); } } } static int int_cmp(int *list, int *i) { return *list == *i; } static void free_exc(struct excp *exc) { mem_free(exc); } void recur_free_exc_list(llist_t * exc) { LLIST_FREE_INNER(exc, free_exc); LLIST_FREE(exc); } static int exc_cmp_day(struct excp *a, struct excp *b) { return a->st < b->st ? -1 : (a->st == b->st ? 0 : 1); } static int exc_inday(struct excp *exc, time_t *day_start) { return (date_cmp_day(exc->st, *day_start) == 0); } static void recur_add_exc(llist_t * exc, time_t day) { struct excp *o = mem_malloc(sizeof(struct excp)); o->st = day; LLIST_ADD_SORTED(exc, o, exc_cmp_day); } void recur_exc_dup(llist_t * in, llist_t * exc) { llist_item_t *i; LLIST_INIT(in); if (exc) { LLIST_FOREACH(exc, i) { struct excp *p = LLIST_GET_DATA(i); recur_add_exc(in, p->st); } } } /* Return a string containing the exception days. */ char *recur_exc2str(llist_t *exc) { llist_item_t *i; struct excp *p; struct string s; struct tm tm; string_init(&s); LLIST_FOREACH(exc, i) { p = LLIST_GET_DATA(i); localtime_r(&p->st, &tm); string_catftime(&s, DATEFMT(conf.input_datefmt), &tm); string_catf(&s, "%c", ' '); } return string_buf(&s); } /* * Update a list of exceptions from a string of days. Any positive number of * spaces are allowed before, between and after the days. */ int recur_str2exc(llist_t *exc, char *days) { int updated = 0; char *d; time_t t = get_today(); llist_t nexc; LLIST_INIT(&nexc); while (1) { while (*days == ' ') days++; if ((d = strchr(days, ' '))) *d = '\0'; else if (!strlen(days)) break; if (parse_datetime(days, &t, 0)) recur_add_exc(&nexc, t); else goto cleanup; if (d) days = d + 1; else break; } recur_free_exc_list(exc); recur_exc_dup(exc, &nexc); updated = 1; cleanup: recur_free_exc_list(&nexc); return updated; } struct recur_event *recur_event_dup(struct recur_event *in) { EXIT_IF(!in, _("null pointer")); struct recur_event *rev = mem_malloc(sizeof(struct recur_event)); rev->id = in->id; rev->day = in->day; rev->mesg = mem_strdup(in->mesg); rev->rpt = mem_malloc(sizeof(struct rpt)); /* Note. The linked lists are NOT copied and no memory allocated. */ rev->rpt->type = in->rpt->type; rev->rpt->freq = in->rpt->freq; rev->rpt->until = in->rpt->until; LLIST_INIT(&rev->rpt->bymonth); LLIST_INIT(&rev->rpt->bywday); LLIST_INIT(&rev->rpt->bymonthday); LLIST_INIT(&rev->rpt->exc); recur_exc_dup(&rev->exc, &in->exc); if (in->note) rev->note = mem_strdup(in->note); else rev->note = NULL; return rev; } struct recur_apoint *recur_apoint_dup(struct recur_apoint *in) { EXIT_IF(!in, _("null pointer")); struct recur_apoint *rapt = mem_malloc(sizeof(struct recur_apoint)); rapt->start = in->start; rapt->dur = in->dur; rapt->state = in->state; rapt->mesg = mem_strdup(in->mesg); rapt->rpt = mem_malloc(sizeof(struct rpt)); /* Note. The linked lists are NOT copied and no memory allocated. */ rapt->rpt->type = in->rpt->type; rapt->rpt->freq = in->rpt->freq; rapt->rpt->until = in->rpt->until; LLIST_INIT(&rapt->rpt->bymonth); LLIST_INIT(&rapt->rpt->bywday); LLIST_INIT(&rapt->rpt->bymonthday); LLIST_INIT(&rapt->rpt->exc); recur_exc_dup(&rapt->exc, &in->exc); if (in->note) rapt->note = mem_strdup(in->note); else rapt->note = NULL; return rapt; } void recur_apoint_llist_init(void) { LLIST_TS_INIT(&recur_alist_p); } void recur_event_llist_init(void) { LLIST_INIT(&recur_elist); } void recur_apoint_free(struct recur_apoint *rapt) { mem_free(rapt->mesg); if (rapt->note) mem_free(rapt->note); if (rapt->rpt) mem_free(rapt->rpt); recur_free_exc_list(&rapt->exc); mem_free(rapt); } void recur_event_free(struct recur_event *rev) { mem_free(rev->mesg); if (rev->note) mem_free(rev->note); if (rev->rpt) mem_free(rev->rpt); recur_free_exc_list(&rev->exc); mem_free(rev); } void recur_apoint_llist_free(void) { LLIST_TS_FREE_INNER(&recur_alist_p, recur_apoint_free); LLIST_TS_FREE(&recur_alist_p); } void recur_event_llist_free(void) { LLIST_FREE_INNER(&recur_elist, recur_event_free); LLIST_FREE(&recur_elist); } static int recur_apoint_cmp(struct recur_apoint *a, struct recur_apoint *b) { if (a->start < b->start) return -1; if (a->start > b->start) return 1; if ((a->state & APOINT_NOTIFY) && !(b->state & APOINT_NOTIFY)) return -1; if (!(a->state & APOINT_NOTIFY) && (b->state & APOINT_NOTIFY)) return 1; return strcmp(a->mesg, b->mesg); } static int recur_event_cmp(struct recur_event *a, struct recur_event *b) { if (a->day < b->day) return -1; if (a->day > b->day) return 1; return strcmp(a->mesg, b->mesg); } /* Insert a new recursive appointment in the general linked list */ struct recur_apoint *recur_apoint_new(char *mesg, char *note, time_t start, long dur, char state, struct rpt *rpt) { struct recur_apoint *rapt = mem_malloc(sizeof(struct recur_apoint)); rapt->mesg = mem_strdup(mesg); rapt->note = (note != NULL) ? mem_strdup(note) : 0; rapt->start = start; rapt->dur = dur; rapt->state = state; rapt->rpt = mem_malloc(sizeof(struct rpt)); *rapt->rpt = *rpt; recur_int_list_dup(&rapt->rpt->bymonth, &rpt->bymonth); recur_free_int_list(&rpt->bymonth); recur_int_list_dup(&rapt->rpt->bywday, &rpt->bywday); recur_free_int_list(&rpt->bywday); recur_int_list_dup(&rapt->rpt->bymonthday, &rpt->bymonthday); recur_free_int_list(&rpt->bymonthday); /* * Note. The exception dates are in the list rapt->exc. * The (empty) list rapt->rpt->exc is not used. */ recur_exc_dup(&rapt->exc, &rpt->exc); recur_free_exc_list(&rpt->exc); LLIST_INIT(&rapt->rpt->exc); LLIST_TS_LOCK(&recur_alist_p); LLIST_TS_ADD_SORTED(&recur_alist_p, rapt, recur_apoint_cmp); LLIST_TS_UNLOCK(&recur_alist_p); return rapt; } /* Insert a new recursive event in the general linked list */ struct recur_event *recur_event_new(char *mesg, char *note, time_t day, int id, struct rpt *rpt) { struct recur_event *rev = mem_malloc(sizeof(struct recur_event)); rev->mesg = mem_strdup(mesg); rev->note = (note != NULL) ? mem_strdup(note) : 0; rev->day = day; rev->id = id; rev->rpt = mem_malloc(sizeof(struct rpt)); *rev->rpt = *rpt; recur_int_list_dup(&rev->rpt->bymonth, &rpt->bymonth); recur_free_int_list(&rpt->bymonth); recur_int_list_dup(&rev->rpt->bywday, &rpt->bywday); recur_free_int_list(&rpt->bywday); recur_int_list_dup(&rev->rpt->bymonthday, &rpt->bymonthday); recur_free_int_list(&rpt->bymonthday); /* Similarly as for recurrent appointment. */ recur_exc_dup(&rev->exc, &rpt->exc); recur_free_exc_list(&rpt->exc); LLIST_INIT(&rev->rpt->exc); LLIST_ADD_SORTED(&recur_elist, rev, recur_event_cmp); return rev; } /* * Correspondance between the defines on recursive type, * and the letter to be written in file. */ char recur_def2char(enum recur_type define) { char recur_char; switch (define) { case RECUR_DAILY: recur_char = 'D'; break; case RECUR_WEEKLY: recur_char = 'W'; break; case RECUR_MONTHLY: recur_char = 'M'; break; case RECUR_YEARLY: recur_char = 'Y'; break; default: recur_char = 0; } return recur_char; } /* * Correspondance between the letters written in file and the defines * concerning the recursive type. */ int recur_char2def(char type) { int recur_def; switch (type) { case 'D': recur_def = RECUR_DAILY; break; case 'W': recur_def = RECUR_WEEKLY; break; case 'M': recur_def = RECUR_MONTHLY; break; case 'Y': recur_def = RECUR_YEARLY; break; default: EXIT(_("unknown character")); return 0; } return recur_def; } /* Write the bymonthday list. */ static void bymonthday_append(struct string *s, llist_t *l) { llist_item_t *i; LLIST_FOREACH(l, i) { int *day = LLIST_GET_DATA(i); string_catf(s, " d%d", *day); } } /* Write the bywday list. */ static void bywday_append(struct string *s, llist_t *l) { llist_item_t *i; LLIST_FOREACH(l, i) { int *wday = LLIST_GET_DATA(i); string_catf(s, " w%d", *wday); } } /* Write the bymonth list. */ static void bymonth_append(struct string *s, llist_t *l) { llist_item_t *i; LLIST_FOREACH(l, i) { int *mon = LLIST_GET_DATA(i); string_catf(s, " m%d", *mon); } } /* Write days for which recurrent items should not be repeated. */ static void recur_exc_append(struct string *s, llist_t *lexc) { llist_item_t *i; struct tm lt; time_t t; int st_mon, st_day, st_year; LLIST_FOREACH(lexc, i) { struct excp *exc = LLIST_GET_DATA(i); t = exc->st; localtime_r(&t, <); st_mon = lt.tm_mon + 1; st_day = lt.tm_mday; st_year = lt.tm_year + 1900; string_catf(s, " !%02u/%02u/%04u", st_mon, st_day, st_year); } } /* Load the recursive appointment description */ char *recur_apoint_scan(FILE *f, struct tm start, struct tm end, char state, char *note, struct item_filter *filter, struct rpt *rpt) { char buf[BUFSIZ], *nl; time_t tstart, tend; struct recur_apoint *rapt = NULL; int cond; if (!check_date(start.tm_year, start.tm_mon, start.tm_mday) || !check_date(end.tm_year, end.tm_mon, end.tm_mday) || !check_time(start.tm_hour, start.tm_min) || !check_time(end.tm_hour, end.tm_min)) return _("illegal date in appointment"); /* Read the appointment description */ if (!fgets(buf, sizeof buf, f)) return _("error in appointment description"); nl = strchr(buf, '\n'); if (nl) { *nl = '\0'; } start.tm_sec = end.tm_sec = 0; start.tm_isdst = end.tm_isdst = -1; start.tm_year -= 1900; start.tm_mon--; end.tm_year -= 1900; end.tm_mon--; tstart = mktime(&start); tend = mktime(&end); if (tstart == -1 || tend == -1 || tstart > tend) return _("date error in appointment"); /* Does it occur on the start day? */ if (!recur_item_find_occurrence(tstart, tend - tstart, rpt, NULL, update_time_in_date(tstart, 0, 0), NULL)) { char *fmt = _("recurrence error: not on start day (%s)"); return day_ins(&fmt, tstart); } /* Filter item. */ if (filter) { cond = ( !(filter->type_mask & TYPE_MASK_RECUR_APPT) || (filter->regex && regexec(filter->regex, buf, 0, 0, 0)) || (filter->start_from != -1 && tstart < filter->start_from) || (filter->start_to != -1 && tstart > filter->start_to) || (filter->end_from != -1 && tend < filter->end_from) || (filter->end_to != -1 && tend > filter->end_to) ); if (filter->hash) { rapt = recur_apoint_new(buf, note, tstart, tend - tstart, state, rpt); char *hash = recur_apoint_hash(rapt); cond = cond || !hash_matches(filter->hash, hash); mem_free(hash); } if ((!filter->invert && cond) || (filter->invert && !cond)) { if (filter->hash) recur_apoint_erase(rapt); return NULL; } } if (!rapt) rapt = recur_apoint_new(buf, note, tstart, tend - tstart, state, rpt); return NULL; } /* Load the recursive events from file */ char *recur_event_scan(FILE * f, struct tm start, int id, char *note, struct item_filter *filter, struct rpt *rpt) { char buf[BUFSIZ], *nl; time_t tstart, tend; struct recur_event *rev = NULL; int cond; if (!check_date(start.tm_year, start.tm_mon, start.tm_mday) || !check_time(start.tm_hour, start.tm_min)) return _("illegel date in event"); /* Read the event description */ if (!fgets(buf, sizeof buf, f)) return _("error in appointment description"); nl = strchr(buf, '\n'); if (nl) { *nl = '\0'; } start.tm_hour = 0; start.tm_min = 0; start.tm_sec = 0; start.tm_isdst = -1; start.tm_year -= 1900; start.tm_mon--; tstart = mktime(&start); if (tstart == -1) return _("date error in event"); tend = ENDOFDAY(tstart); /* Does it occur on the start day? */ if (!recur_item_find_occurrence(tstart, -1, rpt, NULL, update_time_in_date(tstart, 0, 0), NULL)) { char *fmt = _("recurrence error: not on start day (%s)"); return day_ins(&fmt, tstart); } /* Filter item. */ if (filter) { cond = ( !(filter->type_mask & TYPE_MASK_RECUR_EVNT) || (filter->regex && regexec(filter->regex, buf, 0, 0, 0)) || (filter->start_from != -1 && tstart < filter->start_from) || (filter->start_to != -1 && tstart > filter->start_to) || (filter->end_from != -1 && tend < filter->end_from) || (filter->end_to != -1 && tend > filter->end_to) ); if (filter->hash) { rev = recur_event_new(buf, note, tstart, id, rpt); char *hash = recur_event_hash(rev); cond = cond || !hash_matches(filter->hash, hash); mem_free(hash); } if ((!filter->invert && cond) || (filter->invert && !cond)) { if (filter->hash) recur_event_erase(rev); return NULL; } } if (!rev) rev = recur_event_new(buf, note, tstart, id, rpt); return NULL; } char *recur_apoint_tostr(struct recur_apoint *o) { struct string s; struct tm lt; time_t t; string_init(&s); t = o->start; localtime_r(&t, <); string_catf(&s, "%02u/%02u/%04u @ %02u:%02u", lt.tm_mon + 1, lt.tm_mday, 1900 + lt.tm_year, lt.tm_hour, lt.tm_min); t = o->start + o->dur; localtime_r(&t, <); string_catf(&s, " -> %02u/%02u/%04u @ %02u:%02u", lt.tm_mon + 1, lt.tm_mday, 1900 + lt.tm_year, lt.tm_hour, lt.tm_min); t = o->rpt->until; if (t == 0) { /* We have an endless recurrent appointment. */ string_catf(&s, " {%d%c", o->rpt->freq, recur_def2char(o->rpt->type)); } else { localtime_r(&t, <); string_catf(&s, " {%d%c -> %02u/%02u/%04u", o->rpt->freq, recur_def2char(o->rpt->type), lt.tm_mon + 1, lt.tm_mday, 1900 + lt.tm_year); } bymonthday_append(&s, &o->rpt->bymonthday); bywday_append(&s, &o->rpt->bywday); bymonth_append(&s, &o->rpt->bymonth); recur_exc_append(&s, &o->exc); string_catf(&s, "} "); if (o->note) string_catf(&s, ">%s ", o->note); if (o->state & APOINT_NOTIFY) string_catf(&s, "%c", '!'); else string_catf(&s, "%c", '|'); string_catf(&s, "%s", o->mesg); return string_buf(&s); } char *recur_apoint_hash(struct recur_apoint *rapt) { char *raw = recur_apoint_tostr(rapt); char *sha1 = mem_malloc(SHA1_DIGESTLEN * 2 + 1); sha1_digest(raw, sha1); mem_free(raw); return sha1; } void recur_apoint_write(struct recur_apoint *o, FILE * f) { char *str = recur_apoint_tostr(o); fprintf(f, "%s\n", str); mem_free(str); } char *recur_event_tostr(struct recur_event *o) { struct string s; struct tm lt; time_t t; int st_mon, st_day, st_year; int end_mon, end_day, end_year; string_init(&s); t = o->day; localtime_r(&t, <); st_mon = lt.tm_mon + 1; st_day = lt.tm_mday; st_year = lt.tm_year + 1900; t = o->rpt->until; if (t == 0) { /* We have an endless recurrent event. */ string_catf(&s, "%02u/%02u/%04u [%d] {%d%c", st_mon, st_day, st_year, o->id, o->rpt->freq, recur_def2char(o->rpt->type)); } else { localtime_r(&t, <); end_mon = lt.tm_mon + 1; end_day = lt.tm_mday; end_year = lt.tm_year + 1900; string_catf(&s, "%02u/%02u/%04u [%d] {%d%c -> %02u/%02u/%04u", st_mon, st_day, st_year, o->id, o->rpt->freq, recur_def2char(o->rpt->type), end_mon, end_day, end_year); } bymonthday_append(&s, &o->rpt->bymonthday); bywday_append(&s, &o->rpt->bywday); bymonth_append(&s, &o->rpt->bymonth); recur_exc_append(&s, &o->exc); string_catf(&s, "} "); if (o->note) string_catf(&s, ">%s ", o->note); string_catf(&s, "%s", o->mesg); return string_buf(&s); } char *recur_event_hash(struct recur_event *rev) { char *raw = recur_event_tostr(rev); char *sha1 = mem_malloc(SHA1_DIGESTLEN * 2 + 1); sha1_digest(raw, sha1); mem_free(raw); return sha1; } void recur_event_write(struct recur_event *o, FILE * f) { char *str = recur_event_tostr(o); fprintf(f, "%s\n", str); mem_free(str); } /* Write recursive items to file. */ void recur_save_data(FILE * f) { llist_item_t *i; LLIST_FOREACH(&recur_elist, i) { struct recur_event *rev = LLIST_GET_DATA(i); recur_event_write(rev, f); } LLIST_TS_LOCK(&recur_alist_p); LLIST_TS_FOREACH(&recur_alist_p, i) { struct recur_apoint *rapt = LLIST_GET_DATA(i); recur_apoint_write(rapt, f); } LLIST_TS_UNLOCK(&recur_alist_p); } /* * Return the month day counted from the opposite end of the month. */ static int opp_mday(int year, int month, int day) { EXIT_IF(day == 0, _("month day is zero")); int m_days = days[month - 1] + (month == 2 && ISLEAP(year)); if (day > 0) return day - 1 - m_days; else return day + 1 + m_days; } /* * The two following defines together with the diff_days, diff_months and * diff_years functions were provided by Lukas Fleischer to correct the wrong * calculation of recurrent dates after a turn of year. */ #define BC(start, end, bs) \ (((end) - (start) + ((start) % bs) - ((end) % bs)) / bs \ + ((((start) % bs) == 0) ? 1 : 0)) #define LEAPCOUNT(start, end) \ (BC(start, end, 4) - BC(start, end, 100) + BC(start, end, 400)) /* Calculate the difference in days between two dates. */ static long diff_days(struct tm lt_start, struct tm lt_end) { long diff; if (lt_end.tm_year < lt_start.tm_year) return 0; diff = lt_end.tm_yday - lt_start.tm_yday; if (lt_end.tm_year > lt_start.tm_year) { diff += (lt_end.tm_year - lt_start.tm_year) * YEARINDAYS; diff += LEAPCOUNT(lt_start.tm_year + TM_YEAR_BASE, lt_end.tm_year + TM_YEAR_BASE - 1); } return diff; } /* Calculate the difference in months between two dates. */ static long diff_months(struct tm lt_start, struct tm lt_end) { long diff; if (lt_end.tm_year < lt_start.tm_year) return 0; diff = lt_end.tm_mon - lt_start.tm_mon; diff += (lt_end.tm_year - lt_start.tm_year) * YEARINMONTHS; return diff; } /* Calculate the difference in years between two dates. */ static long diff_years(struct tm lt_start, struct tm lt_end) { return lt_end.tm_year - lt_start.tm_year; } /* * Return true if 'mon' and 'mday' is month and day of t * (after a call of mktime()). */ static int date_chk(time_t t, int mon, int mday) { struct tm tm; localtime_r(&t, &tm); return tm.tm_mon == mon && tm.tm_mday == mday; } /* * Return true if the rrule (start, dur, rpt, exc) has an occurrence on the * given day. If so, save that occurrence in a (dynamic or static) buffer. */ static int find_occurrence(time_t start, long dur, struct rpt *rpt, llist_t *exc, time_t day, time_t *occurrence) { /* * Duration-on-day-d fix. * An item cannot end on midnight or else it is counted towards the next day. * An event (dur == -1) has no explicit duration, but is considered to last for * the entire day (d) which depends on DST. */ #define DUR(d) (dur == -1 ? DAYLEN((d)) - 1 : dur - 1) long diff; struct tm lt_day, lt_start, lt_occur; time_t t; int mday, order, pwday, nwday, mon; /* Is the given day before the day of the first occurence? */ if (date_cmp_day(day, start) < 0) return 0; /* * - or after the day of the last occurrence (which may stretch beyond * the until date)? Extraneous days are eliminated later. */ if (rpt->until && date_cmp_day(NEXTDAY(rpt->until) + DUR(rpt->until), day) < 0) return 0; localtime_r(&day, <_day); /* Given day. */ localtime_r(&start, <_start); /* Original item. */ lt_occur = lt_start; /* First occurence. */ /* * Update to the most recent occurrence before or on the selected day. */ switch (rpt->type) { case RECUR_DAILY: /* Number of days since the most recent occurrence. */ diff = diff_days(lt_occur, lt_day) % rpt->freq; lt_occur.tm_mday = lt_day.tm_mday - diff; lt_occur.tm_mon = lt_day.tm_mon; lt_occur.tm_year = lt_day.tm_year; break; case RECUR_WEEKLY: diff = diff_days(lt_occur, lt_day) % (rpt->freq * WEEKINDAYS); lt_occur.tm_mday = lt_day.tm_mday - diff; lt_occur.tm_mon = lt_day.tm_mon; lt_occur.tm_year = lt_day.tm_year; break; case RECUR_MONTHLY: diff = diff_months(lt_occur, lt_day) % rpt->freq; if (!diff && lt_day.tm_mday < lt_occur.tm_mday) diff += rpt->freq; lt_occur.tm_mon = lt_day.tm_mon - diff; lt_occur.tm_year = lt_day.tm_year; break; case RECUR_YEARLY: diff = diff_years(lt_occur, lt_day) % rpt->freq; if (!diff && (lt_day.tm_mon < lt_occur.tm_mon || (lt_day.tm_mon == lt_occur.tm_mon && lt_day.tm_mday < lt_occur.tm_mday))) diff += rpt->freq; lt_occur.tm_year = lt_day.tm_year - diff; break; default: EXIT(_("unknown item type")); } /* Switch to calendar (Unix) time. */ lt_occur.tm_isdst = -1; t = mktime(<_occur); /* * Impossible dates must be ignored (according to RFC 5545). Changing * only the year or the month may lead to dates like 29 February in * non-leap years or 31 November. */ if ((rpt->type == RECUR_MONTHLY || rpt->type == RECUR_YEARLY) && !date_chk(t, lt_occur.tm_mon, lt_start.tm_mday)) return 0; /* * BYMONTHDAY reduction * A month day has two possible list forms. */ mday = opp_mday(lt_occur.tm_year + 1900, lt_occur.tm_mon + 1, lt_occur.tm_mday); if (rpt->bymonthday.head && rpt->type == RECUR_DAILY && !LLIST_FIND_FIRST(&rpt->bymonthday, <_occur.tm_mday, int_cmp) && !LLIST_FIND_FIRST(&rpt->bymonthday, &mday, int_cmp)) return 0; /* BYDAY reduction for DAILY */ if (rpt->bywday.head && rpt->type == RECUR_DAILY && !LLIST_FIND_FIRST(&rpt->bywday, <_occur.tm_wday, int_cmp)) return 0; /* * BYDAY reduction for MONTHLY * A weekday has three possible list forms. */ if (rpt->bywday.head && rpt->type == RECUR_MONTHLY && rpt->bymonthday.head) { /* positive order */ order = (lt_occur.tm_mday + 6) / WEEKINDAYS; pwday = order * WEEKINDAYS + lt_occur.tm_wday; /* negative order */ order = order - wday_per_month(lt_occur.tm_mon + 1, lt_occur.tm_year + 1900, lt_occur.tm_wday) - 1; nwday = order * WEEKINDAYS - lt_occur.tm_wday; if (!LLIST_FIND_FIRST(&rpt->bywday, <_occur.tm_wday, int_cmp) && !LLIST_FIND_FIRST(&rpt->bywday, &pwday, int_cmp) && !LLIST_FIND_FIRST(&rpt->bywday, &nwday, int_cmp)) return 0; } /* * BYDAY reduction for YEARLY * A weekday has three possible list forms. */ if (rpt->bywday.head && rpt->type == RECUR_YEARLY && rpt->bymonthday.head) { /* positive order */ order = lt_occur.tm_yday / WEEKINDAYS; pwday = order * WEEKINDAYS + lt_occur.tm_wday; /* negative order */ order = order - wday_per_year(lt_occur.tm_year + 1900, lt_occur.tm_wday) - 1; nwday = order * WEEKINDAYS - lt_occur.tm_wday; if (!LLIST_FIND_FIRST(&rpt->bywday, <_occur.tm_wday, int_cmp) && !LLIST_FIND_FIRST(&rpt->bywday, &pwday, int_cmp) && !LLIST_FIND_FIRST(&rpt->bywday, &nwday, int_cmp)) return 0; } /* BYMONTH reduction */ mon = lt_occur.tm_mon + 1; if (rpt->bymonth.head && rpt->type != RECUR_YEARLY && !LLIST_FIND_FIRST(&rpt->bymonth, &mon, int_cmp)) return 0; /* Exception day? */ if (exc && LLIST_FIND_FIRST(exc, &t, exc_inday)) return 0; /* Extraneous day? */ if (rpt->until && t >= NEXTDAY(rpt->until)) return 0; /* Does it span the given day? */ if (t + DUR(t) < day) return 0; if (occurrence) *occurrence = t; return 1; } #undef DUR /* * Return true if the rrule (s, d, r, e) has an occurrence, depending * on the frequency, in the year, month or week of day. */ static int freq_chk(time_t day, time_t s, long d, struct rpt *r, llist_t *e) { if (r->type == RECUR_DAILY) EXIT(_("no daily frequency check")); struct tm tm_start, tm_day; struct rpt fc_rpt; time_t fc_day, fc_s; localtime_r(&s, &tm_start); localtime_r(&day, &tm_day); if (r->type == RECUR_WEEKLY) { /* Set day to the weekly occurrence. */ fc_day = date_sec_change( day, 0, WDAY(tm_start.tm_wday) - WDAY(tm_day.tm_wday) ); fc_s = s; } else { /* The start day may be invalid in some months. */ tm_day.tm_mday = tm_start.tm_mday = 1; if (r->type == RECUR_YEARLY) tm_day.tm_mon = tm_start.tm_mon; tm_day.tm_isdst = tm_start.tm_isdst = -1; fc_day = mktime(&tm_day); fc_s = mktime(&tm_start); } /* Turn all reductions off. */ fc_rpt = *r; fc_rpt.until = 0; fc_rpt.bymonth.head = fc_rpt.bywday.head = fc_rpt.bymonthday.head = NULL; return find_occurrence(fc_s, d, &fc_rpt, e, fc_day, NULL); } /* * Return true if the rrule (s, d, r, e) has an occurrence on 'day' after * 'first'; if so, return it in occurrence. */ static int test_occurrence(time_t s, long d, struct rpt *r, llist_t *e, time_t first, time_t day, time_t *occurrence) { time_t occ; if (find_occurrence(s, d, r, e, day, &occ)) { if (occ < first) return 0; if (occurrence) *occurrence = occ; return 1; } return 0; } #define NO_EXPANSION -1 static int expand_weekly(time_t start, long dur, struct rpt *rpt, llist_t *exc, time_t day, time_t *occurrence) { struct tm tm_start; llist_item_t *i; int *w; time_t w_start; localtime_r(&start, &tm_start); /* BYDAY expansion */ if (rpt->bywday.head) { LLIST_FOREACH(&rpt->bywday, i) { w = LLIST_GET_DATA(i); if (*w < 0 || *w > 6) continue; /* * Modify rrule start with a new day in the same week as * start - taking first day of the week into account. */ w_start = date_sec_change( start, 0, WDAY(*w) - WDAY(tm_start.tm_wday) ); if (test_occurrence(w_start, dur, rpt, exc, start, day, occurrence)) return 1; } } else return NO_EXPANSION; /* No occurrence */ return 0; } static int expand_monthly(time_t start, long dur, struct rpt *rpt, llist_t *exc, time_t day, time_t *occurrence) { struct tm tm_start, tm_day; llist_item_t *i; int *w, mday, mon, valid; time_t nstart; struct rpt r = *rpt; localtime_r(&day, &tm_day); /* * The following three conditional alternatives are mutually exclusive * and cover all four cases of two booleans. */ /* BYMONTHDAY expansion */ if (rpt->bymonthday.head) { LLIST_FOREACH(&rpt->bymonthday, i) { mday = *(int *)LLIST_GET_DATA(i); if (mday < 0) mday = opp_mday(tm_day.tm_year + 1900, tm_day.tm_mon + 1, mday); /* * Modify rrule start with a new monthday. * If it is invalid (29, 30 or 31) in the start month, * the month is changed to an earlier one matching the * frequency. */ localtime_r(&start, &tm_start); mon = tm_start.tm_mon; tm_start.tm_mday = mday; tm_start.tm_isdst = -1; nstart = mktime(&tm_start); valid = date_chk(nstart, mon, mday); /* Never valid? */ if (!valid && !(rpt->freq % 12)) return 0; /* Note. The loop will terminate! */ while (!valid) { localtime_r(&start, &tm_start); mon -= rpt->freq; tm_start.tm_mon = mon; tm_start.tm_mday = mday; tm_start.tm_isdst = -1; nstart = mktime(&tm_start); valid = date_chk(nstart, (mon + 12) % 12, mday); } if (test_occurrence(nstart, dur, rpt, exc, start, day, occurrence)) return 1; } } /* BYDAY special expansion for MONTHLY */ else if (rpt->bywday.head) { /* The frequency is modified later. */ if (!freq_chk(day, start, dur, rpt, exc)) return 0; LLIST_FOREACH(&rpt->bywday, i) { w = LLIST_GET_DATA(i); int order, wday, nbwd; localtime_r(&start, &tm_start); /* * Construct a weekly rrule; BYMONTH-reduction in * find_occurrence() will reduce to the bymonth list. */ r.type = RECUR_WEEKLY; if (*w > 6) { /* * A single occurrence counting forwards from * the start of the month. */ order = *w / WEEKINDAYS; wday = *w % WEEKINDAYS; nbwd = wday_per_month(tm_day.tm_mon + 1, tm_day.tm_year + 1900, wday); if (nbwd < order) return 0; r.freq = order; tm_start.tm_mday = 1; tm_start.tm_mon = tm_day.tm_mon; tm_start.tm_year = tm_day.tm_year; tm_start.tm_isdst = -1; /* Start in the week before the month. */ nstart = date_sec_change( next_wday(mktime(&tm_start), wday), 0, -WEEKINDAYS ); r.until = date_sec_change( update_time_in_date(nstart, 0, 0), 0, r.freq * WEEKINDAYS ); if (rpt->until && r.until > rpt->until) return 0; } else if (*w > -1) { /* Expansion to each week. */ wday = *w % WEEKINDAYS; r.freq = 1; nstart = next_wday(start, wday); } else if (*w < -6) { /* * A single ocurrence counting backwards from * the end of the month. */ order = -(*w) / WEEKINDAYS; wday = -(*w) % WEEKINDAYS; nbwd = wday_per_month(tm_day.tm_mon + 1, tm_day.tm_year + 1900, wday); if (nbwd < order) return 0; r.freq = nbwd - order + 1; tm_start.tm_mday = 1; tm_start.tm_mon = tm_day.tm_mon; tm_start.tm_year = tm_day.tm_year; tm_start.tm_isdst = -1; nstart = date_sec_change( next_wday(mktime(&tm_start), wday), 0, -WEEKINDAYS ); r.until = date_sec_change( update_time_in_date(nstart, 0, 0), 0, r.freq * WEEKINDAYS ); if (rpt->until && r.until > rpt->until) return 0; } else EXIT(_("illegal BYDAY value")); if (test_occurrence(nstart, dur, &r, exc, start, day, occurrence)) return 1; } } else return NO_EXPANSION; /* No occurrence */ return 0; } static int expand_yearly(time_t start, long dur, struct rpt *rpt, llist_t *exc, time_t day, time_t *occurrence) { struct tm tm_start, tm_day; llist_item_t *i, *j; int *m, *w, mday, wday, order, nbwd; time_t nstart; struct rpt r; localtime_r(&day, &tm_day); /* * The following five conditional alternatives are mutually exclusive * and cover all eight cases of three booleans. */ /* BYMONTH expansion */ if (rpt->bymonth.head && !rpt->bymonthday.head && !rpt->bywday.head) { LLIST_FOREACH(&rpt->bymonth, i) { m = LLIST_GET_DATA(i); /* Modify rrule start with new month. */ localtime_r(&start, &tm_start); tm_start.tm_mon = *m - 1; tm_start.tm_isdst = -1; nstart = mktime(&tm_start); if (!date_chk(nstart, *m - 1, tm_start.tm_mday)) continue; if (find_occurrence(nstart, dur, rpt, exc, day, occurrence)) return 1; } } else /* BYDAY special expansion for MONTHLY or YEARLY */ if (!rpt->bymonthday.head && rpt->bywday.head) { /* Check needed because frequency is modified later. */ if (!freq_chk(day, start, dur, rpt, exc)) return 0; LLIST_FOREACH(&rpt->bywday, i) { w = LLIST_GET_DATA(i); localtime_r(&start, &tm_start); /* * Construct a suitable weekly rrule. BYMONTH * reduction in find_occurrence() will limit * occurrences if needed. */ r = *rpt; r.type = RECUR_WEEKLY; if (*w > 6) { /* * Special expand: A single ocurrence counting * forward from the start of the month/year. * Start in the week before with a frequency * that matches the ordered weekday and with * until day that allows only one occurrence. */ order = *w / WEEKINDAYS; wday = *w % WEEKINDAYS; if (rpt->bymonth.head) nbwd = wday_per_month( tm_day.tm_mon + 1, tm_day.tm_year + 1900, wday ); else nbwd = wday_per_year( tm_day.tm_year + 1900, wday ); if (nbwd < order) return 0; r.freq = order; tm_start.tm_mday = 1; if (rpt->bymonth.head) tm_start.tm_mon = tm_day.tm_mon; else tm_start.tm_mon = 0; tm_start.tm_year = tm_day.tm_year; tm_start.tm_isdst = -1; nstart = date_sec_change( next_wday(mktime(&tm_start), wday), 0, -WEEKINDAYS ); r.until = date_sec_change( update_time_in_date(nstart, 0, 0), 0, r.freq * WEEKINDAYS ); if (rpt->until && r.until > rpt->until) return 0; } else if (*w > -1) { /* Expand to each week of the month/year. */ wday = *w % WEEKINDAYS; r.freq = 1; nstart = next_wday(start, wday); } else if (*w < -6) { /* * Special expand: A single ocurrence counting * backward from the end of the month/year. */ order = -(*w) / WEEKINDAYS; wday = -(*w) % WEEKINDAYS; if (rpt->bymonth.head) nbwd = wday_per_month( tm_day.tm_mon + 1, tm_day.tm_year + 1900, wday ); else nbwd = wday_per_year( tm_day.tm_year + 1900, wday ); if (nbwd < order) return 0; r.freq = nbwd - order + 1; tm_start.tm_mday = 1; if (rpt->bymonth.head) tm_start.tm_mon = tm_day.tm_mon; else tm_start.tm_mon = 0; tm_start.tm_year = tm_day.tm_year; tm_start.tm_isdst = -1; nstart = date_sec_change( next_wday(mktime(&tm_start), wday), 0, -WEEKINDAYS ); r.until = date_sec_change( update_time_in_date(nstart, 0, 0), 0, r.freq * WEEKINDAYS ); if (rpt->until && r.until > rpt->until) return 0; } else EXIT(_("illegal BYDAY value")); if (test_occurrence(nstart, dur, &r, exc, start, day, occurrence)) return 1; } } else /* BYMONTHDAY expansion */ if (!rpt->bymonth.head && rpt->bymonthday.head) { LLIST_FOREACH(&rpt->bymonthday, i) { mday = *(int *)LLIST_GET_DATA(i); if (mday < 0) mday = opp_mday( tm_day.tm_year + 1900, tm_day.tm_mon + 1, mday ); /* Modify rrule start with new monthday. */ localtime_r(&start, &tm_start); tm_start.tm_mday = mday; tm_start.tm_isdst = -1; nstart = mktime(&tm_start); if (!date_chk(nstart, tm_start.tm_mon, mday)) continue; if (find_occurrence(nstart, dur, rpt, exc, day, occurrence)) return 1; } } else /* BYMONTH and BYMONTHDAY expansion */ if (rpt->bymonth.head && rpt->bymonthday.head) { LLIST_FOREACH(&rpt->bymonth, i) { m = LLIST_GET_DATA(i); LLIST_FOREACH(&rpt->bymonthday, j) { mday = *(int *)LLIST_GET_DATA(j); if (mday < 0) mday = opp_mday( tm_day.tm_year + 1900, tm_day.tm_mon + 1, mday ); /* Modify start with new monthday and month. */ localtime_r(&start, &tm_start); /* Number of days in February! */ if (*m == 2 && mday == 29 && !ISLEAP(tm_start.tm_year + 1900) && rpt->freq % 4) { if (!freq_chk(day, start, dur, rpt, exc)) return 0; tm_start.tm_year -= tm_start.tm_year % 4; } tm_start.tm_mday = mday; tm_start.tm_mon = *m - 1; tm_start.tm_isdst = -1; nstart = mktime(&tm_start); if (!date_chk(nstart, *m - 1, mday)) continue; if (find_occurrence(nstart, dur, rpt, exc, day, occurrence)) return 1; } } } else return NO_EXPANSION; /* No occurrence */ return 0; } /* * Membership test for the recurrence set of the rrule (start, dur, rpt, exc). * * Return true if day belongs to the set. If so, the occurrence is saved in a * buffer. A positive result is always the outcome of find_occurrence(), whereas * a negative result may be arrived at in other ways. * * The basic (type, frequency)-check is in find_occurrence(). When recurrence * set expansion and/or reduction (RFC 5545) is needed, expansion is done before * call of find_occurrence(), while reduction takes place in find_occurrence(). * * Recurrence set expansion is accomplished by a combination of calls of * find_occurrence(), possibly with change of type, frequency and start. */ unsigned recur_item_find_occurrence(time_t start, long dur, struct rpt *rpt, llist_t *exc, time_t day, time_t *occurrence) { int res; /* To make it possible to set an earlier start without expanding the * recurrence set. */ if (date_cmp_day(day, start) < 0) return 0; switch (rpt->type) { case RECUR_DAILY: res = NO_EXPANSION; break; case RECUR_WEEKLY: res = expand_weekly(start, dur, rpt, exc, day, occurrence); break; case RECUR_MONTHLY: res = expand_monthly(start, dur, rpt, exc, day, occurrence); break; case RECUR_YEARLY: res = expand_yearly(start, dur, rpt, exc, day, occurrence); break; default: res = 0; } if (res == NO_EXPANSION) return find_occurrence(start, dur, rpt, exc, day, occurrence); /* The result of find_occurrence() is passed on. */ return res; } #undef NO_EXPANSION unsigned recur_apoint_find_occurrence(struct recur_apoint *rapt, time_t day_start, time_t *occurrence) { return recur_item_find_occurrence(rapt->start, rapt->dur, rapt->rpt, &rapt->exc, day_start, occurrence); } unsigned recur_event_find_occurrence(struct recur_event *rev, time_t day_start, time_t *occurrence) { return recur_item_find_occurrence(rev->day, -1, rev->rpt, &rev->exc, day_start, occurrence); } /* Check if a recurrent item belongs to the selected day. */ unsigned recur_item_inday(time_t start, long dur, struct rpt *rpt, llist_t * exc, time_t day_start) { /* We do not need the (real) start time of the occurrence here, so just * ignore the buffer. */ return recur_item_find_occurrence(start, dur, rpt, exc, day_start, NULL); } unsigned recur_apoint_inday(struct recur_apoint *rapt, time_t *day_start) { return recur_item_inday(rapt->start, rapt->dur, rapt->rpt, &rapt->exc, *day_start); } unsigned recur_event_inday(struct recur_event *rev, time_t *day_start) { return recur_item_inday(rev->day, -1, rev->rpt, &rev->exc, *day_start); } /* Add an exception to a recurrent event. */ void recur_event_add_exc(struct recur_event *rev, time_t date) { recur_add_exc(&rev->exc, date); } /* Add an exception to a recurrent appointment. */ void recur_apoint_add_exc(struct recur_apoint *rapt, time_t date) { int need_check_notify = 0; if (notify_bar()) need_check_notify = notify_same_recur_item(rapt); recur_add_exc(&rapt->exc, date); if (need_check_notify) notify_check_next_app(0); } /* * Delete a recurrent event from the list (if delete_whole is not null), * or delete only one occurence of the recurrent event. */ void recur_event_erase(struct recur_event *rev) { llist_item_t *i = LLIST_FIND_FIRST(&recur_elist, rev, NULL); if (!i) EXIT(_("event not found")); LLIST_REMOVE(&recur_elist, i); } /* * Delete a recurrent appointment from the list (if delete_whole is not null), * or delete only one occurence of the recurrent appointment. */ void recur_apoint_erase(struct recur_apoint *rapt) { LLIST_TS_LOCK(&recur_alist_p); llist_item_t *i = LLIST_TS_FIND_FIRST(&recur_alist_p, rapt, NULL); int need_check_notify = 0; if (!i) EXIT(_("appointment not found")); if (notify_bar()) need_check_notify = notify_same_recur_item(rapt); LLIST_TS_REMOVE(&recur_alist_p, i); if (need_check_notify) notify_check_next_app(0); LLIST_TS_UNLOCK(&recur_alist_p); } /* Read monthday list. */ void recur_bymonthday(llist_t *l, FILE *data_file) { int c = 0, d; LLIST_INIT(l); while ((c = getc(data_file)) == 'd') { ungetc(c, data_file); if (fscanf(data_file, "d%d ", &d) != 1) EXIT(_("syntax error in bymonthday")); int *i = mem_malloc(sizeof(int)); *i = d; LLIST_ADD(l, i); } ungetc(c, data_file); } /* Read weekday list. */ void recur_bywday(enum recur_type type, llist_t *l, FILE *data_file) { int c = 0, w; type = !(type == RECUR_MONTHLY || type == RECUR_YEARLY); LLIST_INIT(l); while ((c = getc(data_file)) == 'w') { ungetc(c, data_file); if (fscanf(data_file, "w%d ", &w) != 1) EXIT(_("syntax error in bywday")); if (type && (w < 0 || w > 6)) EXIT(_("illegal BYDAY value")); int *i = mem_malloc(sizeof(int)); *i = w; LLIST_ADD(l, i); } ungetc(c, data_file); } /* Read month list. */ void recur_bymonth(llist_t *l, FILE *data_file) { int c = 0, m; LLIST_INIT(l); while ((c = getc(data_file)) == 'm') { ungetc(c, data_file); if (fscanf(data_file, "m%d ", &m) != 1) EXIT(_("syntax error in bymonth")); EXIT_IF(m < 1 || m > 12, _("illegal bymonth value")); int *i = mem_malloc(sizeof(int)); *i = m; LLIST_ADD(l, i); } ungetc(c, data_file); } /* * Read days for which recurrent items must not be repeated * (such days are called exceptions). */ void recur_exc_scan(llist_t * lexc, FILE * data_file) { int c = 0; struct tm day; LLIST_INIT(lexc); while ((c = getc(data_file)) == '!') { ungetc(c, data_file); if (fscanf(data_file, "!%d / %d / %d ", &day.tm_mon, &day.tm_mday, &day.tm_year) != 3) { EXIT(_("syntax error in item date")); } EXIT_IF(!check_date(day.tm_year, day.tm_mon, day.tm_mday), _("date error in item exception")); day.tm_hour = 0; day.tm_min = day.tm_sec = 0; day.tm_isdst = -1; day.tm_year -= 1900; day.tm_mon--; struct excp *exc = mem_malloc(sizeof(struct excp)); exc->st = mktime(&day); LLIST_ADD(lexc, exc); } ungetc(c, data_file); } /* * Look in the appointment list if we have an item which starts after start and * before the item stored in the notify_app structure (which is the next item * to be notified). Note, the search may change the notify_app structure. */ void recur_apoint_check_next(struct notify_app *app, time_t start, time_t day) { llist_item_t *i; time_t real_recur_start_time; LLIST_TS_LOCK(&recur_alist_p); LLIST_TS_FOREACH(&recur_alist_p, i) { struct recur_apoint *rapt = LLIST_TS_GET_DATA(i); /* Tomorrow? */ if (recur_apoint_find_occurrence (rapt, day + DAYINSEC, &real_recur_start_time) && real_recur_start_time > start && real_recur_start_time < app->time) { app->time = real_recur_start_time; app->txt = mem_strdup(rapt->mesg); app->state = rapt->state; app->got_app = 1; } /* Today? */ if (recur_apoint_find_occurrence (rapt, day, &real_recur_start_time) && real_recur_start_time > start && real_recur_start_time < app->time) { app->time = real_recur_start_time; app->txt = mem_strdup(rapt->mesg); app->state = rapt->state; app->got_app = 1; } } LLIST_TS_UNLOCK(&recur_alist_p); } /* Switch recurrent item notification state. */ void recur_apoint_switch_notify(struct recur_apoint *rapt) { LLIST_TS_LOCK(&recur_alist_p); rapt->state ^= APOINT_NOTIFY; if (notify_bar()) notify_check_repeated(rapt); LLIST_TS_UNLOCK(&recur_alist_p); } void recur_event_paste_item(struct recur_event *rev, time_t date) { long time_shift; llist_item_t *i; time_shift = date - rev->day; rev->day += time_shift; if (rev->rpt->until != 0) rev->rpt->until += time_shift; LLIST_FOREACH(&rev->exc, i) { struct excp *exc = LLIST_GET_DATA(i); exc->st += time_shift; } LLIST_ADD_SORTED(&recur_elist, rev, recur_event_cmp); } void recur_apoint_paste_item(struct recur_apoint *rapt, time_t date) { time_t ostart = rapt->start; int days; llist_item_t *i; struct tm t; localtime_r((time_t *)&rapt->start, &t); rapt->start = update_time_in_date(date, t.tm_hour, t.tm_min); /* The number of days shifted. */ days = (rapt->start - ostart) / DAYINSEC; if (rapt->rpt->until != 0) rapt->rpt->until = date_sec_change(rapt->rpt->until, 0, days); LLIST_FOREACH(&rapt->exc, i) { struct excp *exc = LLIST_GET_DATA(i); exc->st = date_sec_change(exc->st, 0, days); } LLIST_TS_LOCK(&recur_alist_p); LLIST_TS_ADD_SORTED(&recur_alist_p, rapt, recur_apoint_cmp); LLIST_TS_UNLOCK(&recur_alist_p); if (notify_bar()) notify_check_repeated(rapt); } /* * Finds the next occurrence of a recurrent item and returns it in the provided * buffer. Useful for test of a repeated item. */ int recur_next_occurrence(time_t s, long d, struct rpt *r, llist_t *e, time_t day, time_t *next) { int ret = 0; if (r->until && r->until <= day) return ret; while (!r->until || day < r->until) { day = NEXTDAY(day); if (!check_sec(&day)) break; if (recur_item_find_occurrence(s, d, r, e, day, next)) { /* Multi-day appointment. */ if (*next < day) continue; ret = 1; break; } } return ret; } /* * Finds the nth occurrence (incl. start) of a recurrence rule (s, d, r, e) * and returns it in the provided buffer. */ int recur_nth_occurrence(time_t s, long d, struct rpt *r, llist_t *e, int n, time_t *nth) { time_t day; if (n <= 0) return 0; for (n--, *nth = s; n > 0; n--) { day = update_time_in_date(*nth, 0, 0); if (!recur_next_occurrence(s, d, r, e, day, nth)) break; } return !n; }