bday

view bdengine.c @ 1:8534f0e3a0db

added manpage; adjusted makefile
author meillo@marmaro.de
date Sun, 16 Dec 2007 22:52:15 +0100
parents
children 9ec037775c38
line source
1 /*
2 birthday.c
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.
25 $Id: bdengine.c,v 1.14 2001/10/21 07:03:49 andy Exp $
27 We're getting there. At the moment, the file used by default is ~/.birthdays
28 under UNIX, or C:\PERSONAL\BDAYS.LST under DOS, but this can be overridden on
29 the command line. The file has the following format:
31 name/event/whatever=date flags
32 where:
33 date is dd/mm, dd/mm/yy (assumes 20th century!) or dd/mm/yyyy
34 flags is ONE or ZERO of
35 o bd for a birthday (default)
36 o bir for a birthday (exactly equivalent to `bd')
37 o ann for an anniversary
38 o ev for an event
39 and zero or more of
40 o w<n> to set the warn-in-advance time to n days (don't include the
41 brackets! :)
42 o to<date>
43 o for<days>
44 to specify the length of time taken by an event, for example a
45 holiday.
47 Comment lines are preceeded by #.
49 Note: If you deviate from this format, I cannot guarantee anything about
50 it's behaviour. In most cases, it will just quietly ignore the error,
51 which probably isn't ideal behaviour. Oh, well.
53 2003/05/20: Automatic reallocation of output buffer in listsrings() by
54 Sebastian Schmidt <yath@yath.eu.org>.
56 */
60 /* ========== */
63 #include <stdio.h>
64 #include <stdarg.h>
65 #include <stdlib.h>
66 #include <string.h>
67 #include <time.h>
69 #include <sys/types.h>
70 #include <unistd.h>
71 #include <pwd.h>
73 #include "birthday.h"
75 /* ========== */
79 /*
80 xmalloc/xrealloc functions, and fatal exit function
81 Note: the x* functions are lifted straight from the GNU libc info docs
82 $Id: xmalloc.c,v 1.2 1999/01/16 17:08:59 andy Exp $
83 */
85 void *xmalloc (size_t size) {
86 register void *value = malloc (size);
87 if (value == 0) {
88 fprintf(stderr, "virtual memory exhausted\n");
89 exit(1);
90 }
91 return value;
92 }
95 void *xrealloc (void *ptr, size_t size) {
96 register void *value = realloc (ptr, size);
97 if (value == 0) {
98 fprintf(stderr, "virtual memory exhausted\n");
99 exit(1);
100 }
101 return value;
102 }
104 /* ========== */
111 int skptok(int j, char *ptr);
112 int evcmp(const void *e1, const void *e2);
113 char *deffname(void);
115 struct event *dir_include(char *dir, char *parm);
117 /* ========== Global variables */
119 struct date today;
120 int iDWarn = DEF_WARN;
121 int iMaxWarn = MAX_WARN;
122 int iMinWarn = MIN_WARN;
124 const unsigned MLENDAT[]={31,-1,31,30,31,30,31,31,30,31,30,31};
126 const struct _ftable FTABLE[] = {
127 {"bir",F_TBIRTHDAY},
128 {"bd", F_TBIRTHDAY},
129 {"ann",F_TANNIVERSARY},
130 {"ev", F_TEVENT},
131 {"mes", F_TMESSAGE},
132 {"w", F_WTIME_P},
133 {"to", F_TODATE},
134 {"for", F_FORDAYS},
135 {NULL, 0}
136 };
139 /* list of directives. These are entered in the file prefixed by '&'. The function should be declared as
140 struct event *dir_func(char *directive, char *parameters);
141 and should return a pointer to a NULL-terminated list of extra records, or NULL if there are none.
142 This structure will allow us to dynamically add directives, if we wish to do so in the future.
143 */
145 struct {
146 const char *text;
147 int len;
148 struct event *(*func)(char*,char*);
149 } directlist[] = {
150 { "include", 0, dir_include },
151 { NULL, 0, NULL }
152 };
154 /* ========== */
159 /* compare the first strlen(a) characters of a and b */
160 #define strbegcmp(a,b) strncmp(a,b,strlen(a))
162 /* like strcat(), but lets the buffer automagically grow :-)
163 * (needs local variable "size" with the buffer size) */
164 #define append(where, what) do { \
165 if (strlen(what) > (size - strlen(where))) { \
166 xrealloc(where, size + 128 + strlen(what)); \
167 size += 128 + strlen(what); \
168 } \
169 strcat(where, what); \
170 } while(0)
172 /* ========== */
174 /* returns delta(d) in days, weeks, months, etc
175 * the returned buffer is malloc()ed, do not forget to free() it */
176 char *tdelta(struct date *d) {
177 int dy, wk, mn, yr;
178 char *tmp;
179 char *buf = xmalloc(128);
180 int size = 128;
181 *buf = 0;
183 switch (delta(d)) {
184 case 0:
185 append(buf, "today");
186 return buf;
187 case 1:
188 append(buf, "tomorrow");
189 return buf;
190 default:
191 /* like delta(), we ignore the year */
192 yr=-before(*d,today);
193 mn=d->month - today.month;
194 dy=d->day - today.day;
196 if (dy < 0) {
197 dy += mlen(today.month, today.year);
198 mn--;
199 }
200 if (mn < 0) {
201 mn += 12;
202 yr++;
203 }
205 wk = (dy/7);
206 dy%=7;
208 append(buf, "in ");
209 tmp = ttime(yr, mn, wk, dy);
210 append(buf, tmp);
211 free(tmp);
213 if (*(buf + strlen(buf) - 1) == 's')
214 append(buf, "'");
215 else
216 append(buf, "'s");
218 append(buf, " time");
220 return buf;
221 }
222 }
228 /*
229 void donum(n,txt) {
230 do {
231 if (n > 0) {
232 snprintf(tmp, sizeof(tmp), "%d", n);
233 append(buf, tmp);
234 append(buf, " " txt);
235 if (n != 1)
236 append(buf, "s");
237 terms--;
238 if (orgterms > 1) {
239 if (terms == 1)
240 append(buf, " and ");
241 else if (terms > 1)
242 append(buf, ", ");
243 }
244 }
245 } while(0)
246 }
247 */
250 #define donum(n,txt) do { \
251 if (n > 0) { \
252 snprintf(tmp, sizeof(tmp), "%d", n); \
253 append(buf, tmp); \
254 append(buf, " " txt); \
255 if (n != 1) \
256 append(buf, "s"); \
257 terms--; \
258 if (orgterms > 1) { \
259 if (terms == 1) \
260 append(buf, " and "); \
261 else if (terms > 1) \
262 append(buf, ", "); \
263 } \
264 } \
265 } while(0)
268 /* returns allocated buffer, don't forget to free() */
269 char* ttime(int yr, int mn, int wk, int dy) {
270 char* buf = xmalloc(128);
271 int size = 128;
272 int terms, orgterms;
273 char tmp[128];
275 *buf = 0; /* Initialize buffer */
276 terms = orgterms = (yr!=0) + (mn!=0) + (wk!=0) + (dy!=0);
278 donum(yr, "year");
279 donum(mn, "month");
280 donum(wk, "week");
281 donum(dy, "day");
283 return buf;
284 }
285 #undef donum
292 /* lists the birthdays in their string format, one by one, and passes the string
293 to a function. */
294 void liststrings(struct event *evl, prnfunc outf) {
295 int i,j;
296 char *buf, *tmp;
297 int size;
299 for (i = 0; evl[i].text != NULL; i++) {
300 buf = xmalloc(128);
301 *buf = '\0';
302 size = 128;
304 if (evl[i].warn == -1 && delta(&(evl[i].date))==0) {
305 append(buf, evl[i].text);
306 } else if (evl[i].enddate.day == 0) {
307 if (delta(&(evl[i].date)) <= warnperiod(evl[i])) {
308 append(buf, evl[i].text);
309 append(buf, " ");
310 tmp = tdelta(&(evl[i].date));
311 append(buf, tmp);
312 free(tmp);
313 }
314 } else {
315 if (delta(&(evl[i].date)) <= warnperiod(evl[i])) {
316 append(buf, evl[i].text);
317 append(buf, " for ");
318 /* +1 because, if the difference between two dates is one day,
319 then the length of an event on those days is two days */
320 j = ddiff(&(evl[i].date),&(evl[i].enddate)) + 1;
321 tmp = ttime(0, 0, j/7, j%7);
322 append(buf, tmp);
323 free(tmp);
324 append(buf, " ");
325 tmp = tdelta(&(evl[i].date));
326 append(buf, tmp);
327 } else if (delta(&(evl[i].enddate)) <= warnperiod(evl[i])) {
328 append(buf, evl[i].text);
329 append(buf, " ");
330 j = delta(&(evl[i].enddate));
331 if (j) {
332 append(buf, "for ");
333 tmp = ttime(0, 0, j/7, j%7);
334 append(buf, tmp);
335 free(tmp);
336 append(buf, " longer");
337 } else {
338 append(buf, "finishes today");
339 }
340 }
341 }
342 if (*buf) {
343 append(buf, ".");
344 outf(buf);
345 }
346 free(buf);
347 }
348 }
356 char* deffname(void) {
357 char buf[256];
359 strcpy(buf,getpwuid(getuid())->pw_dir);
360 strcat(buf, "/" DEFAULT_FILE);
362 return strdup(buf);
363 }
370 /* sort the events by the time before the next time they come up, putting those
371 where the start has passed but we are still in the time-period first */
372 int evcmp(const void *p1, const void *p2) {
373 struct event *e1=(struct event *)p1;
374 struct event *e2=(struct event *)p2;
375 unsigned d1,d2;
377 /* if the delta for the enddate is less than that for the start date, then we
378 have passed the start date but not yet the end date, and so we should
379 display the enddate; otherwise, we should display the start date */
381 d1=delta(&(e1->date));
382 if (e1->enddate.day && delta(&(e1->enddate)) < d1)
383 d1=delta(&(e1->enddate));
385 d2=delta(&(e2->date));
386 if (e2->enddate.day && delta(&(e2->enddate)) < d2)
387 d2=delta(&(e2->enddate));
389 if (d1 < d2) return -1;
390 if (d1 > d2) return 1;
392 return strcmp(e1->text, e2->text);
393 }
400 /* difference in days between two dates */
401 /* it is assumed that D1 < D2, and so the result is always positive */
402 unsigned ddiff(struct date *D1, struct date *D2) {
403 struct date d1,d2;
404 int dd,m;
406 /* make working copies */
407 d1=*D1;
408 d2=*D2;
410 /* sort out zero years */
411 if (d1.year == 0 || d2.year==0) {
412 if (d1.year != d2.year) {
413 if (d1.year == 0) {
414 if (before(d1,d2))
415 d1.year=d2.year;
416 else
417 d1.year=d2.year-1;
418 } else {
419 if (before(d1,d2))
420 d2.year=d1.year;
421 else
422 d2.year=d1.year+1;
423 }
424 } else { /* both years zero */
425 if (before(d1,d2))
426 d1.year=d2.year=today.year;
427 else {
428 d1.year=today.year;
429 d2.year=d1.year+1;
430 }
431 }
432 }
434 /* now we can actually do the comparison ... */
435 dd=0;
437 /* to start with, we work in months */
438 for (m=d1.month; m < d2.month + (d2.year-d1.year)*12; m++)
439 dd += mlen(((m-1)%12)+1, d1.year + m/12);
441 /* and then we renormalise for the days within the months */
442 /* the first month was included in our calculations */
443 dd -= d1.day;
444 /* but the last one wasn't */
445 dd += d2.day;
447 return dd;
448 }
457 /* actually until the next anniversary of ... */
458 unsigned delta(struct date *date) {
459 struct date d;
460 unsigned dt, mn;
462 memcpy(&d, date, sizeof(struct date));
464 /* past the end of the year */
465 if (before(d, today)) {
466 d.year = 1;
467 } else {
468 d.year = 0;
469 }
471 for (mn = today.month, dt=0; mn < d.month + 12*d.year; mn++)
472 dt += mlen(((mn-1)%12) + 1,today.year + mn/12);
474 dt -= today.day;
475 dt += d.day;
477 return dt;
478 }
485 void gettoday(void) {
486 struct tm *tm;
487 time_t t;
489 time(&t);
490 tm = localtime(&t);
491 today.day = tm->tm_mday;
492 today.month = tm->tm_mon + 1; /* 1-12 instead of 0-11 */
493 today.year = tm->tm_year;
494 today.year += 1900;
495 }
501 struct event *readlist(char *fname) {
502 FILE *file;
503 int i,j,k,l,d;
504 struct event *evl;
505 char buf[1024], buf2[1024];
506 char *ptr;
507 unsigned flags;
508 struct event *nevl;
509 /* initialise */
510 if (fname==NULL) {
511 fname=deffname();
512 }
514 gettoday();
516 if (fname[0] == '-' && fname[1] == 0) {
517 /* read from stdin */
518 file=stdin;
519 } else {
520 /* now read it */
521 if((file=fopen(fname, "rt"))==NULL) {
522 fprintf(stderr, "Unable to open file \"%s\"\n", fname);
523 exit(1);
524 }
525 }
528 for (i = 0, evl=NULL; fgets(buf, sizeof(buf), file) != NULL; i++) {
529 evl = (struct event *) xrealloc(evl, sizeof(struct event) * (i + 1));
531 if (*buf == '#' || *buf == '\n')
532 {
533 i--;
534 continue;
535 }
536 if (*buf == '&') {
537 buf[strlen(buf)-1] = 0;
538 if ((ptr=strchr(buf+1,' ')) == NULL && (ptr=strchr((buf+1),'\t')) == NULL) {
539 ptr=(buf+1)+strlen((buf+1));
540 }
542 *ptr++=0;
543 nevl=NULL;
544 k=0;
545 for (j=0; directlist[j].text != NULL; j++) {
546 if (directlist[j].len == 0) directlist[j].len = strlen(directlist[j].text);
547 /*
548 fprintf(stderr, "Checking against directive #%d, %s, which has length %d, against \"%s\" with length %d\n",
549 j, directlist[j].text, directlist[j].len, buf+1, ptr-(buf+1)-1);
550 */
551 if (directlist[j].len == ptr-(buf+1)-1 && !strncmp(directlist[j].text,(buf+1),directlist[j].len)) {
552 nevl = directlist[j].func((buf+1),ptr);
553 k=1;
554 break;
555 }
556 }
558 if (nevl != NULL) {
559 for (j=0; nevl[j].text != NULL; j++);
560 i+=j-1;
561 evl = (struct event *) xrealloc(evl, sizeof(struct event) * (i + 1));
562 /* in fact, this loop reverses the order of the elements, but we're going to sort them later anyway. */
563 for (j=0; nevl[j].text != NULL; j++)
564 evl[i-j]=nevl[j];
565 }
566 if (!k) {
567 fprintf(stderr, "Warning: unrecognised directive \"%s\" ignored.\n", (buf+1));
568 i--;
569 }
570 continue;
571 }
573 /* parse string in buf */
574 ptr = strrchr(buf, '='); /* allow '=' in text */
575 if(ptr==NULL) /* not a valid line, so ignore it! Cool, huh? */
576 {
577 fprintf(stderr, "WARNING: Invalid line in input file:\n%s", buf);
578 i--;
579 continue;
580 }
582 *(ptr++) = 0;
584 j = sscanf(ptr, "%u-%u-%u", &(evl[i].date.year), &(evl[i].date.month), &(evl[i].date.day));
585 /* if our year is only two digits, add 1900 to it ... */
586 if(evl[i].date.year < 100) evl[i].date.year+=1900;
587 /* ... unless it wasn't read, in which case set it to zero */
588 if(j==2) evl[i].date.year=0;
590 /* parse flags */
592 evl[i].warn=iDWarn;
593 evl[i].enddate.day=evl[i].enddate.month=evl[i].enddate.year=0;
595 flags=j=0;
596 while(j = skptok(j, ptr),ptr[j]!=0)
597 {
598 for (k = 0; FTABLE[k].txt != NULL && strncmp(FTABLE[k].txt, ptr + j, strlen(FTABLE[k].txt)); k++);
599 switch (FTABLE[k].flag) {
600 case F_WTIME_P: /* w<n> -- sets warning time */
601 sscanf(ptr + j, "w %u", &(evl[i].warn));
602 break;
603 case F_FORDAYS: /* for<days> -- sets the duration of the event */
604 sscanf(ptr + j, "for %d", &d);
605 evl[i].enddate=evl[i].date;
606 for (l=1; l < d; l++) {
607 evl[i].enddate.day++;
608 if (evl[i].enddate.day > mlen(evl[i].enddate.month,
609 evl[i].enddate.year)) {
610 evl[i].enddate.month++;
611 evl[i].enddate.day=1;
612 }
613 if (evl[i].enddate.month > 12) {
614 evl[i].enddate.year++;
615 evl[i].enddate.month=1;
616 }
617 }
618 break;
619 case F_TODATE:
620 l = sscanf(ptr + j, "to %u-%u-%u", &(evl[i].enddate.year), &(evl[i].enddate.month), &(evl[i].enddate.day));
621 if (evl[i].enddate.year < 100) evl[i].enddate.year+=1900;
622 if (l == 2) evl[i].enddate.year=0;
623 break;
624 case 0:
625 break;
626 default:
627 flags|=FTABLE[k].flag;
628 break;
629 }
630 }
632 /* construct event text */
634 switch(flags & F_MTYPE) {
635 case F_TBIRTHDAY:
636 default: /* assume it's a birthday */
637 if (evl[i].date.year != 0) {
638 int tmp_age=ydelta(evl[i].date, today);
639 if (tmp_age!=1) {
640 sprintf(buf2, "%s is %d years old", buf, tmp_age);
641 } else {
642 sprintf(buf2, "%s is %d year old", buf, tmp_age);
643 }
644 } else {
645 sprintf(buf2, "%s has a birthday", buf);
646 }
647 break;
648 case F_TANNIVERSARY:
649 if (evl[i].date.year != 0) {
650 sprintf(buf2, "%s %d years ago", buf, ydelta(evl[i].date, today));
651 } else {
652 strcpy(buf2, buf);
653 }
654 break;
655 case F_TEVENT:
656 /* if a year was specified, and this warning isn't for it, ignore! */
657 if ((evl[i].date.year != 0 && ydelta(evl[i].date, today) != 0) &&
658 (evl[i].enddate.year == 0 || ydelta(evl[i].enddate, today) != 0)) {
659 i--;
660 continue;
661 }
662 strcpy(buf2, buf);
663 break;
664 case F_TMESSAGE:
665 /* Like an event, except that it only comes up on the given date, and no text at all is appended */
666 if ((evl[i].date.year != 0 && ydelta(evl[i].date, today) != 0) &&
667 (evl[i].enddate.year == 0 || ydelta(evl[i].enddate, today) != 0)) {
668 i--;
669 continue;
670 }
671 strcpy(buf2, buf);
672 evl[i].warn=-1; /* special code! */
673 break;
674 }
675 evl[i].text = strdup(buf2);
676 }
678 evl = (struct event *) xrealloc(evl, sizeof(struct event) * (i + 1));
679 evl[i].date.day=evl[i].date.month=evl[i].date.year=0;
680 evl[i].text = (char *) NULL;
682 fclose(file);
683 free(fname);
685 /* NB uses i from above */
686 qsort(evl, i, sizeof(struct event), evcmp);
687 return evl;
688 }
697 int skptok(int j, char *ptr) {
698 for (; ptr[j] != 0 && ptr[j] != ' ' && ptr[j] != '\t' ; j++);
699 for (; ptr[j] != 0 && (ptr[j] == ' ' || ptr[j] == '\t'); j++);
701 return j;
702 }
709 /* ---------------------------------------------------------------------- */
710 /* Functions to parse input file directives */
711 struct event *dir_include(char *dir, char *parm) {
712 struct event *evl;
714 evl=readlist(strdup(parm));
716 return evl;
717 }