bday

view bdengine.c @ 4:5326c222cd4e

removed MESSAGE; code beautifing workaround for MESSAGE: use an event with warn 0 (message=yyyy-mm-dd w 0)
author meillo@marmaro.de
date Mon, 17 Dec 2007 12:14:41 +0100
parents dc2f94280b01
children 5af6bf2cb271
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 ann for an anniversary
37 o ev for an event
38 and zero or more of
39 o w <n> to set the warn-in-advance time to n days (don't include the
40 brackets! :)
41 o to <date>
42 o for <days>
43 to specify the length of time taken by an event, for example a
44 holiday.
46 Comment lines are preceeded by #.
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 2003/05/20: Automatic reallocation of output buffer in listsrings() by
53 Sebastian Schmidt <yath@yath.eu.org>.
55 */
59 /* ========== */
62 #include <stdio.h>
63 #include <stdarg.h>
64 #include <stdlib.h>
65 #include <string.h>
66 #include <time.h>
68 #include <sys/types.h>
69 #include <unistd.h>
70 #include <pwd.h>
72 #include "birthday.h"
74 /* ========== */
78 /*
79 xmalloc/xrealloc functions, and fatal exit function
80 Note: the x* functions are lifted straight from the GNU libc info docs
81 $Id: xmalloc.c,v 1.2 1999/01/16 17:08:59 andy Exp $
82 */
84 void* xmalloc (size_t size) {
85 register void* value = malloc (size);
86 if (value == 0) {
87 fprintf(stderr, "virtual memory exhausted\n");
88 exit(1);
89 }
90 return value;
91 }
94 void* xrealloc (void* ptr, size_t size) {
95 register void* value = realloc (ptr, size);
96 if (value == 0) {
97 fprintf(stderr, "virtual memory exhausted\n");
98 exit(1);
99 }
100 return value;
101 }
103 /* ========== */
110 int skptok(int j, char *ptr);
111 int evcmp(const void *e1, const void *e2);
112 char *deffname(void);
115 /* ========== Global variables */
117 struct date today;
118 int iDWarn = DEF_WARN;
120 const unsigned MLENDAT[]={31,-1,31,30,31,30,31,31,30,31,30,31};
122 const struct _ftable FTABLE[] = {
123 {"bir",F_TBIRTHDAY},
124 {"bd", F_TBIRTHDAY},
125 {"ann",F_TANNIVERSARY},
126 {"ev", F_TEVENT},
127 {"w", F_WTIME_P},
128 {"to", F_TODATE},
129 {"for", F_FORDAYS},
130 {NULL, 0}
131 };
135 /* ========== */
140 /* compare the first strlen(a) characters of a and b */
141 #define strbegcmp(a,b) strncmp(a,b,strlen(a))
143 /* like strcat(), but lets the buffer automagically grow :-)
144 * (needs local variable "size" with the buffer size) */
145 #define append(where, what) do { \
146 if (strlen(what) > (size - strlen(where))) { \
147 xrealloc(where, size + 128 + strlen(what)); \
148 size += 128 + strlen(what); \
149 } \
150 strcat(where, what); \
151 } while(0)
153 /* ========== */
155 /* returns delta(d) in days, weeks, months, etc
156 * the returned buffer is malloc()ed, do not forget to free() it */
157 char *tdelta(struct date *d) {
158 int dy, wk, mn, yr;
159 char *tmp;
160 char *buf = xmalloc(128);
161 int size = 128;
162 *buf = 0;
164 switch (delta(d)) {
165 case 0:
166 append(buf, "today");
167 return buf;
168 case 1:
169 append(buf, "tomorrow");
170 return buf;
171 default:
172 /* like delta(), we ignore the year */
173 yr=-before(*d,today);
174 mn=d->month - today.month;
175 dy=d->day - today.day;
177 if (dy < 0) {
178 dy += mlen(today.month, today.year);
179 mn--;
180 }
181 if (mn < 0) {
182 mn += 12;
183 yr++;
184 }
186 wk = (dy/7);
187 dy%=7;
189 append(buf, "in ");
190 tmp = ttime(yr, mn, wk, dy);
191 append(buf, tmp);
192 free(tmp);
194 if (*(buf + strlen(buf) - 1) == 's')
195 append(buf, "'");
196 else
197 append(buf, "'s");
199 append(buf, " time");
201 return buf;
202 }
203 }
209 /*
210 void donum(n,txt) {
211 do {
212 if (n > 0) {
213 snprintf(tmp, sizeof(tmp), "%d", n);
214 append(buf, tmp);
215 append(buf, " " txt);
216 if (n != 1)
217 append(buf, "s");
218 terms--;
219 if (orgterms > 1) {
220 if (terms == 1)
221 append(buf, " and ");
222 else if (terms > 1)
223 append(buf, ", ");
224 }
225 }
226 } while(0)
227 }
228 */
231 #define donum(n,txt) do { \
232 if (n > 0) { \
233 snprintf(tmp, sizeof(tmp), "%d", n); \
234 append(buf, tmp); \
235 append(buf, " " txt); \
236 if (n != 1) \
237 append(buf, "s"); \
238 terms--; \
239 if (orgterms > 1) { \
240 if (terms == 1) \
241 append(buf, " and "); \
242 else if (terms > 1) \
243 append(buf, ", "); \
244 } \
245 } \
246 } while(0)
249 /* returns allocated buffer, don't forget to free() */
250 char* ttime(int yr, int mn, int wk, int dy) {
251 char* buf = xmalloc(128);
252 int size = 128;
253 int terms, orgterms;
254 char tmp[128];
256 *buf = 0; /* Initialize buffer */
257 terms = orgterms = (yr!=0) + (mn!=0) + (wk!=0) + (dy!=0);
259 donum(yr, "year");
260 donum(mn, "month");
261 donum(wk, "week");
262 donum(dy, "day");
264 return buf;
265 }
266 #undef donum
273 /* lists the birthdays in their string format, one by one, and passes the string
274 to a function. */
275 void liststrings(struct event *evl, prnfunc outf) {
276 int i,j;
277 char *buf, *tmp;
278 int size;
280 for (i = 0; evl[i].text != NULL; i++) {
281 buf = xmalloc(128);
282 *buf = '\0';
283 size = 128;
285 if (evl[i].warn == -1 && delta(&(evl[i].date))==0) {
286 append(buf, evl[i].text);
287 } else if (evl[i].enddate.day == 0) {
288 if (delta(&(evl[i].date)) <= evl[i].warn) {
289 append(buf, evl[i].text);
290 append(buf, " ");
291 tmp = tdelta(&(evl[i].date));
292 append(buf, tmp);
293 free(tmp);
294 }
295 } else {
296 if (delta(&(evl[i].date)) <= evl[i].warn) {
297 append(buf, evl[i].text);
298 append(buf, " for ");
299 /* +1 because, if the difference between two dates is one day,
300 then the length of an event on those days is two days */
301 j = ddiff(&(evl[i].date),&(evl[i].enddate)) + 1;
302 tmp = ttime(0, 0, j/7, j%7);
303 append(buf, tmp);
304 free(tmp);
305 append(buf, " ");
306 tmp = tdelta(&(evl[i].date));
307 append(buf, tmp);
308 } else if (delta(&(evl[i].enddate)) <= evl[i].warn) {
309 append(buf, evl[i].text);
310 append(buf, " ");
311 j = delta(&(evl[i].enddate));
312 if (j) {
313 append(buf, "for ");
314 tmp = ttime(0, 0, j/7, j%7);
315 append(buf, tmp);
316 free(tmp);
317 append(buf, " longer");
318 } else {
319 append(buf, "finishes today");
320 }
321 }
322 }
323 if (*buf) {
324 append(buf, ".");
325 outf(buf);
326 }
327 free(buf);
328 }
329 }
337 char* deffname(void) {
338 char buf[256];
340 strcpy(buf, getpwuid(getuid())->pw_dir);
341 strcat(buf, "/" DEFAULT_FILE);
343 return strdup(buf);
344 }
351 /* sort the events by the time before the next time they come up, putting those
352 where the start has passed but we are still in the time-period first */
353 int evcmp(const void *p1, const void *p2) {
354 struct event *e1=(struct event *)p1;
355 struct event *e2=(struct event *)p2;
356 unsigned d1,d2;
358 /* if the delta for the enddate is less than that for the start date, then we
359 have passed the start date but not yet the end date, and so we should
360 display the enddate; otherwise, we should display the start date */
362 d1=delta(&(e1->date));
363 if (e1->enddate.day && delta(&(e1->enddate)) < d1)
364 d1=delta(&(e1->enddate));
366 d2=delta(&(e2->date));
367 if (e2->enddate.day && delta(&(e2->enddate)) < d2)
368 d2=delta(&(e2->enddate));
370 if (d1 < d2) return -1;
371 if (d1 > d2) return 1;
373 return strcmp(e1->text, e2->text);
374 }
381 /* difference in days between two dates */
382 /* it is assumed that D1 < D2, and so the result is always positive */
383 unsigned ddiff(struct date *D1, struct date *D2) {
384 struct date d1,d2;
385 int dd,m;
387 /* make working copies */
388 d1=*D1;
389 d2=*D2;
391 /* sort out zero years */
392 if (d1.year == 0 || d2.year==0) {
393 if (d1.year != d2.year) {
394 if (d1.year == 0) {
395 if (before(d1,d2))
396 d1.year=d2.year;
397 else
398 d1.year=d2.year-1;
399 } else {
400 if (before(d1,d2))
401 d2.year=d1.year;
402 else
403 d2.year=d1.year+1;
404 }
405 } else { /* both years zero */
406 if (before(d1,d2))
407 d1.year=d2.year=today.year;
408 else {
409 d1.year=today.year;
410 d2.year=d1.year+1;
411 }
412 }
413 }
415 /* now we can actually do the comparison ... */
416 dd=0;
418 /* to start with, we work in months */
419 for (m=d1.month; m < d2.month + (d2.year-d1.year)*12; m++)
420 dd += mlen(((m-1)%12)+1, d1.year + m/12);
422 /* and then we renormalise for the days within the months */
423 /* the first month was included in our calculations */
424 dd -= d1.day;
425 /* but the last one wasn't */
426 dd += d2.day;
428 return dd;
429 }
438 /* actually until the next anniversary of ... */
439 unsigned delta(struct date *date) {
440 struct date d;
441 unsigned dt, mn;
443 memcpy(&d, date, sizeof(struct date));
445 /* past the end of the year */
446 if (before(d, today)) {
447 d.year = 1;
448 } else {
449 d.year = 0;
450 }
452 for (mn = today.month, dt=0; mn < d.month + 12*d.year; mn++)
453 dt += mlen(((mn-1)%12) + 1,today.year + mn/12);
455 dt -= today.day;
456 dt += d.day;
458 return dt;
459 }
466 void gettoday(void) {
467 struct tm *tm;
468 time_t t;
470 time(&t);
471 tm = localtime(&t);
472 today.day = tm->tm_mday;
473 today.month = tm->tm_mon + 1; /* 1-12 instead of 0-11 */
474 today.year = tm->tm_year;
475 today.year += 1900;
476 }
482 struct event *readlist(char *fname) {
483 FILE *file;
484 int i,j,k,l,d;
485 struct event *evl;
486 char buf[1024], buf2[1024];
487 char *ptr;
488 unsigned flags;
490 /* initialise */
491 if (fname==NULL) {
492 fname=deffname();
493 }
495 gettoday();
497 if (fname[0] == '-' && fname[1] == 0) {
498 /* read from stdin */
499 file = stdin;
500 } else {
501 /* now read it */
502 if ((file = fopen(fname, "rt")) == NULL) {
503 fprintf(stderr, "Unable to open file \"%s\"\n", fname);
504 exit(1);
505 }
506 }
509 for (i = 0, evl = NULL; fgets(buf, sizeof(buf), file) != NULL; i++) {
510 evl = (struct event *) xrealloc(evl, sizeof(struct event) * (i + 1));
512 /* ignore comments and empty lines */
513 if (*buf == '#' || *buf == '\n') {
514 i--;
515 continue;
516 }
518 /* parse string in buf */
519 ptr = strrchr(buf, '='); /* allow '=' in text */
521 /* not a valid line, so ignore it! Cool, huh? */
522 /* Attention: only recognizes lines without '=' */
523 if (ptr == NULL) {
524 fprintf(stderr, "WARNING: Invalid line in input file:\n%s", buf);
525 i--;
526 continue;
527 }
529 *(ptr++) = 0;
531 j = sscanf(ptr, "%u-%u-%u", &(evl[i].date.year), &(evl[i].date.month), &(evl[i].date.day));
532 /* ... unless it wasn't read, in which case set it to zero */
533 if (j==2) {
534 evl[i].date.year = 0;
535 }
538 /* parse flags */
540 evl[i].warn = iDWarn;
541 evl[i].enddate.day = 0;
542 evl[i].enddate.month = 0;
543 evl[i].enddate.year = 0;
545 flags = 0;
546 j = 0;
548 while(j = skptok(j, ptr), ptr[j] != 0) {
549 for (k = 0; FTABLE[k].txt != NULL && strncmp(FTABLE[k].txt, ptr + j, strlen(FTABLE[k].txt)); k++) {
550 }
552 switch (FTABLE[k].flag) {
553 case F_WTIME_P: /* w <n> -- sets warning time */
554 sscanf(ptr + j, "w %u", &(evl[i].warn));
555 break;
556 case F_FORDAYS: /* for <days> -- sets the duration of the event */
557 sscanf(ptr + j, "for %u", &d);
558 evl[i].enddate=evl[i].date;
559 for (l = 1; l < d; l++) {
560 evl[i].enddate.day++;
561 if (evl[i].enddate.day > mlen(evl[i].enddate.month, evl[i].enddate.year)) {
562 evl[i].enddate.month++;
563 evl[i].enddate.day = 1;
564 }
565 if (evl[i].enddate.month > 12) {
566 evl[i].enddate.year++;
567 evl[i].enddate.month = 1;
568 }
569 }
570 break;
571 case F_TODATE: /* to <date> -- sets the end date of the event */
572 l = sscanf(ptr + j, "to %u-%u-%u", &(evl[i].enddate.year), &(evl[i].enddate.month), &(evl[i].enddate.day));
573 if (l == 2) {
574 evl[i].enddate.year = 0;
575 }
576 break;
577 case 0:
578 break;
579 default:
580 flags |= FTABLE[k].flag;
581 break;
582 }
583 }
586 /* construct event text */
588 switch(flags & F_MTYPE) {
589 case F_TBIRTHDAY:
590 default: /* assume it's a birthday */
591 if (evl[i].date.year != 0) {
592 int tmp_age = ydelta(evl[i].date, today);
593 if (tmp_age != 1) {
594 sprintf(buf2, "%s is %d years old", buf, tmp_age);
595 } else {
596 sprintf(buf2, "%s is %d year old", buf, tmp_age);
597 }
598 } else {
599 sprintf(buf2, "%s has a birthday", buf);
600 }
601 break;
602 case F_TANNIVERSARY:
603 if (evl[i].date.year != 0) {
604 sprintf(buf2, "%s %d years ago", buf, ydelta(evl[i].date, today));
605 } else {
606 strcpy(buf2, buf);
607 }
608 break;
609 case F_TEVENT:
610 /* if a year was specified, and this warning isn't for it, ignore! */
611 if ((evl[i].date.year != 0 && ydelta(evl[i].date, today) != 0) && (evl[i].enddate.year == 0 || ydelta(evl[i].enddate, today) != 0)) {
612 i--;
613 continue;
614 }
615 strcpy(buf2, buf);
616 break;
617 }
618 evl[i].text = strdup(buf2);
619 }
621 evl = (struct event *) xrealloc(evl, sizeof(struct event) * (i + 1));
622 evl[i].date.day = 0;
623 evl[i].date.month = 0;
624 evl[i].date.year = 0;
625 evl[i].text = (char *) NULL;
627 fclose(file);
628 free(fname);
630 /* NB uses i from above */
631 qsort(evl, i, sizeof(struct event), evcmp);
632 return evl;
633 }
642 int skptok(int j, char *ptr) {
643 for (; ptr[j] != 0 && ptr[j] != ' ' && ptr[j] != '\t' ; j++);
644 for (; ptr[j] != 0 && (ptr[j] == ' ' || ptr[j] == '\t'); j++);
646 return j;
647 }