bday

view bdengine.c @ 3:dc2f94280b01

new Makefile; removed MinWarn and MaxWarn; adjusted manpage
author meillo@marmaro.de
date Mon, 17 Dec 2007 11:28:40 +0100
parents 9ec037775c38
children 5326c222cd4e
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);
116 /* ========== Global variables */
118 struct date today;
119 int iDWarn = DEF_WARN;
121 const unsigned MLENDAT[]={31,-1,31,30,31,30,31,31,30,31,30,31};
123 const struct _ftable FTABLE[] = {
124 {"bir",F_TBIRTHDAY},
125 {"bd", F_TBIRTHDAY},
126 {"ann",F_TANNIVERSARY},
127 {"ev", F_TEVENT},
128 {"mes", F_TMESSAGE},
129 {"w", F_WTIME_P},
130 {"to", F_TODATE},
131 {"for", F_FORDAYS},
132 {NULL, 0}
133 };
137 /* ========== */
142 /* compare the first strlen(a) characters of a and b */
143 #define strbegcmp(a,b) strncmp(a,b,strlen(a))
145 /* like strcat(), but lets the buffer automagically grow :-)
146 * (needs local variable "size" with the buffer size) */
147 #define append(where, what) do { \
148 if (strlen(what) > (size - strlen(where))) { \
149 xrealloc(where, size + 128 + strlen(what)); \
150 size += 128 + strlen(what); \
151 } \
152 strcat(where, what); \
153 } while(0)
155 /* ========== */
157 /* returns delta(d) in days, weeks, months, etc
158 * the returned buffer is malloc()ed, do not forget to free() it */
159 char *tdelta(struct date *d) {
160 int dy, wk, mn, yr;
161 char *tmp;
162 char *buf = xmalloc(128);
163 int size = 128;
164 *buf = 0;
166 switch (delta(d)) {
167 case 0:
168 append(buf, "today");
169 return buf;
170 case 1:
171 append(buf, "tomorrow");
172 return buf;
173 default:
174 /* like delta(), we ignore the year */
175 yr=-before(*d,today);
176 mn=d->month - today.month;
177 dy=d->day - today.day;
179 if (dy < 0) {
180 dy += mlen(today.month, today.year);
181 mn--;
182 }
183 if (mn < 0) {
184 mn += 12;
185 yr++;
186 }
188 wk = (dy/7);
189 dy%=7;
191 append(buf, "in ");
192 tmp = ttime(yr, mn, wk, dy);
193 append(buf, tmp);
194 free(tmp);
196 if (*(buf + strlen(buf) - 1) == 's')
197 append(buf, "'");
198 else
199 append(buf, "'s");
201 append(buf, " time");
203 return buf;
204 }
205 }
211 /*
212 void donum(n,txt) {
213 do {
214 if (n > 0) {
215 snprintf(tmp, sizeof(tmp), "%d", n);
216 append(buf, tmp);
217 append(buf, " " txt);
218 if (n != 1)
219 append(buf, "s");
220 terms--;
221 if (orgterms > 1) {
222 if (terms == 1)
223 append(buf, " and ");
224 else if (terms > 1)
225 append(buf, ", ");
226 }
227 }
228 } while(0)
229 }
230 */
233 #define donum(n,txt) 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)
251 /* returns allocated buffer, don't forget to free() */
252 char* ttime(int yr, int mn, int wk, int dy) {
253 char* buf = xmalloc(128);
254 int size = 128;
255 int terms, orgterms;
256 char tmp[128];
258 *buf = 0; /* Initialize buffer */
259 terms = orgterms = (yr!=0) + (mn!=0) + (wk!=0) + (dy!=0);
261 donum(yr, "year");
262 donum(mn, "month");
263 donum(wk, "week");
264 donum(dy, "day");
266 return buf;
267 }
268 #undef donum
275 /* lists the birthdays in their string format, one by one, and passes the string
276 to a function. */
277 void liststrings(struct event *evl, prnfunc outf) {
278 int i,j;
279 char *buf, *tmp;
280 int size;
282 for (i = 0; evl[i].text != NULL; i++) {
283 buf = xmalloc(128);
284 *buf = '\0';
285 size = 128;
287 if (evl[i].warn == -1 && delta(&(evl[i].date))==0) {
288 append(buf, evl[i].text);
289 } else if (evl[i].enddate.day == 0) {
290 if (delta(&(evl[i].date)) <= evl[i].warn) {
291 append(buf, evl[i].text);
292 append(buf, " ");
293 tmp = tdelta(&(evl[i].date));
294 append(buf, tmp);
295 free(tmp);
296 }
297 } else {
298 if (delta(&(evl[i].date)) <= evl[i].warn) {
299 append(buf, evl[i].text);
300 append(buf, " for ");
301 /* +1 because, if the difference between two dates is one day,
302 then the length of an event on those days is two days */
303 j = ddiff(&(evl[i].date),&(evl[i].enddate)) + 1;
304 tmp = ttime(0, 0, j/7, j%7);
305 append(buf, tmp);
306 free(tmp);
307 append(buf, " ");
308 tmp = tdelta(&(evl[i].date));
309 append(buf, tmp);
310 } else if (delta(&(evl[i].enddate)) <= evl[i].warn) {
311 append(buf, evl[i].text);
312 append(buf, " ");
313 j = delta(&(evl[i].enddate));
314 if (j) {
315 append(buf, "for ");
316 tmp = ttime(0, 0, j/7, j%7);
317 append(buf, tmp);
318 free(tmp);
319 append(buf, " longer");
320 } else {
321 append(buf, "finishes today");
322 }
323 }
324 }
325 if (*buf) {
326 append(buf, ".");
327 outf(buf);
328 }
329 free(buf);
330 }
331 }
339 char* deffname(void) {
340 char buf[256];
342 strcpy(buf, getpwuid(getuid())->pw_dir);
343 strcat(buf, "/" DEFAULT_FILE);
345 return strdup(buf);
346 }
353 /* sort the events by the time before the next time they come up, putting those
354 where the start has passed but we are still in the time-period first */
355 int evcmp(const void *p1, const void *p2) {
356 struct event *e1=(struct event *)p1;
357 struct event *e2=(struct event *)p2;
358 unsigned d1,d2;
360 /* if the delta for the enddate is less than that for the start date, then we
361 have passed the start date but not yet the end date, and so we should
362 display the enddate; otherwise, we should display the start date */
364 d1=delta(&(e1->date));
365 if (e1->enddate.day && delta(&(e1->enddate)) < d1)
366 d1=delta(&(e1->enddate));
368 d2=delta(&(e2->date));
369 if (e2->enddate.day && delta(&(e2->enddate)) < d2)
370 d2=delta(&(e2->enddate));
372 if (d1 < d2) return -1;
373 if (d1 > d2) return 1;
375 return strcmp(e1->text, e2->text);
376 }
383 /* difference in days between two dates */
384 /* it is assumed that D1 < D2, and so the result is always positive */
385 unsigned ddiff(struct date *D1, struct date *D2) {
386 struct date d1,d2;
387 int dd,m;
389 /* make working copies */
390 d1=*D1;
391 d2=*D2;
393 /* sort out zero years */
394 if (d1.year == 0 || d2.year==0) {
395 if (d1.year != d2.year) {
396 if (d1.year == 0) {
397 if (before(d1,d2))
398 d1.year=d2.year;
399 else
400 d1.year=d2.year-1;
401 } else {
402 if (before(d1,d2))
403 d2.year=d1.year;
404 else
405 d2.year=d1.year+1;
406 }
407 } else { /* both years zero */
408 if (before(d1,d2))
409 d1.year=d2.year=today.year;
410 else {
411 d1.year=today.year;
412 d2.year=d1.year+1;
413 }
414 }
415 }
417 /* now we can actually do the comparison ... */
418 dd=0;
420 /* to start with, we work in months */
421 for (m=d1.month; m < d2.month + (d2.year-d1.year)*12; m++)
422 dd += mlen(((m-1)%12)+1, d1.year + m/12);
424 /* and then we renormalise for the days within the months */
425 /* the first month was included in our calculations */
426 dd -= d1.day;
427 /* but the last one wasn't */
428 dd += d2.day;
430 return dd;
431 }
440 /* actually until the next anniversary of ... */
441 unsigned delta(struct date *date) {
442 struct date d;
443 unsigned dt, mn;
445 memcpy(&d, date, sizeof(struct date));
447 /* past the end of the year */
448 if (before(d, today)) {
449 d.year = 1;
450 } else {
451 d.year = 0;
452 }
454 for (mn = today.month, dt=0; mn < d.month + 12*d.year; mn++)
455 dt += mlen(((mn-1)%12) + 1,today.year + mn/12);
457 dt -= today.day;
458 dt += d.day;
460 return dt;
461 }
468 void gettoday(void) {
469 struct tm *tm;
470 time_t t;
472 time(&t);
473 tm = localtime(&t);
474 today.day = tm->tm_mday;
475 today.month = tm->tm_mon + 1; /* 1-12 instead of 0-11 */
476 today.year = tm->tm_year;
477 today.year += 1900;
478 }
484 struct event *readlist(char *fname) {
485 FILE *file;
486 int i,j,k,l,d;
487 struct event *evl;
488 char buf[1024], buf2[1024];
489 char *ptr;
490 unsigned flags;
492 /* initialise */
493 if (fname==NULL) {
494 fname=deffname();
495 }
497 gettoday();
499 if (fname[0] == '-' && fname[1] == 0) {
500 /* read from stdin */
501 file=stdin;
502 } else {
503 /* now read it */
504 if((file=fopen(fname, "rt"))==NULL) {
505 fprintf(stderr, "Unable to open file \"%s\"\n", fname);
506 exit(1);
507 }
508 }
511 for (i = 0, evl=NULL; fgets(buf, sizeof(buf), file) != NULL; i++) {
512 evl = (struct event *) xrealloc(evl, sizeof(struct event) * (i + 1));
514 /* ignore comments and empty lines */
515 if (*buf == '#' || *buf == '\n') {
516 i--;
517 continue;
518 }
520 /* parse string in buf */
521 ptr = strrchr(buf, '='); /* allow '=' in text */
523 /* not a valid line, so ignore it! Cool, huh? */
524 if (ptr == NULL) {
525 fprintf(stderr, "WARNING: Invalid line in input file:\n%s", buf);
526 i--;
527 continue;
528 }
530 *(ptr++) = 0;
532 j = sscanf(ptr, "%u-%u-%u", &(evl[i].date.year), &(evl[i].date.month), &(evl[i].date.day));
533 /* if our year is only two digits, add 1900 to it ... */
534 if(evl[i].date.year < 100) evl[i].date.year+=1900;
535 /* ... unless it wasn't read, in which case set it to zero */
536 if(j==2) evl[i].date.year=0;
538 /* parse flags */
540 evl[i].warn=iDWarn;
541 evl[i].enddate.day=evl[i].enddate.month=evl[i].enddate.year=0;
543 flags=j=0;
544 while(j = skptok(j, ptr),ptr[j]!=0) {
545 for (k = 0; FTABLE[k].txt != NULL && strncmp(FTABLE[k].txt, ptr + j, strlen(FTABLE[k].txt)); k++);
546 switch (FTABLE[k].flag) {
547 case F_WTIME_P: /* w<n> -- sets warning time */
548 sscanf(ptr + j, "w %u", &(evl[i].warn));
549 break;
550 case F_FORDAYS: /* for<days> -- sets the duration of the event */
551 sscanf(ptr + j, "for %d", &d);
552 evl[i].enddate=evl[i].date;
553 for (l=1; l < d; l++) {
554 evl[i].enddate.day++;
555 if (evl[i].enddate.day > mlen(evl[i].enddate.month,
556 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:
567 l = sscanf(ptr + j, "to %u-%u-%u", &(evl[i].enddate.year), &(evl[i].enddate.month), &(evl[i].enddate.day));
568 if (evl[i].enddate.year < 100) {
569 evl[i].enddate.year+=1900;
570 }
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) &&
610 (evl[i].enddate.year == 0 || ydelta(evl[i].enddate, today) != 0)) {
611 i--;
612 continue;
613 }
614 strcpy(buf2, buf);
615 break;
616 case F_TMESSAGE:
617 /* Like an event, except that it only comes up on the given date, and no text at all is appended */
618 if ((evl[i].date.year != 0 && ydelta(evl[i].date, today) != 0) &&
619 (evl[i].enddate.year == 0 || ydelta(evl[i].enddate, today) != 0)) {
620 i--;
621 continue;
622 }
623 strcpy(buf2, buf);
624 evl[i].warn=-1; /* special code! */
625 break;
626 }
627 evl[i].text = strdup(buf2);
628 }
630 evl = (struct event *) xrealloc(evl, sizeof(struct event) * (i + 1));
631 evl[i].date.day=evl[i].date.month=evl[i].date.year=0;
632 evl[i].text = (char *) NULL;
634 fclose(file);
635 free(fname);
637 /* NB uses i from above */
638 qsort(evl, i, sizeof(struct event), evcmp);
639 return evl;
640 }
649 int skptok(int j, char *ptr) {
650 for (; ptr[j] != 0 && ptr[j] != ' ' && ptr[j] != '\t' ; j++);
651 for (; ptr[j] != 0 && (ptr[j] == ' ' || ptr[j] == '\t'); j++);
653 return j;
654 }