summaryrefslogblamecommitdiffstats
path: root/src/recur.c
blob: 54c43ba1538d34429745b0101905b58014535895 (plain) (tree)
1
2
3
4
5
6
7
8
9

                                  
  
                                                                        
                       
  


                                                                     
  



















                                                                        
  
                                                        
                                           


   

                   
                  


                      
                     
                 
 

                         
 
                                      
 
                      
 
 
                                        
 

                                        
 
 
                                                      
 
                                                             

 
                                                    
 

                                                         
 
                                              

 
                                                
 
                        
 
                       
 





                                                           

 





















                                                                              
                                              
 
                        






















                                                
                    

                             
                       

 
                                                           
 
                                        
 
                                                                         
 


                                         
 



                                                  
 
                                     
 



                                                 
 
                   

 
                                                              
 
                                        
 

                                                    
 



                                          
 



                                                   
 
                                      
 



                                                  
 
                    
 
 
                                  
 
                                      

 




                                 
                                                 
 






                                     
 
 
                                              
 






                                    

 
                                  
 

                                                               

 
                                 
 

                                                         
 
 
          
                                                                
 









                                                                      

 
                                                                        
 





                                        

 
                                                                   
                                                                           
                                                                     
                                                             





















                                                           
                                                                    


                                        


                                                             

                                                                             
                                                     
 
                                                                         
 













                                                          
 
                                                             
 
                   

 


                                                        
   
                                           
 




















                                                   

 
  


                                                                     
                             
 



















                                             

 
                                                                  
                                                             
 



                                    
 






                                                     
                                                                            
         

 
                                                


                                                                          

                                                                  


                                    

                                         



























                                                                          

                                  










                                                                 

                          
















                                                                                
 



                                                                             
                 
         



                                                                          

                    


                                         
                                                                       
                                                                          

                                                                

                              
                                    

                                       





























                                                                           
                                     
 

                          















                                                                                
 



                                                                             
                 
         



                                                            

                   

 
                                                
 
                        


                     

                        

                             

                                                                      


                              
                                                                        


                                                                      


                                                               


                                                      
                                                                         


                                                                    



                                                 
                                     
                                           
            



                                           

 









                                                        
                                                         
 







                                              




                                       

                        





                                    


                                                                            






                                                      
                                                                              



                                                                       








                                                 









                                                        




                                                       

 
                                    
                              
 
                        
 



                                                            
 





                                                              

 
  


                                                                             
   
                                                                         


                                                                         

                                                                         
 
                                                         
                                                           
 
                  
 

                                              
 
                                                 
 




                                                                         
 
                    

 
                                                           
                                                             
 
                  
 

                                              
 

                                                                   
 
                    


                                                          
                                                            
 
                                                 

 
                                                         
 
                                                        

 
  

                                                                             
  
                                                     
                                                                             

                                                                         
   
        
                                                            
                                                                          
                                                              
                                              
 









                                                                           
                                               
                                   
 
                                                               
 
                                       

                         


                                                                            
 


                                                                       
 


                                                                              







                                                                 

                                                       





                                                                   

                                                                  




                                                                  

                                                          
                                                           

                                                            





                                                            


                                             
 





                                                                             
                                                




                                                           
                                                        

                         

                                                   

                         

                                            
                         
 

                                  

                 
               

 
        
                                                                         
                                                
 




                                                                      


        
                                                                      
                                               
 
                                                                  


                                                                         



                                                            


                                                                      
 




                                                                               

 
                                                                         
 


                                                                   

 
                                                                      
 
                                                        

                                                               

 
                                            
                                                              
 
                                       


                                                  
                                                                 
 
                                  
 




                                                                 

 
  
                                                                        
                                                       
   
                                               
 
                                                                    
 

                                           
 
                                      



                                                                              
                                                             
   
                                                  
 
                                      
 

                                                                          
 

                                                 
 




                                                                 
 
                                        

 
  


                                                           
                                                     
 

                      
 






                                                                           
 
                                                                          
                                                           
 








                                                                   
 

  


                                                                               
   
                                                                              
 
                        
                                     
 
                                      
                                             
                                                                 
 










                                                                  

                                                       

                                                           






                                                          
 
 
                                               
                                                          
 
                                      
 


                                            
 
                                        
 
 
                                                                 
 

                        
 

                                     
 

                                              
 



                                                     
 
                                                             

 
                                                                    
 
                                    
                 
                        



                                                                     
 

                                                 
 
                                  
                                                                              
 

                                                     
                                                            
         
 
                                      
                                                                    
                                        
 

                                            
 
/*
 * 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_exc(struct excp *exc)
{
	mem_free(exc);
}

static void 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 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);
}

static void 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 the list of exceptions from a string of days. Any positive number of
 * spaces are allowed before, between and after the days.
 */
int recur_update_exc(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;
	}
	free_exc_list(exc);
	exc_dup(exc, &nexc);
	updated = 1;
