/* pal * * Copyright (C) 2004, Scott Kuhl * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include #include #include "main.h" #include "event.h" PalEvent* pal_event_init() { PalEvent* event = g_malloc(sizeof(PalEvent)); event->text = NULL; event->type = NULL; event->start_date = NULL; event->end_date = NULL; event->date_string = NULL; event->start_time = NULL; event->file_name = NULL; return event; } PalEvent* pal_event_copy(PalEvent* orig) { PalEvent* new = g_malloc(sizeof(PalEvent)); new->text = g_strdup(orig->text); new->start = orig->start; new->end = orig->end; new->hide = orig->hide; new->type = g_strdup(orig->type); new->file_num = orig->file_num; new->file_name = g_strdup(orig->file_name); new->color = orig->color; if(orig->start_date == NULL) new->start_date = NULL; else { new->start_date = g_malloc(sizeof(GDate)); memcpy(new->start_date, orig->start_date, sizeof(GDate)); } if(orig->end_date == NULL) new->end_date = NULL; else { new->end_date = g_malloc(sizeof(GDate)); memcpy(new->end_date, orig->end_date, sizeof(GDate)); } if(orig->start_time == NULL) new->start_time = NULL; else { new->start_time = g_malloc(sizeof(PalTime)); memcpy(new->start_time, orig->start_time, sizeof(PalTime)); } new->is_todo = orig->is_todo; new->date_string = g_strdup(orig->date_string); new->global = orig->global; return new; } void pal_event_free(PalEvent* event) { if(event == NULL) return; if(event->text != NULL) g_free(event->text); if(event->type != NULL) g_free(event->type); if(event->start_date != NULL) g_date_free(event->start_date); if(event->end_date != NULL) g_date_free(event->end_date); if(event->date_string != NULL) g_free(event->date_string); if(event->start_time != NULL) g_free(event->start_time); if(event->file_name != NULL) g_free(event->file_name); g_free(event); event = NULL; return; } /* Fills in start_date and end_date in pal_event by looking at * date_string. Returns the key for the event */ void pal_event_fill_dates(PalEvent* pal_event, const gchar* date_string) { gchar** s; s = g_strsplit(date_string, ":", 3); if(s[1] != NULL && s[2] != NULL) { GDate* d2 = get_date(s[1]); GDate* d3 = get_date(s[2]); if(d2 != NULL && d3 != NULL) { pal_event->start_date = get_date(s[1]); pal_event->end_date = get_date(s[2]); } } g_strfreev(s); } gchar* pal_event_date_string_to_key(const gchar* date_string) { gchar** s; gchar* r; s = g_strsplit(date_string, ":", 3); r = g_strdup(s[0]); g_strfreev(s); return r; } /* the recur variable indicates how many :yyyymmdd's may follow the * current one */ gboolean is_valid_yyyymmdd(const gchar* date_string, int recur) { gint d[8]; /* if key is regular event in the form: single day, monthly, yearly, yearly/monthly */ if(sscanf(date_string, "%1d%1d%1d%1d%1d%1d%1d%1d", &d[0],&d[1],&d[2],&d[3],&d[4],&d[5],&d[6],&d[7]) == 8) { gint year, month, day; year = d[0] * 1000 + d[1] * 100 + d[2] * 10 + d[3]; month = d[4] * 10 + d[5]; day = d[6] * 10 + d[7]; if(day < 1 || day > 31 || month < 1 || month > 12 || year < 1) return FALSE; /* FIXME: Don't allow one-time events on leap day in years * that leap day doesn't exist */ if(month == 2 && day > 29) return FALSE; /* 30 days in april, june, sept, nov */ if((month == 4 || month == 6 || month == 9 || month == 11) && day > 30) return FALSE; /* make sure there weren't more characters that sscanf didn't see */ if(strlen(date_string) == 8) return TRUE; if(date_string[8] == ':' && recur != 0) return is_valid_yyyymmdd(&(date_string[9]), recur-1); } return FALSE; } gboolean is_valid_000000dd(const gchar* date_string) { gint d[8]; if(sscanf(date_string, "000000%1d%1d", &d[6],&d[7]) == 2) { int day = d[6] * 10 + d[7]; if(day < 1 || day > 31) return FALSE; if(strlen(date_string) == 8) return TRUE; if(date_string[8] == ':') return is_valid_yyyymmdd(&(date_string[9]), 2); } return FALSE; } gboolean is_valid_0000mmdd(const gchar* date_string) { gint d[8]; if(sscanf(date_string, "0000%1d%1d%1d%1d", &d[4],&d[5],&d[6],&d[7]) == 4) { gint month, day; day = d[6] * 10 + d[7]; month = d[4] * 10 + d[5]; if(day < 1 || day > 31 || month < 1 || month > 12) return FALSE; /* FIXME: Don't allow one-time events on leap day in years * that leap day doesn't exist */ if(month == 2 && day > 29) return FALSE; /* 30 days in april, june, sept, nov */ if((month == 4 || month == 6 || month == 9 || month == 11) && day > 30) return FALSE; if(strlen(date_string) == 8) return TRUE; else if(date_string[8] == ':') return is_valid_yyyymmdd(&(date_string[9]), 2); } return FALSE; } gboolean is_valid_star_mmnd(const gchar* date_string) { gint d[8]; if(sscanf(date_string, "*%1d%1d%1d%1d", &d[0],&d[1],&d[2],&d[3]) == 4) /* nth weekday of month */ { gint month, n, weekday; month = d[0] * 10 + d[1]; n = d[2]; weekday = d[3]; if(weekday > 0 && weekday < 8 && month >= 0 && month < 13 && n > 0 && n < 6) /* no more than 5 weeks in a month */ { if(strlen(date_string) == 5) return TRUE; else if(date_string[5] == ':') return is_valid_yyyymmdd(&(date_string[6]), 2); } } return FALSE; } gboolean is_valid_star_mmLd(const gchar* date_string) { gint d[8]; if(sscanf(date_string, "*%1d%1dL%1d", &d[0],&d[1],&d[2]) == 3) /* last weekday of month */ { gint month, weekday; month = d[0] * 10 + d[1]; weekday = d[2]; if(weekday > 0 && weekday < 8 && month >= 0 && month < 13 ) { if(strlen(date_string) == 5) return TRUE; else if(date_string[5] == ':') return is_valid_yyyymmdd(&(date_string[6]), 2); } } return FALSE; } gboolean is_valid_EASTER(const gchar* date_string) { if(strncmp(date_string, "EASTER", 6) == 0) { if(strlen(date_string) == 6) return TRUE; if(date_string[6] == ':') return is_valid_yyyymmdd(&(date_string[7]), 2); if(date_string[6] == '-' || date_string[6] == '+') { if(g_ascii_isdigit(date_string[7]) && g_ascii_isdigit(date_string[8]) && g_ascii_isdigit(date_string[9])) { if(date_string[10] == ':') return is_valid_yyyymmdd(&(date_string[11]), 2); else if(date_string[10] == '\0') return TRUE; } } } return FALSE; } gboolean is_valid_date_string(const gchar* match_string, const gchar* date_string) { if(strncmp(match_string, date_string, strlen(match_string)) == 0) { if(strlen(date_string) == strlen(match_string)) return TRUE; else if(date_string[strlen(match_string)] == ':') return is_valid_yyyymmdd(&(date_string[strlen(match_string)+1]), 2); } return FALSE; } /* checks if date_string is a valid date string. Before calling this * function, g_strstrip needs to be called on date_string! g_ascii_strup * should also be called on the date_string. */ gboolean is_valid_key(const gchar* date_string) { if(is_valid_000000dd(date_string) || is_valid_0000mmdd(date_string) || is_valid_yyyymmdd(date_string, 0) || is_valid_star_mmnd(date_string) || is_valid_star_mmLd(date_string) || is_valid_EASTER(date_string) || is_valid_date_string("TODO", date_string) || is_valid_date_string("DAILY", date_string) || is_valid_date_string("MON", date_string) || is_valid_date_string("TUE", date_string) || is_valid_date_string("WED", date_string) || is_valid_date_string("THU", date_string) || is_valid_date_string("FRI", date_string) || is_valid_date_string("SAT", date_string) || is_valid_date_string("SUN", date_string)) return TRUE; return FALSE; } GDate* find_easter(gint year) { gint a,b,c,d,e,f,g,h,i,k,l,m,p,month,day; a = year%19; b = year/100; c = year%100; d = b/4; e = b%4; f = (b+8) / 25; g = (b-f+1) / 3; h = (19*a+b-d-g+15) % 30; i = c/4; k = c%4; l = (32+2*e+2*i-h-k)%7; m = (a+11*h+22*l) / 451; month = (h+l-7*m+114) / 31; p = (h+l-7*m+114) % 31; day = p+1; return g_date_new_dmy((GDateDay) day, (GDateMonth) month, (GDateYear) year); } gchar* get_easter_key(const GDate* date) { gchar* key = g_malloc(sizeof(gchar)*12); GDate* easter = find_easter(g_date_get_year(date)); gint diff = g_date_days_between(date,easter); g_date_free(easter); if(diff != 0) snprintf(key, 12, "EASTER%c%03d", (diff > 0) ? '-' : '+', (diff > 0) ? diff : -diff); else snprintf(key, 12, "EASTER"); return key; } /* gets the yyyymmdd key for the given date. The returned string * should be freed. */ gchar* get_key(const GDate* date) { gchar* key = g_malloc(sizeof(gchar)*9); snprintf(key, 9, "%04d%02d%02d", g_date_get_year(date), g_date_get_month(date), g_date_get_day(date)); return key; } /* this only works on dates in yyyymmdd format. It ignores everything * after the 8th character. The returned GDate object should be * freed. Returns NULL on failure*/ GDate* get_date(const gchar* key) { GDate* date = NULL; gint year, month, day; sscanf(key, "%04d%02d%02d", &year, &month, &day); if(g_date_valid_dmy((GDateDay) day, (GDateMonth) month, (GDateYear) year)) date = g_date_new_dmy((GDateDay) day, (GDateMonth) month, (GDateYear) year); return date; } gboolean last_weekday_of_month(const GDate* date) { GDate* local = g_memdup(date,sizeof(GDate)); g_date_add_days(local,7); if(g_date_get_month(local) != g_date_get_month(date)) { g_date_free(local); return TRUE; } g_date_free(local); return FALSE; } /* Returns n from date --- as in: "date" is the "n"th * sunday/monday/... of the month */ gint get_nth_day(const GDate* date) { int i = (g_date_get_day(date) / 7) + 1; if(g_date_get_day(date) % 7 == 0) i--; return i; } /* removes events from the list whose range does not include "date". * Returns the beginning of the new list (it might have changed) */ GList* inspect_range(GList* list, const GDate* date) { GList* item = list; if(list == NULL) return list; while(g_list_length(item) > 0) { if(((PalEvent*) item->data)->start_date != NULL && ((PalEvent*) item->data)->end_date != NULL) { if(g_date_days_between(date, ((PalEvent*) item->data)->start_date) > 0 || g_date_days_between(date, ((PalEvent*) item->data)->end_date) < 0) { /* if not on last item */ if(g_list_length(item) > 1) { /* save reference to next data item */ gpointer n = g_list_next(item)->data; /* remove this list element */ list = g_list_remove(list, item->data); /* find what we need to look at next */ item = g_list_find(list, n); } else /* we're on last item */ { list = g_list_remove(list, item->data); item = g_list_last(list); } } else item = g_list_next(item); } else item = g_list_next(item); } return list; } gint pal_event_sort_fn(gconstpointer x, gconstpointer y) { PalEvent* a = (PalEvent*) x; PalEvent* b = (PalEvent*) y; /* Put events with start times before events without start times */ if(a->start_time != NULL && b->start_time == NULL) return -1; if(a->start_time == NULL && b->start_time != NULL) return 1; /* if both events have start times, sort by start time */ if(a->start_time != NULL && b->start_time != NULL) { if(a->start_time->hour < b->start_time->hour) return -1; if(a->start_time->hour > b->start_time->hour) return 1; /* if we get here, the hours are the same */ if(a->start_time->min < b->start_time->min) return -1; if(a->start_time->min > b->start_time->min) return 1; return 0; } /* if neither event has start times, sort by order in pal.conf */ if(a->file_num == b->file_num) return 0; if(a->file_num < b->file_num) return -1; else return 1; } GList* pal_event_sort_events(GList* events) { if(events == NULL) return NULL; return g_list_sort(events, pal_event_sort_fn); } /* Returns a list of events on the given date. The returned list is sorted. */ GList* get_events(const GDate* date) { GList* list = NULL; gchar* key = get_key(date); gchar* new_key; GList* days_events; gint weekday; /* get easter related events */ gchar* easter_key = get_easter_key(date); days_events = g_hash_table_lookup(ht, easter_key); g_free(easter_key); if(days_events != NULL) list = g_list_concat(list, g_list_copy(days_events)); /* get events that happen only on that day */ days_events = g_hash_table_lookup(ht,key); if(days_events != NULL) list = g_list_concat(list, g_list_copy(days_events)); /* get days that always happen *today* (whatever that is) */ { GDate* today = g_date_new(); g_date_set_time(today, time(NULL)); if(g_date_days_between(today, date) == 0) { days_events = g_hash_table_lookup(ht,"TODO"); if(days_events != NULL) list = g_list_concat(list, g_list_copy(days_events)); } g_date_free(today); } /* weekly events */ switch(g_date_get_weekday(date)) { case 1: days_events = g_hash_table_lookup(ht, "MON"); break; case 2: days_events = g_hash_table_lookup(ht, "TUE"); break; case 3: days_events = g_hash_table_lookup(ht, "WED"); break; case 4: days_events = g_hash_table_lookup(ht, "THU"); break; case 5: days_events = g_hash_table_lookup(ht, "FRI"); break; case 6: days_events = g_hash_table_lookup(ht, "SAT"); break; case 7: days_events = g_hash_table_lookup(ht, "SUN"); break; default: days_events = NULL; /*impossible*/ } if(days_events != NULL) list = g_list_concat(list, g_list_copy(days_events)); /* daily event */ days_events = g_hash_table_lookup(ht, "DAILY"); if(days_events != NULL) list = g_list_concat(list, g_list_copy(days_events)); /* get events that happen on that day every year */ new_key = g_strdup(key); *new_key = '0'; *(new_key+1) = '0'; *(new_key+2) = '0'; *(new_key+3) = '0'; days_events = g_hash_table_lookup(ht,new_key); if(days_events != NULL) list = g_list_concat(list, g_list_copy(days_events)); g_free(new_key); /* get events that happen every month, every year */ new_key = g_strdup(key); *new_key = '0'; *(new_key+1) = '0'; *(new_key+2) = '0'; *(new_key+3) = '0'; *(new_key+4) = '0'; *(new_key+5) = '0'; days_events = g_hash_table_lookup(ht,new_key); if(days_events != NULL) list = g_list_concat(list, g_list_copy(days_events)); g_free(new_key); /* convert weekday to friendly weekday from: 1(mon) -> 7(sun) to: 1(sun) -> 7(sat) */ weekday = g_date_get_weekday(date); if(weekday == 7) weekday = 1; else weekday++; /* get events that happen every year, on certain day/week */ new_key = g_strdup(key); snprintf(new_key, 9, "*%02d%01d%01d", g_date_get_month(date), get_nth_day(date), weekday); days_events = g_hash_table_lookup(ht,new_key); if(days_events != NULL) list = g_list_concat(list, g_list_copy(days_events)); g_free(new_key); /* get events that happen monthly, on certain day/week */ new_key = g_strdup(key); snprintf(new_key, 9, "*00%01d%01d", get_nth_day(date), weekday); days_events = g_hash_table_lookup(ht,new_key); if(days_events != NULL) list = g_list_concat(list, g_list_copy(days_events)); g_free(new_key); if(last_weekday_of_month(date)) { /* get events that happen every year, last *day of month */ new_key = g_strdup(key); snprintf(new_key, 9, "*%02dL%01d", g_date_get_month(date), weekday); days_events = g_hash_table_lookup(ht,new_key); if(days_events != NULL) list = g_list_concat(list, g_list_copy(days_events)); g_free(new_key); /* get events that happen every month, last *day of month */ new_key = g_strdup(key); snprintf(new_key, 9, "*00L%01d", weekday); days_events = g_hash_table_lookup(ht,new_key); if(days_events != NULL) list = g_list_concat(list, g_list_copy(days_events)); g_free(new_key); } list = inspect_range(list, date); list = pal_event_sort_events(list); g_free(key); return list; } /* the returned string should be freed */ gchar* pal_event_escape(const PalEvent* event, const GDate* today) { gchar* in = event->text; gchar* out_string = g_malloc(sizeof(gchar)*strlen(event->text)*2); gchar* out = out_string; while(*in != '\0') { if( *in == '!' && strlen(in) > 5 && g_ascii_isdigit(*(in+1)) && g_ascii_isdigit(*(in+2)) && g_ascii_isdigit(*(in+3)) && g_ascii_isdigit(*(in+4)) && *(in+5) == '!') { int diff; int now = g_date_get_year(today); int event = g_ascii_digit_value(*(in+1)); event *= 10; event += g_ascii_digit_value(*(in+2)); event *= 10; event += g_ascii_digit_value(*(in+3)); event *= 10; event += g_ascii_digit_value(*(in+4)); diff = now-event; out += sprintf(out, "%i", diff); in += 6; } else { *out = *in; out++; in++; } } *out = '\0'; return out_string; }