When the order of a weekday in BYDAY rule expansion (like -5SA or 5SU for monthly or 55WE for yearly) exceeds the number of available weekdays in the period (month or year), rule expansion with negative order could result in a floating point exception. The reason: the modified frequency might become zero. Solution. Check order against number of available weekdays and terminate expansion early whenever possible (also for positive orders). Signed-off-by: Lars Henriksen <LarsHenriksen@get2net.dk> Signed-off-by: Lukas Fleischer <lfleischer@calcurse.org>
1822 lines
45 KiB
C
1822 lines
45 KiB
C
/*
|
|
* Calcurse - text-based organizer
|
|
*
|
|
* Copyright (c) 2004-2020 calcurse Development Team <misc@calcurse.org>
|
|
* 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 <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <sys/types.h>
|
|
#include <time.h>
|
|
|
|
#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))
|
|
return _("recurrence error: not on start day");
|
|
|
|
/* 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))
|
|
return _("recurrence error: not on start day");
|
|
|
|
/* 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 occur, time_t *next)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (r->until && r->until <= occur)
|
|
return ret;
|
|
|
|
while (!r->until || occur < r->until) {
|
|
occur = NEXTDAY(occur);
|
|
if (!check_sec(&occur))
|
|
break;
|
|
if (recur_item_find_occurrence(s, d, r, e, occur, next)) {
|
|
ret = 1;
|
|
break;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|