cleanup:
	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));
	rev->rpt->type = in->rpt->type;
	rev->rpt->freq = in->rpt->freq;
	rev->rpt->until = in->rpt->until;

	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));
	rapt->rpt->type = in->rpt->type;
	rapt->rpt->freq = in->rpt->freq;
	rapt->rpt->until = in->rpt->until;

	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);
	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);
	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, int type,
				      int freq, time_t until,
				      llist_t * except)
{
	struct recur_apoint *rapt =
	    mem_malloc(sizeof(struct recur_apoint));

	rapt->rpt = mem_malloc(sizeof(struct rpt));
	rapt->mesg = mem_strdup(mesg);
	rapt->note = (note != NULL) ? mem_strdup(note) : 0;
	rapt->start = start;
	rapt->state = state;
	rapt->dur = dur;
	rapt->rpt->type = type;
	rapt->rpt->freq = freq;
	rapt->rpt->until = until;
	if (except) {
		exc_dup(&rapt->exc, except);
		free_exc_list(except);
	} else {
		LLIST_INIT(&rapt->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, int type, int freq, time_t until,
				    llist_t * except)
{
	struct recur_event *rev = mem_malloc(sizeof(struct recur_event));

	rev->rpt = mem_malloc(sizeof(struct rpt));
	rev->mesg = mem_strdup(mesg);
	rev->note = (note != NULL) ? mem_strdup(note) : 0;
	rev->day = day;
	rev->id = id;
	rev->rpt->type = type;
	rev->rpt->freq = freq;
	rev->rpt->until = until;
	if (except) {
		exc_dup(&rev->exc, except);
		free_exc_list(except);
	} else {
		LLIST_INIT(&rev->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:
		EXIT(_("unknown repetition type"));
		return 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 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, &lt);
		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 */
struct recur_apoint *recur_apoint_scan(FILE * f, struct tm start,
				       struct tm end, char type, int freq,
				       struct tm until, char *note,
				       llist_t * exc, char state,
				       struct item_filter *filter)
{
	char buf[BUFSIZ], *nl;
	time_t tstart, tend, tuntil;
	struct recur_apoint *rapt = NULL;
	int cond;

	EXIT_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) ||
		(until.tm_year != 0
		 && !check_date(until.tm_year, until.tm_mon,
				until.tm_mday)),
		_("date error in appointment"));

	/* Read the appointment description */
	if (!fgets(buf, sizeof buf, f))
		return NULL;

	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 (until.tm_year != 0) {
		until.tm_hour = 0;
		until.tm_min = 0;
		until.tm_sec = 0;
		until.tm_isdst = -1;
		until.tm_year -= 1900;
		until.tm_mon--;
		tuntil = mktime(&until);
	} else {
		tuntil = 0;
	}
	EXIT_IF(tstart == -1 || tend == -1 || tstart > tend
		|| tuntil == -1, _("date error in appointment"));

	/* 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,
						recur_char2def(type),
						freq, tuntil, exc);
			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, recur_char2def(type), freq,
					tuntil, exc);

	return rapt;
}

/* Load the recursive events from file */
struct recur_event *recur_event_scan(FILE * f, struct tm start, int id,
				     char type, int freq, struct tm until,
				     char *note, llist_t * exc,
				     struct item_filter *filter)
{
	char buf[BUFSIZ], *nl;
	time_t tstart, tend, tuntil;
	struct recur_event *rev = NULL;
	int cond;

	EXIT_IF(!check_date(start.tm_year, start.tm_mon, start.tm_mday) ||
		!check_time(start.tm_hour, start.tm_min) ||
		(until.tm_year != 0
		 && !check_date(until.tm_year, until.tm_mon,
				until.tm_mday)), _("date error in event"));

	/* Read the event description */
	if (!fgets(buf, sizeof buf, f))
		return NULL;

	nl = strchr(buf, '\n');
	if (nl) {
		*nl = '\0';
	}
	start.tm_hour = until.tm_hour = 0;
	start.tm_min = until.tm_min = 0;
	start.tm_sec = until.tm_sec = 0;
	start.tm_isdst = until.tm_isdst = -1;
	start.tm_year -= 1900;
	start.tm_mon--;
	if (until.tm_year != 0) {
		until.tm_year -= 1900;
		until.tm_mon--;
		tuntil = mktime(&until);
	} else {
		tuntil = 0;
	}
	tstart = mktime(&start);
	EXIT_IF(tstart == -1 || tuntil == -1, _("date error in event"));
	tend = tstart + DAYINSEC - 1;

	/* 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,
					      recur_char2def(type),
					      freq, tuntil, exc);
			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,
				      recur_char2def(type),
				      freq, tuntil, exc);

	return rev;
}

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, &lt);
	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, &lt);
	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, &lt);
		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);
	}
	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, &lt);
	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, &lt);
		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);
	}
	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);
}

/*
 * 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;
}

static int exc_inday(struct excp *exc, time_t *day_start)
{
	return (date_cmp_day(exc->st, *day_start) == 0);
}

/*
 * Check if the recurrent item belongs to the selected day, and if yes, store
 * the start date of the occurrence that belongs to the day in a buffer.
 *
 * This function was improved thanks to Tony's patch.
 * Thanks also to youshe for reporting daylight saving time related problems.
 * And finally thanks to Lukas for providing a patch to correct the wrong
 * calculation of recurrent dates after a turn of years.
 */
unsigned
recur_item_find_occurrence(time_t item_start, long item_dur,
			   llist_t * item_exc, int rpt_type, int rpt_freq,
			   time_t rpt_until, time_t day_start,
			   time_t *occurrence)
{
/*
 * Function-internal duration
 * 1) To avoid an item ending on midnight (which belongs to the next day),
 *    duration is always diminished by 1 second.
 * 2) An event has no explicit duration, but lasts for an entire day, which
 *    in turn depends on DST.
 */
#define ITEM_DUR(d) ((item_dur == -1 ? DAYLEN(d) : item_dur) - 1)

	long diff;
	struct tm lt_day, lt_item, lt_item_day;
	time_t occ, item_day_start;

	item_day_start = update_time_in_date(item_start, 0, 0);

	if (day_start < item_day_start)
		return 0;

	if (rpt_until && day_start >=
	    rpt_until + (item_start - item_day_start) + ITEM_DUR(rpt_until))
		return 0;

	localtime_r(&day_start, &lt_day);	/* selected day */
	localtime_r(&item_start, &lt_item);	/* first occurrence */
	lt_item_day = lt_item;			/* recent occurrence */

	/*
	 * Update to the most recent occurrence before or on the selected day.
	 */
	switch (rpt_type) {
	case RECUR_DAILY:
		diff = diff_days(lt_item_day, lt_day) % rpt_freq;
		lt_item_day.tm_mday = lt_day.tm_mday - diff;
		lt_item_day.tm_mon = lt_day.tm_mon;
		lt_item_day.tm_year = lt_day.tm_year;
		break;
	case RECUR_WEEKLY:
		diff = diff_days(lt_item_day, lt_day) %
			(rpt_freq * WEEKINDAYS);
		lt_item_day.tm_mday = lt_day.tm_mday - diff;
		lt_item_day.tm_mon = lt_day.tm_mon;
		lt_item_day.tm_year = lt_day.tm_year;
		break;
	case RECUR_MONTHLY:
		diff = diff_months(lt_item_day, lt_day) % rpt_freq;
		if (!diff && lt_day.tm_mday < lt_item_day.tm_mday)
			diff += rpt_freq;
		lt_item_day.tm_mon = lt_day.tm_mon - diff;
		lt_item_day.tm_year = lt_day.tm_year;
		break;
	case RECUR_YEARLY:
		diff = diff_years(lt_item_day, lt_day) % rpt_freq;
		if (!diff &&
		    (lt_day.tm_mon < lt_item_day.tm_mon ||
		    (lt_day.tm_mon == lt_item_day.tm_mon &&
		     lt_day.tm_mday < lt_item_day.tm_mday)))
			diff += rpt_freq;
		lt_item_day.tm_year = lt_day.tm_year - diff;
		break;
	default:
		EXIT(_("unknown item type"));
	}

	/* Switch to calendar (Unix) time. */
	lt_item_day.tm_isdst = -1;
	occ = mktime(&lt_item_day);

	/*
	 * 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) {
		localtime_r(&occ, &lt_item_day);
		if (lt_item_day.tm_mday != lt_item.tm_mday)
			return 0;
	}

	/* Exception day? */
	if (LLIST_FIND_FIRST(item_exc, &occ, exc_inday))
		return 0;

	/* After until day? */
	if (rpt_until && occ >= NEXTDAY(rpt_until))
		return 0;

	/* Does it span the selected day? */
	if (occ + ITEM_DUR(occ) < day_start)
		return 0;

	if (occurrence)
		*occurrence = occ;

	return 1;
#undef ITEM_DUR
}

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->exc, rapt->rpt->type,
					  rapt->rpt->freq,
					  rapt->rpt->until, 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->exc,
					  rev->rpt->type, rev->rpt->freq,
					  rev->rpt->until, day_start,
					  occurrence);
}

/* Check if a recurrent item belongs to the selected day. */
unsigned
recur_item_inday(time_t item_start, long item_dur, llist_t * item_exc,
		 int rpt_type, int rpt_freq, time_t rpt_until,
		 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(item_start, item_dur, item_exc,
					  rpt_type, rpt_freq, rpt_until,
					  day_start, NULL);
}

unsigned recur_apoint_inday(struct recur_apoint *rapt, time_t *day_start)
{
	return recur_item_inday(rapt->start, rapt->dur, &rapt->exc,
				rapt->rpt->type, rapt->rpt->freq,
				rapt->rpt->until, *day_start);
}

unsigned recur_event_inday(struct recur_event *rev, time_t *day_start)
{
	return recur_item_inday(rev->day, -1, &rev->exc,
				rev->rpt->type, rev->rpt->freq,
				rev->rpt->until, *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 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);
	}
}

/*
 * 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);
}