bday

view bday.c @ 9:4f48b4f86e3d

added COPYRIGHT and COPYING; adjusted copyright hints in the code
author meillo@marmaro.de
date Wed, 19 Dec 2007 01:19:57 +0100
parents 19c1ad697022
children a56120a4678f
line source
1 /*
2 bday
4 Birthday/Anniversary reminder
6 (c) 2007 markus schnalke <meillo@marmaro.de>
7 (c) 1994-1999 AS Mortimer
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License as
11 published by the Free Software Foundation; either version 2 of the
12 License, or (at your option) any later version. You may also
13 distribute it under the Artistic License, as comes with Perl.
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 You should also have recieved a copy of the Artistic license with
24 this program.
26 ===============================================================================
28 Input is read through standard input. For example: bday < ~/.birthdays
29 The input (file) has to have the following format:
31 text=date flags
33 where:
34 date is yyyy-mm-dd
35 flags is ONE or ZERO of
36 bd for a birthday (default)
37 ann for an anniversary
38 ev for an event
39 and zero or more of
40 w <n> to set the warn-in-advance time to n days (don't include the
41 brackets! :)
42 to <date>
43 for <days>
44 to specify the length of time taken by an event, for example a holiday.
46 Lines preceeded by # are treated as comments.
48 Note: If you deviate from this format, I cannot guarantee anything about
49 it's behaviour. In most cases, it will just quietly ignore the error,
50 which probably isn't ideal behaviour. Oh, well.
52 ===============================================================================
53 */
56 /* standard time to warn in advance, when no explicit w flag is given. */
57 #define DEF_WARN 14
60 #include <stdarg.h>
61 #include <stdio.h>
62 #include <stdlib.h>
63 #include <string.h>
64 #include <sys/types.h>
65 #include <time.h>
66 #include <unistd.h>
70 /* ========== Global constants and data types */
73 /* month lengths etc */
74 #define isleapyear(y) ((y)%4==0 && ((y)%100 != 0 || (y)%400 == 0))
75 const unsigned MLENDAT[];
76 #define mlen(m,y) (MLENDAT[(m)-1] != -1 ? MLENDAT[(m)-1] : (isleapyear((y)) ? 29 : 28))
77 #define before(a,b) ((a).month < (b).month || ((a).month == (b).month && (a).day < (b).day))
78 #define ydelta(a,b) ((int) (b).year - (a).year + before((a),(b)))
80 /* -------- modifier flags */
81 #define F_MTYPE 0x07
82 #define F_TBIRTHDAY 1
83 #define F_TANNIVERSARY 2
84 #define F_TEVENT 3
86 /* flags processed immediately on encountering */
87 #define F_MIMMEDIATE 0x24
88 #define F_WTIME_P 0x08
89 #define F_FORDAYS 0x16
90 #define F_TODATE 0x24
92 struct _ftable {char* txt; unsigned flag;};
94 const struct _ftable FTABLE[];
96 struct date {
97 unsigned day;
98 unsigned month;
99 unsigned year;
100 };
102 struct event {
103 char* text;
104 struct date date;
105 struct date enddate;
106 int warn;
107 };
109 typedef int (*prnfunc)(const char *);
111 /* ========== Global Variables */
113 struct event* readlist(void);
114 void gettoday(void);
115 unsigned delta(struct date*);
116 unsigned ddiff(struct date* D1, struct date* D2);
117 void liststrings(struct event* evl, prnfunc outf);
118 char* tdelta(struct date* d);
119 char* ttime(int yr, int mn, int wk, int dy);
120 int skptok(int j, char* ptr);
121 int evcmp(const void* e1, const void* e2);
124 struct date today;
125 int iDWarn = DEF_WARN;
127 const unsigned MLENDAT[]={31,-1,31,30,31,30,31,31,30,31,30,31};
129 const struct _ftable FTABLE[] = {
130 {"bd", F_TBIRTHDAY},
131 {"ann",F_TANNIVERSARY},
132 {"ev", F_TEVENT},
133 {"w", F_WTIME_P},
134 {"to", F_TODATE},
135 {"for", F_FORDAYS},
136 {NULL, 0}
137 };
144 /*
145 xmalloc/xrealloc functions
146 Note: the x* functions are lifted straight from the GNU libc info docs
147 $Id: xmalloc.c,v 1.2 1999/01/16 17:08:59 andy Exp $
148 */
150 void* xmalloc (size_t size) {
151 register void* value = malloc (size);
152 if (value == 0) {
153 fprintf(stderr, "virtual memory exhausted\n");
154 exit(1);
155 }
156 return value;
157 }
159 void* xrealloc (void* ptr, size_t size) {
160 register void* value = realloc (ptr, size);
161 if (value == 0) {
162 fprintf(stderr, "virtual memory exhausted\n");
163 exit(1);
164 }
165 return value;
166 }
169 /* ========== */
172 /* like strcat(), but lets the buffer automagically grow :-)
173 * (needs local variable "size" with the buffer size) */
174 #define append(where, what) do { \
175 if (strlen(what) > (size - strlen(where))) { \
176 xrealloc(where, size + 128 + strlen(what)); \
177 size += 128 + strlen(what); \
178 } \
179 strcat(where, what); \
180 } while(0)
182 /* ========== */
184 /* returns delta(d) in days, weeks, months, etc
185 * the returned buffer is malloc()ed, do not forget to free() it */
186 char* tdelta(struct date* d) {
187 int dy, wk, mn, yr;
188 char* tmp;
189 char* buf = xmalloc(128);
190 int size = 128;
191 *buf = 0;
193 switch (delta(d)) {
194 case 0:
195 append(buf, "today");
196 return buf;
197 case 1:
198 append(buf, "tomorrow");
199 return buf;
200 default:
201 /* like delta(), we ignore the year */
202 yr = -before(*d, today);
203 mn = d->month - today.month;
204 dy = d->day - today.day;
206 if (dy < 0) {
207 dy += mlen(today.month, today.year);
208 mn--;
209 }
210 if (mn < 0) {
211 mn += 12;
212 yr++;
213 }
215 wk = (dy / 7);
216 dy %= 7;
218 append(buf, "in ");
219 tmp = ttime(yr, mn, wk, dy);
220 append(buf, tmp);
221 free(tmp);
223 return buf;
224 }
225 }
231 /*
232 void donum(n,txt) {
233 do {
234 if (n > 0) {
235 snprintf(tmp, sizeof(tmp), "%d", n);
236 append(buf, tmp);
237 append(buf, " " txt);
238 if (n != 1)
239 append(buf, "s");
240 terms--;
241 if (orgterms > 1) {
242 if (terms == 1)
243 append(buf, " and ");
244 else if (terms > 1)
245 append(buf, ", ");
246 }
247 }
248 } while(0)
249 }
250 */
253 #define donum(n,txt) do { \
254 if (n > 0) { \
255 snprintf(tmp, sizeof(tmp), "%d", n); \
256 append(buf, tmp); \
257 append(buf, " " txt); \
258 if (n != 1) \
259 append(buf, "s"); \
260 terms--; \
261 if (orgterms > 1) { \
262 if (terms == 1) \
263 append(buf, " and "); \
264 else if (terms > 1) \
265 append(buf, ", "); \
266 } \
267 } \
268 } while(0)
271 /* returns allocated buffer, don't forget to free() */
272 char* ttime(int yr, int mn, int wk, int dy) {
273 char* buf = xmalloc(128);
274 int size = 128;
275 int terms, orgterms;
276 char tmp[128];
278 *buf = 0; /* Initialize buffer */
279 terms = orgterms = (yr!=0) + (mn!=0) + (wk!=0) + (dy!=0);
281 donum(yr, "year");
282 donum(mn, "month");
283 donum(wk, "week");
284 donum(dy, "day");
286 return buf;
287 }
288 #undef donum
295 /* lists the birthdays in their string format, one by one, and passes the string to a function. */
296 void liststrings(struct event* evl, prnfunc outf) {
297 int i,j;
298 char *buf, *tmp;
299 int size;
301 for (i = 0; evl[i].text != NULL; i++) {
302 buf = xmalloc(128);
303 *buf = '\0';
304 size = 128;
306 if (evl[i].warn == -1 && delta(&(evl[i].date))==0) {
307 append(buf, evl[i].text);
308 } else if (evl[i].enddate.day == 0) {
309 if (delta(&(evl[i].date)) <= evl[i].warn) {
310 append(buf, evl[i].text);
311 append(buf, " ");
312 tmp = tdelta(&(evl[i].date));
313 append(buf, tmp);
314 free(tmp);
315 }
316 } else {
317 if (delta(&(evl[i].date)) <= evl[i].warn) {
318 append(buf, evl[i].text);
319 append(buf, " for ");
320 /* +1 because, if the difference between two dates is one day, then the length of an event on those days is two days */
321 j = ddiff(&(evl[i].date),&(evl[i].enddate)) + 1;
322 tmp = ttime(0, 0, j/7, j%7);
323 append(buf, tmp);
324 free(tmp);
325 append(buf, " ");
326 tmp = tdelta(&(evl[i].date));
327 append(buf, tmp);
328 } else if (delta(&(evl[i].enddate)) <= evl[i].warn) {
329 append(buf, evl[i].text);
330 append(buf, " ");
331 j = delta(&(evl[i].enddate));
332 if (j) {
333 append(buf, "for ");
334 tmp = ttime(0, 0, j/7, j%7);
335 append(buf, tmp);
336 free(tmp);
337 append(buf, " longer");
338 } else {
339 append(buf, "finishes today");
340 }
341 }
342 }
343 if (*buf) {
344 append(buf, ".");
345 outf(buf);
346 }
347 free(buf);
348 }
349 }
358 /* sort the events by the time before the next time they come up, putting those
359 where the start has passed but we are still in the time-period first */
360 int evcmp(const void* p1, const void* p2) {
361 struct event* e1=(struct event*) p1;
362 struct event* e2=(struct event*) p2;
363 unsigned d1, d2;
365 /* if the delta for the enddate is less than that for the start date, then we
366 have passed the start date but not yet the end date, and so we should
367 display the enddate; otherwise, we should display the start date */
369 d1=delta(&(e1->date));
370 if (e1->enddate.day && delta(&(e1->enddate)) < d1)
371 d1=delta(&(e1->enddate));
373 d2=delta(&(e2->date));
374 if (e2->enddate.day && delta(&(e2->enddate)) < d2)
375 d2=delta(&(e2->enddate));
377 if (d1 < d2) return -1;
378 if (d1 > d2) return 1;
380 return strcmp(e1->text, e2->text);
381 }
388 /* difference in days between two dates */
389 /* it is assumed that D1 < D2, and so the result is always positive */
390 unsigned ddiff(struct date* D1, struct date* D2) {
391 struct date d1, d2;
392 int dd, m;
394 /* make working copies */
395 d1 = *D1;
396 d2 = *D2;
398 /* sort out zero years */
399 if (d1.year == 0 || d2.year==0) {
400 if (d1.year != d2.year) {
401 if (d1.year == 0) {
402 if (before(d1,d2))
403 d1.year = d2.year;
404 else
405 d1.year = d2.year - 1;
406 } else {
407 if (before(d1, d2))
408 d2.year = d1.year;
409 else
410 d2.year = d1.year + 1;
411 }
412 } else { /* both years zero */
413 if (before(d1, d2))
414 d1.year = d2.year = today.year;
415 else {
416 d1.year = today.year;
417 d2.year = d1.year + 1;
418 }
419 }
420 }
422 /* now we can actually do the comparison ... */
423 dd = 0;
425 /* to start with, we work in months */
426 for (m=d1.month; m < d2.month + (d2.year-d1.year)*12; m++)
427 dd += mlen(((m-1)%12)+1, d1.year + m/12);
429 /* and then we renormalise for the days within the months */
430 /* the first month was included in our calculations */
431 dd -= d1.day;
432 /* but the last one wasn't */
433 dd += d2.day;
435 return dd;
436 }
445 /* actually until the next anniversary of ... */
446 unsigned delta(struct date *date) {
447 struct date d;
448 unsigned dt, mn;
450 memcpy(&d, date, sizeof(struct date));
452 /* past the end of the year */
453 if (before(d, today)) {
454 d.year = 1;
455 } else {
456 d.year = 0;
457 }
459 for (mn = today.month, dt=0; mn < d.month + 12*d.year; mn++) {
460 dt += mlen(((mn-1)%12) + 1,today.year + mn/12);
461 }
463 dt -= today.day;
464 dt += d.day;
466 return dt;
467 }
474 void gettoday(void) {
475 struct tm *tm;
476 time_t t;
478 time(&t);
479 tm = localtime(&t);
480 today.day = tm->tm_mday;
481 today.month = tm->tm_mon + 1; /* 1-12 instead of 0-11 */
482 today.year = tm->tm_year + 1900;
483 }
494 struct event* readlist() {
495 int i, j, k, l, d;
496 struct event *evl;
497 char buf[1024], buf2[1024];
498 char *ptr;
499 unsigned flags;
501 /* initialise */
502 gettoday();
504 for (i = 0, evl = NULL; fgets(buf, sizeof(buf), stdin) != NULL; i++) {
505 evl = (struct event *) xrealloc(evl, sizeof(struct event) * (i + 1));
507 /* ignore comments and empty lines */
508 if (*buf == '#' || *buf == '\n') {
509 i--;
510 continue;
511 }
513 /* parse string in buf */
514 ptr = strrchr(buf, '='); /* allow '=' in text */
516 /* not a valid line, so ignore it! Cool, huh? */
517 /* Attention: only recognizes lines without '=' */
518 if (ptr == NULL) {
519 fprintf(stderr, "WARNING: Invalid line in input:\n%s", buf);
520 i--;
521 continue;
522 }
524 *(ptr++) = 0;
526 j = sscanf(ptr, "%u-%u-%u", &(evl[i].date.year), &(evl[i].date.month), &(evl[i].date.day));
527 /* ... unless it wasn't read, in which case set it to zero */
528 if (j==2) {
529 evl[i].date.year = 0;
530 }
533 /* parse flags */
535 evl[i].warn = iDWarn;
536 evl[i].enddate.day = 0;
537 evl[i].enddate.month = 0;
538 evl[i].enddate.year = 0;
540 flags = 0;
541 j = 0;
543 while(j = skptok(j, ptr), ptr[j] != 0) {
544 for (k = 0; FTABLE[k].txt != NULL && strncmp(FTABLE[k].txt, ptr + j, strlen(FTABLE[k].txt)); k++) {
545 }
547 switch (FTABLE[k].flag) {
548 case F_WTIME_P: /* w <n> -- sets warning time */
549 sscanf(ptr + j, "w %u", &(evl[i].warn));
550 break;
551 case F_FORDAYS: /* for <days> -- sets the duration of the event */
552 sscanf(ptr + j, "for %u", &d);
553 evl[i].enddate=evl[i].date;
554 for (l = 1; l < d; l++) {
555 evl[i].enddate.day++;
556 if (evl[i].enddate.day > mlen(evl[i].enddate.month, evl[i].enddate.year)) {
557 evl[i].enddate.month++;
558 evl[i].enddate.day = 1;
559 }
560 if (evl[i].enddate.month > 12) {
561 evl[i].enddate.year++;
562 evl[i].enddate.month = 1;
563 }
564 }
565 break;
566 case F_TODATE: /* to <date> -- sets the end date of the event */
567 l = sscanf(ptr + j, "to %u-%u-%u", &(evl[i].enddate.year), &(evl[i].enddate.month), &(evl[i].enddate.day));
568 if (l == 2) {
569 evl[i].enddate.year = 0;
570 }
571 break;
572 case 0:
573 break;
574 default:
575 flags |= FTABLE[k].flag;
576 break;
577 }
578 }
581 /* construct event text */
583 switch(flags & F_MTYPE) {
584 case F_TBIRTHDAY:
585 default: /* assume it's a birthday */
586 if (evl[i].date.year != 0) {
587 int tmp_age = ydelta(evl[i].date, today);
588 if (tmp_age != 1) {
589 sprintf(buf2, "%s is %d years old", buf, tmp_age);
590 } else {
591 sprintf(buf2, "%s is %d year old", buf, tmp_age);
592 }
593 } else {
594 sprintf(buf2, "%s has a birthday", buf);
595 }
596 break;
597 case F_TANNIVERSARY:
598 if (evl[i].date.year != 0) {
599 sprintf(buf2, "%s %d years ago", buf, ydelta(evl[i].date, today));
600 } else {
601 strcpy(buf2, buf);
602 }
603 break;
604 case F_TEVENT:
605 /* if a year was specified, and this warning isn't for it, ignore! */
606 if ((evl[i].date.year != 0 && ydelta(evl[i].date, today) != 0) && (evl[i].enddate.year == 0 || ydelta(evl[i].enddate, today) != 0)) {
607 i--;
608 continue;
609 }
610 strcpy(buf2, buf);
611 break;
612 }
613 evl[i].text = strdup(buf2);
614 }
616 evl = (struct event *) xrealloc(evl, sizeof(struct event) * (i + 1));
617 evl[i].date.day = 0;
618 evl[i].date.month = 0;
619 evl[i].date.year = 0;
620 evl[i].text = (char *) NULL;
622 fclose(stdin);
624 /* NB uses i from above */
625 qsort(evl, i, sizeof(struct event), evcmp);
626 return evl;
627 }
633 int skptok(int j, char *ptr) {
634 for (; ptr[j] != 0 && ptr[j] != ' ' && ptr[j] != '\t' ; j++);
635 for (; ptr[j] != 0 && (ptr[j] == ' ' || ptr[j] == '\t'); j++);
637 return j;
638 }
645 int main(int argc, char* argv[]) {
647 while (--argc > 0 && (*++argv)[0] == '-') {
648 if (strcmp(argv[0], "-W") == 0) {
649 /* TODO: catch if no value given */
650 iDWarn = atoi((++argv)[0]);
651 argc--;
652 } else {
653 fprintf(stderr, "unknown option %s\n", argv[0]);
654 exit(1);
655 }
656 }
658 liststrings(readlist(), puts);
660 return 0;
661 }