bday

view bday.c @ 7:b6f4c7fba64a

all sources now in one file
author meillo@marmaro.de
date Tue, 18 Dec 2007 11:59:21 +0100
parents birthday.c@fc6e40f7bd5a
children 19c1ad697022
line source
1 /*
2 birthday
4 Birthday/Anniversary display on login
6 (c) 1996 AS Mortimer
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License as
10 published by the Free Software Foundation; either version 2 of the
11 License, or (at your option) any later version. You may also
12 distribute it under the Artistic License, as comes with Perl.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 You should also have recieved a copy of the Artistic license with
23 this program.
26 We're getting there. At the moment, the file used by default is ~/.birthdays
27 under UNIX, or C:\PERSONAL\BDAYS.LST under DOS, but this can be overridden on
28 the command line. The file has the following format:
30 name/event/whatever=date flags
31 where:
32 date is dd/mm, dd/mm/yy (assumes 20th century!) or dd/mm/yyyy
33 flags is ONE or ZERO of
34 o bd for a birthday (default)
35 o ann for an anniversary
36 o ev for an event
37 and zero or more of
38 o w <n> to set the warn-in-advance time to n days (don't include the
39 brackets! :)
40 o to <date>
41 o for <days>
42 to specify the length of time taken by an event, for example a
43 holiday.
45 Comment lines are preceeded by #.
47 Note: If you deviate from this format, I cannot guarantee anything about
48 it's behaviour. In most cases, it will just quietly ignore the error,
49 which probably isn't ideal behaviour. Oh, well.
51 2003/05/20: Automatic reallocation of output buffer in listsrings() by
52 Sebastian Schmidt <yath@yath.eu.org>.
54 */
57 #include <stdarg.h>
58 #include <stdio.h>
59 #include <stdlib.h>
60 #include <string.h>
61 #include <sys/types.h>
62 #include <time.h>
63 #include <unistd.h>
67 /* standard time to warn in advance, when no explicit w flag is given. */
68 #define DEF_WARN 14
70 /* ========== Global constants and data types */
73 /* month lengths etc */
75 #define isleapyear(y) ((y)%4==0 && ((y)%100 != 0 || (y)%400 == 0))
76 const unsigned MLENDAT[];
77 #define mlen(m,y) (MLENDAT[(m)-1] != -1 ? MLENDAT[(m)-1] : (isleapyear((y)) ? 29 : 28))
78 #define before(a,b) ((a).month < (b).month || ((a).month == (b).month && (a).day < (b).day))
79 #define ydelta(a,b) ((int) (b).year - (a).year + before((a),(b)))
81 /* -------- modifier flags */
83 #define F_MTYPE 0x07
84 #define F_TBIRTHDAY 1
85 #define F_TANNIVERSARY 2
86 #define F_TEVENT 3
88 /* flags processed immediately on encountering */
89 #define F_MIMMEDIATE 0x24
90 #define F_WTIME_P 0x08
91 #define F_FORDAYS 0x16
92 #define F_TODATE 0x24
94 struct _ftable {char *txt; unsigned flag;};
96 const struct _ftable FTABLE[];
98 struct date {
99 unsigned day;
100 unsigned month;
101 unsigned year;
102 };
104 struct event {
105 char *text;
106 struct date date;
107 struct date enddate;
108 int warn;
109 };
111 typedef int (*prnfunc)(const char *);
113 /* ========== Global Variables */
115 struct event* readlist(void);
116 void gettoday(void);
117 unsigned delta(struct date *);
118 unsigned ddiff(struct date *D1, struct date *D2);
119 void liststrings(struct event* evl, prnfunc outf);
120 char *tdelta(struct date *d);
121 char *ttime(int yr, int mn, int wk, int dy);
123 int skptok(int j, char *ptr);
124 int evcmp(const void *e1, const void *e2);
127 struct date today;
128 int iDWarn = DEF_WARN;
130 const unsigned MLENDAT[]={31,-1,31,30,31,30,31,31,30,31,30,31};
132 const struct _ftable FTABLE[] = {
133 {"bd", F_TBIRTHDAY},
134 {"ann",F_TANNIVERSARY},
135 {"ev", F_TEVENT},
136 {"w", F_WTIME_P},
137 {"to", F_TODATE},
138 {"for", F_FORDAYS},
139 {NULL, 0}
140 };
147 /*
148 xmalloc/xrealloc functions
149 Note: the x* functions are lifted straight from the GNU libc info docs
150 $Id: xmalloc.c,v 1.2 1999/01/16 17:08:59 andy Exp $
151 */
153 void* xmalloc (size_t size) {
154 register void* value = malloc (size);
155 if (value == 0) {
156 fprintf(stderr, "virtual memory exhausted\n");
157 exit(1);
158 }
159 return value;
160 }
163 void* xrealloc (void* ptr, size_t size) {
164 register void* value = realloc (ptr, size);
165 if (value == 0) {
166 fprintf(stderr, "virtual memory exhausted\n");
167 exit(1);
168 }
169 return value;
170 }
172 /* ========== */
176 /* like strcat(), but lets the buffer automagically grow :-)
177 * (needs local variable "size" with the buffer size) */
178 #define append(where, what) do { \
179 if (strlen(what) > (size - strlen(where))) { \
180 xrealloc(where, size + 128 + strlen(what)); \
181 size += 128 + strlen(what); \
182 } \
183 strcat(where, what); \
184 } while(0)
186 /* ========== */
188 /* returns delta(d) in days, weeks, months, etc
189 * the returned buffer is malloc()ed, do not forget to free() it */
190 char *tdelta(struct date *d) {
191 int dy, wk, mn, yr;
192 char *tmp;
193 char *buf = xmalloc(128);
194 int size = 128;
195 *buf = 0;
197 switch (delta(d)) {
198 case 0:
199 append(buf, "today");
200 return buf;
201 case 1:
202 append(buf, "tomorrow");
203 return buf;
204 default:
205 /* like delta(), we ignore the year */
206 yr=-before(*d,today);
207 mn=d->month - today.month;
208 dy=d->day - today.day;
210 if (dy < 0) {
211 dy += mlen(today.month, today.year);
212 mn--;
213 }
214 if (mn < 0) {
215 mn += 12;
216 yr++;
217 }
219 wk = (dy / 7);
220 dy %= 7;
222 append(buf, "in ");
223 tmp = ttime(yr, mn, wk, dy);
224 append(buf, tmp);
225 free(tmp);
227 return buf;
228 }
229 }
235 /*
236 void donum(n,txt) {
237 do {
238 if (n > 0) {
239 snprintf(tmp, sizeof(tmp), "%d", n);
240 append(buf, tmp);
241 append(buf, " " txt);
242 if (n != 1)
243 append(buf, "s");
244 terms--;
245 if (orgterms > 1) {
246 if (terms == 1)
247 append(buf, " and ");
248 else if (terms > 1)
249 append(buf, ", ");
250 }
251 }
252 } while(0)
253 }
254 */
257 #define donum(n,txt) do { \
258 if (n > 0) { \
259 snprintf(tmp, sizeof(tmp), "%d", n); \
260 append(buf, tmp); \
261 append(buf, " " txt); \
262 if (n != 1) \
263 append(buf, "s"); \
264 terms--; \
265 if (orgterms > 1) { \
266 if (terms == 1) \
267 append(buf, " and "); \
268 else if (terms > 1) \
269 append(buf, ", "); \
270 } \
271 } \
272 } while(0)
275 /* returns allocated buffer, don't forget to free() */
276 char* ttime(int yr, int mn, int wk, int dy) {
277 char* buf = xmalloc(128);
278 int size = 128;
279 int terms, orgterms;
280 char tmp[128];
282 *buf = 0; /* Initialize buffer */
283 terms = orgterms = (yr!=0) + (mn!=0) + (wk!=0) + (dy!=0);
285 donum(yr, "year");
286 donum(mn, "month");
287 donum(wk, "week");
288 donum(dy, "day");
290 return buf;
291 }
292 #undef donum
299 /* lists the birthdays in their string format, one by one, and passes the string to a function. */
300 void liststrings(struct event* evl, prnfunc outf) {
301 int i,j;
302 char *buf, *tmp;
303 int size;
305 for (i = 0; evl[i].text != NULL; i++) {
306 buf = xmalloc(128);
307 *buf = '\0';
308 size = 128;
310 if (evl[i].warn == -1 && delta(&(evl[i].date))==0) {
311 append(buf, evl[i].text);
312 } else if (evl[i].enddate.day == 0) {
313 if (delta(&(evl[i].date)) <= evl[i].warn) {
314 append(buf, evl[i].text);
315 append(buf, " ");
316 tmp = tdelta(&(evl[i].date));
317 append(buf, tmp);
318 free(tmp);
319 }
320 } else {
321 if (delta(&(evl[i].date)) <= evl[i].warn) {
322 append(buf, evl[i].text);
323 append(buf, " for ");
324 /* +1 because, if the difference between two dates is one day, then the length of an event on those days is two days */
325 j = ddiff(&(evl[i].date),&(evl[i].enddate)) + 1;
326 tmp = ttime(0, 0, j/7, j%7);
327 append(buf, tmp);
328 free(tmp);
329 append(buf, " ");
330 tmp = tdelta(&(evl[i].date));
331 append(buf, tmp);
332 } else if (delta(&(evl[i].enddate)) <= evl[i].warn) {
333 append(buf, evl[i].text);
334 append(buf, " ");
335 j = delta(&(evl[i].enddate));
336 if (j) {
337 append(buf, "for ");
338 tmp = ttime(0, 0, j/7, j%7);
339 append(buf, tmp);
340 free(tmp);
341 append(buf, " longer");
342 } else {
343 append(buf, "finishes today");
344 }
345 }
346 }
347 if (*buf) {
348 append(buf, ".");
349 outf(buf);
350 }
351 free(buf);
352 }
353 }
362 /* sort the events by the time before the next time they come up, putting those
363 where the start has passed but we are still in the time-period first */
364 int evcmp(const void *p1, const void *p2) {
365 struct event *e1=(struct event *)p1;
366 struct event *e2=(struct event *)p2;
367 unsigned d1,d2;
369 /* if the delta for the enddate is less than that for the start date, then we
370 have passed the start date but not yet the end date, and so we should
371 display the enddate; otherwise, we should display the start date */
373 d1=delta(&(e1->date));
374 if (e1->enddate.day && delta(&(e1->enddate)) < d1)
375 d1=delta(&(e1->enddate));
377 d2=delta(&(e2->date));
378 if (e2->enddate.day && delta(&(e2->enddate)) < d2)
379 d2=delta(&(e2->enddate));
381 if (d1 < d2) return -1;
382 if (d1 > d2) return 1;
384 return strcmp(e1->text, e2->text);
385 }
392 /* difference in days between two dates */
393 /* it is assumed that D1 < D2, and so the result is always positive */
394 unsigned ddiff(struct date *D1, struct date *D2) {
395 struct date d1,d2;
396 int dd,m;
398 /* make working copies */
399 d1=*D1;
400 d2=*D2;
402 /* sort out zero years */
403 if (d1.year == 0 || d2.year==0) {
404 if (d1.year != d2.year) {
405 if (d1.year == 0) {
406 if (before(d1,d2))
407 d1.year=d2.year;
408 else
409 d1.year=d2.year-1;
410 } else {
411 if (before(d1,d2))
412 d2.year=d1.year;
413 else
414 d2.year=d1.year+1;
415 }
416 } else { /* both years zero */
417 if (before(d1,d2))
418 d1.year=d2.year=today.year;
419 else {
420 d1.year=today.year;
421 d2.year=d1.year+1;
422 }
423 }
424 }
426 /* now we can actually do the comparison ... */
427 dd=0;
429 /* to start with, we work in months */
430 for (m=d1.month; m < d2.month + (d2.year-d1.year)*12; m++)
431 dd += mlen(((m-1)%12)+1, d1.year + m/12);
433 /* and then we renormalise for the days within the months */
434 /* the first month was included in our calculations */
435 dd -= d1.day;
436 /* but the last one wasn't */
437 dd += d2.day;
439 return dd;
440 }
449 /* actually until the next anniversary of ... */
450 unsigned delta(struct date *date) {
451 struct date d;
452 unsigned dt, mn;
454 memcpy(&d, date, sizeof(struct date));
456 /* past the end of the year */
457 if (before(d, today)) {
458 d.year = 1;
459 } else {
460 d.year = 0;
461 }
463 for (mn = today.month, dt=0; mn < d.month + 12*d.year; mn++)
464 dt += mlen(((mn-1)%12) + 1,today.year + mn/12);
466 dt -= today.day;
467 dt += d.day;
469 return dt;
470 }
477 void gettoday(void) {
478 struct tm *tm;
479 time_t t;
481 time(&t);
482 tm = localtime(&t);
483 today.day = tm->tm_mday;
484 today.month = tm->tm_mon + 1; /* 1-12 instead of 0-11 */
485 today.year = tm->tm_year + 1900;
486 }
497 struct event* readlist() {
498 int i, j, k, l, d;
499 struct event *evl;
500 char buf[1024], buf2[1024];
501 char *ptr;
502 unsigned flags;
504 /* initialise */
505 gettoday();
507 for (i = 0, evl = NULL; fgets(buf, sizeof(buf), stdin) != NULL; i++) {
508 evl = (struct event *) xrealloc(evl, sizeof(struct event) * (i + 1));
510 /* ignore comments and empty lines */
511 if (*buf == '#' || *buf == '\n') {
512 i--;
513 continue;
514 }
516 /* parse string in buf */
517 ptr = strrchr(buf, '='); /* allow '=' in text */
519 /* not a valid line, so ignore it! Cool, huh? */
520 /* Attention: only recognizes lines without '=' */
521 if (ptr == NULL) {
522 fprintf(stderr, "WARNING: Invalid line in input:\n%s", buf);
523 i--;
524 continue;
525 }
527 *(ptr++) = 0;
529 j = sscanf(ptr, "%u-%u-%u", &(evl[i].date.year), &(evl[i].date.month), &(evl[i].date.day));
530 /* ... unless it wasn't read, in which case set it to zero */
531 if (j==2) {
532 evl[i].date.year = 0;
533 }
536 /* parse flags */
538 evl[i].warn = iDWarn;
539 evl[i].enddate.day = 0;
540 evl[i].enddate.month = 0;
541 evl[i].enddate.year = 0;
543 flags = 0;
544 j = 0;
546 while(j = skptok(j, ptr), ptr[j] != 0) {
547 for (k = 0; FTABLE[k].txt != NULL && strncmp(FTABLE[k].txt, ptr + j, strlen(FTABLE[k].txt)); k++) {
548 }
550 switch (FTABLE[k].flag) {
551 case F_WTIME_P: /* w <n> -- sets warning time */
552 sscanf(ptr + j, "w %u", &(evl[i].warn));
553 break;
554 case F_FORDAYS: /* for <days> -- sets the duration of the event */
555 sscanf(ptr + j, "for %u", &d);
556 evl[i].enddate=evl[i].date;
557 for (l = 1; l < d; l++) {
558 evl[i].enddate.day++;
559 if (evl[i].enddate.day > mlen(evl[i].enddate.month, evl[i].enddate.year)) {
560 evl[i].enddate.month++;
561 evl[i].enddate.day = 1;
562 }
563 if (evl[i].enddate.month > 12) {
564 evl[i].enddate.year++;
565 evl[i].enddate.month = 1;
566 }
567 }
568 break;
569 case F_TODATE: /* to <date> -- sets the end date of the event */
570 l = sscanf(ptr + j, "to %u-%u-%u", &(evl[i].enddate.year), &(evl[i].enddate.month), &(evl[i].enddate.day));
571 if (l == 2) {
572 evl[i].enddate.year = 0;
573 }
574 break;
575 case 0:
576 break;
577 default:
578 flags |= FTABLE[k].flag;
579 break;
580 }
581 }
584 /* construct event text */
586 switch(flags & F_MTYPE) {
587 case F_TBIRTHDAY:
588 default: /* assume it's a birthday */
589 if (evl[i].date.year != 0) {
590 int tmp_age = ydelta(evl[i].date, today);
591 if (tmp_age != 1) {
592 sprintf(buf2, "%s is %d years old", buf, tmp_age);
593 } else {
594 sprintf(buf2, "%s is %d year old", buf, tmp_age);
595 }
596 } else {
597 sprintf(buf2, "%s has a birthday", buf);
598 }
599 break;
600 case F_TANNIVERSARY:
601 if (evl[i].date.year != 0) {
602 sprintf(buf2, "%s %d years ago", buf, ydelta(evl[i].date, today));
603 } else {
604 strcpy(buf2, buf);
605 }
606 break;
607 case F_TEVENT:
608 /* if a year was specified, and this warning isn't for it, ignore! */
609 if ((evl[i].date.year != 0 && ydelta(evl[i].date, today) != 0) && (evl[i].enddate.year == 0 || ydelta(evl[i].enddate, today) != 0)) {
610 i--;
611 continue;
612 }
613 strcpy(buf2, buf);
614 break;
615 }
616 evl[i].text = strdup(buf2);
617 }
619 evl = (struct event *) xrealloc(evl, sizeof(struct event) * (i + 1));
620 evl[i].date.day = 0;
621 evl[i].date.month = 0;
622 evl[i].date.year = 0;
623 evl[i].text = (char *) NULL;
625 fclose(stdin);
627 /* NB uses i from above */
628 qsort(evl, i, sizeof(struct event), evcmp);
629 return evl;
630 }
636 int skptok(int j, char *ptr) {
637 for (; ptr[j] != 0 && ptr[j] != ' ' && ptr[j] != '\t' ; j++);
638 for (; ptr[j] != 0 && (ptr[j] == ' ' || ptr[j] == '\t'); j++);
640 return j;
641 }
648 int main(int argc, char* argv[]) {
650 while (--argc > 0 && (*++argv)[0] == '-') {
651 if (strcmp(argv[0], "-W") == 0) {
652 /* TODO: catch if no value given */
653 iDWarn = atoi((++argv)[0]);
654 argc--;
655 } else {
656 fprintf(stderr, "unknown option %s\n", argv[0]);
657 exit(1);
658 }
659 }
661 liststrings(readlist(), puts);
663 return 0;
664 }