/* * Remind Me * --------- * A program to help you organise dates and events * and remind you when birthdays are happening. * * The datafile is called "remindme.txt" and the * syntax of each line in that file looks like this: * description-string, year/month/day, frequency * For example: * My birthday, 1975/4/16, every year * * The frequency can either be the word "once" or it * can be the word "every" followed by an optional number, * followed by the word "day", "week", "month" or "year". * Here's another example: * Meet the boss, 1998/8/14, every 2 weeks */ #include #include #include #include #include #include #include /* * Globally defined data: */ int base_year = 2000; /* base counting year is year 2000 */ int base_day = 6; /* January 1, 2000 is a Saturday */ char * day_names [] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" }; char * month_names [] = { "", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; char * day_header = "Mon Tue Wed Thu Fri Sat Sun"; char * month_string = "Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec"; int month_days [2][13] = { { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } }; char * every = "every"; char * freq_names [] = { "once", "day", "week", "month", "year" }; char * editor = "red"; /* * Function and structure definitions: */ typedef enum EventFreq { Once, Daily, Weekly, Monthly, Yearly } Freq; typedef struct DateObject Date; struct DateObject { int day, month, year; }; typedef struct ReminderObject * Reminder; struct ReminderObject { char * name; Date date; Freq freq; int rate; }; typedef struct DatabaseObject * Database; struct DatabaseObject { char * filename; int size; Reminder * list; }; typedef struct RemindMeObject * RemindMe; struct RemindMeObject { Database db; window win; textbox messages; textbox calendar; }; char * concat(char *str, char *fmt, ...); char * make_calendar(int year); Database load_database(char * filename); void save_database(Database db); char * check_database(Database db); RemindMe create_reminder_window(Database db); void set_messages(RemindMe rm, char *msg); /* * Reminder program: */ void main(int argc, char * argv[]) { /* Create a window, load the database, check messages. */ Database db; char * msg; RemindMe rm; char * filename = "remindme.txt"; if (argv[1] != NULL) filename = argv[1]; db = load_database(filename); msg = check_database(db); if (msg != NULL) { if (initapp(argc, argv) != 0) { rm = create_reminder_window(db); set_messages(rm, msg); free(msg); show(rm->win); mainloop(); } else { printf("%s", msg); } } } /* * String functions: */ char * concat(char *str, char *fmt, ...) { /* Append some information to a string */ va_list args; int length = 0; char buf[256]; va_start(args, fmt); if (str != NULL) { length = strlen(str); str = (char *) realloc(str, length+256); } else { str = (char *) malloc(256); str[0] = '\0'; } vsprintf(buf, fmt, args); strcat(str, buf); va_end(args); return str; } /* * Date functions: */ int is_leap_year(int year) { /* return 1 if year is a leap year, 0 otherwise */ if (year % 4 != 0) return 0; if (year % 400 == 0) return 1; if (year % 100 == 0) return 0; return 1; } int january_1st(int year) { /* Return a number from 1 to 7 (Monday to Sunday) which specifies what day January 1st was in the given year */ int y = base_year; int d = base_day; if (year < base_year) /* go backwards */ while (y > year) { y = y - 1; d = d - 1 - is_leap_year(y); d = (d + 7) % 7; } else /* go forwards */ while (y < year) { d = d + 1 + is_leap_year(y); d = d % 7; y = y + 1; } if (d == 0) d = 7; return d; } int date_weekday(Date date) { /* Return a number from 1 to 7 (Monday to Sunday) which corresponds to the given date's weekday */ int d, leap, month; leap = is_leap_year(date.year); d = january_1st(date.year)-1; for(month=1; month < date.month; month++) d += month_days[leap][month]; d += date.day; d %= 7; if (d == 0) d = 7; return d; } char * make_calendar(int year) { /* Return a string containing a calendar for a given year */ char *cal; int leap, jan_1st, start, end; int month, day, date, newline; cal = NULL; cal = concat(cal, "%5d ", year); cal = concat(cal, day_header); cal = concat(cal, "\n"); /* set up variables */ leap = is_leap_year(year); jan_1st = january_1st(year); start = 2 - jan_1st; month = 0; day = 1; newline = 1; /* generate the calendar */ for (month=1; month <= 12; month++) { end = month_days[leap][month]; for (date=start; date <= end; date++) { if (newline) { if ((date >= 1) && (date <= 7)) cal = concat(cal, " %3.3s ", month_names[month]); else cal = concat(cal, " "); newline = 0; } if (date < 1) cal = concat(cal, " "); else cal = concat(cal, "%3d ", date); day = day + 1; if (day > 7) { day = 1; cal = concat(cal, "\n"); newline = 1; } } start = 1; } /* end of calendar */ if ((day > 1) && (day < 8)) { for (end=0; end < 8 - day; end++) cal = concat(cal, " "); cal = concat(cal, "\n"); } /* return the calendar string */ return cal; } Date current_date(void) { /* Return the current date as a Date object */ Date today; time_t t; struct tm *tmp; time(&t); tmp = localtime(&t); today.day = tmp->tm_mday; /* 1 to 31 */ today.month = tmp->tm_mon + 1; /* 1 to 12 */ today.year = tmp->tm_year + 1900; /* 0 to 32000 */ return today; } Date next_day(Date date) { /* Return the given date plus one day */ int leap = is_leap_year(date.year); int maxdays = month_days[leap][date.month]; date.day += 1; if (date.day > maxdays) { date.day -= maxdays; date.month += 1; } if (date.month > 12) { date.month -= 12; date.year += 1; } return date; } Date next_week(Date date) { /* Return the given date plus one week */ int leap = is_leap_year(date.year); int maxdays = month_days[leap][date.month]; date.day += 7; if (date.day > maxdays) { date.day -= maxdays; date.month += 1; } if (date.month > 12) { date.month -= 12; date.year += 1; } return date; } int compare_dates(Date d1, Date d2) { /* Return -1 if d1d2 */ if (d1.year < d2.year) return -1; if (d1.year > d2.year) return +1; if (d1.month < d2.month) return -1; if (d1.month > d2.month) return +1; if (d1.day < d2.day) return -1; if (d1.day > d2.day) return +1; return 0; } int compare_event_dates(Date d1, Date d2, Freq freq, int rate) { /* Compare the event date d1 with the current date d2 returning 0 if they match, -1 or +1 if not */ int i; switch (freq) { case Once: break; case Daily: while (compare_dates(d1,d2) < 0) { for (i=0; i < rate; i++) d1 = next_day(d1); } break; case Weekly: while (compare_dates(d1,d2) < 0) { for (i=0; i < rate; i++) d1 = next_week(d1); } break; case Monthly: if (d1.month % rate == d2.month % rate) { d1.month = d2.month; d1.year = d2.year; } break; case Yearly: if (d1.year % rate == d2.year % rate) d1.year = d2.year; break; } return compare_dates(d1,d2); } /* * Database functions: */ Reminder str_to_rem(char *line) { /* scan "My birthday, 1970/1/17, every year" or "Party, 2000/1/1, every 2 weeks" */ Reminder rem; int i; /* scan from the end of the line for the two commas */ for (i=0; line[i] != '\0'; i++) continue; for(; i >= 0; i--) if (line[i] == ',') /* last comma */ break; for (--i; i >= 0; i--) if (line[i] == ',') /* second last */ break; if ((i < 0) || (line[i] != ',')) /* some error */ return NULL; line[i] = '\0'; rem = (Reminder) malloc(sizeof(struct ReminderObject)); if (rem == NULL) return rem; /* read the name of the event, up to a comma */ rem->name = strdup(line); line += i+1; /* read the date, Year/Month/Day, slash-separated */ rem->date.year = atoi(line); for (; line[0] != '\0'; line++) if (line[0] == '/') { line++; break; } rem->date.month = atoi(line); for (; line[0] != '\0'; line++) if (line[0] == '/') { line++; break; } rem->date.day = atoi(line); /* read up to a comma, then skip spaces, then read either the word "every" or "once" */ for (; line[0] != '\0'; line++) if (line[0] == ',') { line++; break; } for (; line[0] != '\0'; line++) if (line[0] != ' ') break; if (strncmp(line, every, 5) == 0) { /* every */ line += 5; /* find repeat rate, if it's there */ rem->rate = atoi(line); if (rem->rate == 0) rem->rate = 1; /* skip number and spaces */ for (; line[0] != '\0'; line++) if (isalpha(line[0])) break; for (i=1; i<5; i++) if (strncmp(line, freq_names[i], 3) == 0) { rem->freq = (Freq) i; break; } } else { /* once */ rem->freq = Once; rem->rate = 0; } return rem; } void add_reminder(Database db, Reminder rem) { if (rem == NULL) return; db->list = (Reminder *) realloc(db->list, (db->size + 1) * sizeof(Reminder)); db->list[db->size] = rem; db->size = db->size + 1; } Database load_database(char *filename) { /* Load the database of events into memory */ Database db; FILE *f; char line[256]; db = (Database) malloc(sizeof(struct DatabaseObject)); if (db == NULL) return db; db->filename = strdup(filename); db->size = 0; db->list = NULL; f = fopen(filename, "r"); if (f != NULL) { while (fgets(line, sizeof(line), f) != NULL) add_reminder(db, str_to_rem(line)); fclose(f); } return db; } void del_database(Database db) { int i; for (i=0; i < db->size; i++) free(db->list[i]); free(db->list); free(db); } void print_rem(FILE *f, Reminder rem) { if (rem == NULL) return; fprintf(f, "%s, ", rem->name); fprintf(f, "%d/%d/%d, ", rem->date.year, rem->date.month, rem->date.day); if (rem->freq == Once) fprintf(f, "%s", freq_names[0]); else { fprintf(f, "%s ", every); if (rem->rate > 1) { fprintf(f, "%d ", rem->rate); fprintf(f, "%ss", freq_names[rem->freq]); } else { fprintf(f, "%s", freq_names[rem->freq]); } } fprintf(f, "\n"); } void save_database(Database db) { /* Save the modified database back to disk */ int i; FILE *f; f = fopen(db->filename, "w"); if (f != NULL) { for (i=0; i < db->size; i++) print_rem(f, db->list[i]); fclose(f); } } char * check_database(Database db) { char *msg; int i, x, d, weekday, found; Date date; Reminder rem; Date dates[14]; char * names[14]; msg = NULL; dates[0] = current_date(); for (d=1; d < 14; d++) dates[d] = next_day(dates[d-1]); weekday = date_weekday(dates[0]); names[0] = concat(NULL, "Today (%s %s %d)", day_names[weekday%7], month_names[dates[0].month], dates[0].day); names[1] = concat(NULL, "Tomorrow (%s %s %d)", day_names[(weekday+1)%7], month_names[dates[1].month], dates[1].day); for (d=2; d < 8; d++) { names[d] = concat(NULL, "%s (%s %d)", day_names[(weekday+d)%7], month_names[dates[d].month], dates[d].day); } for (d=8; d < 14; d++) { names[d] = concat(NULL, "%s Week (%s %d)", day_names[(weekday+d)%7], month_names[dates[d].month], dates[d].day); } for (d=0; d < 14; d++) { for (found=i=0; i < db->size; i++) { rem = db->list[i]; if (rem == NULL) continue; if (compare_event_dates(rem->date, dates[d], rem->freq, rem->rate) == 0) { if (! found) { msg = concat(msg, "%s\n", names[d]); for (x=strlen(names[d]); x>0; x--) msg = concat(msg, "-"); msg = concat(msg, "\n"); } found = 1; msg = concat(msg, "%s", rem->name); if (rem->freq == Yearly) msg = concat(msg, " (%d)", dates[d].year - rem->date.year); msg = concat(msg, "\n"); } } if (found) msg = concat(msg, "\n"); free(names[d]); } return msg; } /* * Interface: */ void resize_reminder_window(window w, rect r) { RemindMe rm = (RemindMe) getdata(w); r.x = r.y = 10; r.width -= 20; r.width /= 2; r.width -= 5; r.height -= 20; resize(rm->messages, r); r.x += r.width+10; resize(rm->calendar, r); } void edit_database(menuitem mi) { RemindMe rm = (RemindMe) getdata(parentwindow(mi)); char *cmd = NULL; if (rm && (rm->db) && (rm->db->filename)) { cmd = concat(NULL, "%s %s", editor, rm->db->filename); execapp(cmd); free(cmd); } } void rescan_database(menuitem mi) { RemindMe rm = (RemindMe) getdata(parentwindow(mi)); Database prev; settext(rm->messages, ""); prev = rm->db; rm->db = load_database(prev->filename); settext(rm->messages, check_database(rm->db)); selecttext(rm->messages, 0, 0); del_database(prev); } void exit_program(menuitem mi) { exitapp(); } RemindMe create_reminder_window(Database db) { /* Create a window to view the events */ RemindMe rm; rect r; rm = (RemindMe) malloc(sizeof(struct RemindMeObject)); rm->db =db; r = rect(0,20,640,420); rm->win = newwindow("Remind Me", r, StandardWindow); setdata(rm->win, rm); setbackground(rm->win, LightGrey); newmenu("File"); newmenuitem("Edit Database", 'E', edit_database); newmenuitem("Rescan Database", 'R', rescan_database); newmenuitem("-", 0, NULL); newmenuitem("Exit", 'Q', exit_program); r.x = r.y = 10; r.width -= 20; r.width /= 2; r.width -= 5; r.height -= 20; rm->messages = newtextarea(NULL, r); settextfont(rm->messages, Courier); r.x += r.width+10; rm->calendar = newtextarea(NULL, r); settextfont(rm->calendar, Courier); setresize(rm->win, resize_reminder_window); return rm; } void set_messages(RemindMe rm, char *msg) { Date today = current_date(); char *text; textbox t; t = rm->messages; settext(t, msg); selecttext(t, 0, 0); t = rm->calendar; inserttext(t, (text = make_calendar(today.year))); free(text); inserttext(t, "\n\n"); inserttext(t, (text = make_calendar(today.year+1))); free(text); selecttext(t, 0, 0); }