comparison 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
comparison
equal deleted inserted replaced
6:fc6e40f7bd5a 7:b6f4c7fba64a
1 /*
2 birthday
3
4 Birthday/Anniversary display on login
5
6 (c) 1996 AS Mortimer
7
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.
13
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.
17
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
21
22 You should also have recieved a copy of the Artistic license with
23 this program.
24
25
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:
29
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.
44
45 Comment lines are preceeded by #.
46
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.
50
51 2003/05/20: Automatic reallocation of output buffer in listsrings() by
52 Sebastian Schmidt <yath@yath.eu.org>.
53
54 */
55
56
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>
64
65
66
67 /* standard time to warn in advance, when no explicit w flag is given. */
68 #define DEF_WARN 14
69
70 /* ========== Global constants and data types */
71
72
73 /* month lengths etc */
74
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)))
80
81 /* -------- modifier flags */
82
83 #define F_MTYPE 0x07
84 #define F_TBIRTHDAY 1
85 #define F_TANNIVERSARY 2
86 #define F_TEVENT 3
87
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
93
94 struct _ftable {char *txt; unsigned flag;};
95
96 const struct _ftable FTABLE[];
97
98 struct date {
99 unsigned day;
100 unsigned month;
101 unsigned year;
102 };
103
104 struct event {
105 char *text;
106 struct date date;
107 struct date enddate;
108 int warn;
109 };
110
111 typedef int (*prnfunc)(const char *);
112
113 /* ========== Global Variables */
114
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);
122
123 int skptok(int j, char *ptr);
124 int evcmp(const void *e1, const void *e2);
125
126
127 struct date today;
128 int iDWarn = DEF_WARN;
129
130 const unsigned MLENDAT[]={31,-1,31,30,31,30,31,31,30,31,30,31};
131
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 };
141
142
143
144
145
146
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 */
152
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 }
161
162
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 }
171
172 /* ========== */
173
174
175
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)
185
186 /* ========== */
187
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;
196
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;
209
210 if (dy < 0) {
211 dy += mlen(today.month, today.year);
212 mn--;
213 }
214 if (mn < 0) {
215 mn += 12;
216 yr++;
217 }
218
219 wk = (dy / 7);
220 dy %= 7;
221
222 append(buf, "in ");
223 tmp = ttime(yr, mn, wk, dy);
224 append(buf, tmp);
225 free(tmp);
226
227 return buf;
228 }
229 }
230
231
232
233
234
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 */
255
256
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)
273
274
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];
281
282 *buf = 0; /* Initialize buffer */
283 terms = orgterms = (yr!=0) + (mn!=0) + (wk!=0) + (dy!=0);
284
285 donum(yr, "year");
286 donum(mn, "month");
287 donum(wk, "week");
288 donum(dy, "day");
289
290 return buf;
291 }
292 #undef donum
293
294
295
296
297
298
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;
304
305 for (i = 0; evl[i].text != NULL; i++) {
306 buf = xmalloc(128);
307 *buf = '\0';
308 size = 128;
309
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 }
354
355
356
357
358
359
360
361
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;
368
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 */
372
373 d1=delta(&(e1->date));
374 if (e1->enddate.day && delta(&(e1->enddate)) < d1)
375 d1=delta(&(e1->enddate));
376
377 d2=delta(&(e2->date));
378 if (e2->enddate.day && delta(&(e2->enddate)) < d2)
379 d2=delta(&(e2->enddate));
380
381 if (d1 < d2) return -1;
382 if (d1 > d2) return 1;
383
384 return strcmp(e1->text, e2->text);
385 }
386
387
388
389
390
391
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;
397
398 /* make working copies */
399 d1=*D1;
400 d2=*D2;
401
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 }
425
426 /* now we can actually do the comparison ... */
427 dd=0;
428
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);
432
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;
438
439 return dd;
440 }
441
442
443
444
445
446
447
448
449 /* actually until the next anniversary of ... */
450 unsigned delta(struct date *date) {
451 struct date d;
452 unsigned dt, mn;
453
454 memcpy(&d, date, sizeof(struct date));
455
456 /* past the end of the year */
457 if (before(d, today)) {
458 d.year = 1;
459 } else {
460 d.year = 0;
461 }
462
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);
465
466 dt -= today.day;
467 dt += d.day;
468
469 return dt;
470 }
471
472
473
474
475
476
477 void gettoday(void) {
478 struct tm *tm;
479 time_t t;
480
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 }
487
488
489
490
491
492
493
494
495
496
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;
503
504 /* initialise */
505 gettoday();
506
507 for (i = 0, evl = NULL; fgets(buf, sizeof(buf), stdin) != NULL; i++) {
508 evl = (struct event *) xrealloc(evl, sizeof(struct event) * (i + 1));
509
510 /* ignore comments and empty lines */
511 if (*buf == '#' || *buf == '\n') {
512 i--;
513 continue;
514 }
515
516 /* parse string in buf */
517 ptr = strrchr(buf, '='); /* allow '=' in text */
518
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 }
526
527 *(ptr++) = 0;
528
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 }
534
535
536 /* parse flags */
537
538 evl[i].warn = iDWarn;
539 evl[i].enddate.day = 0;
540 evl[i].enddate.month = 0;
541 evl[i].enddate.year = 0;
542
543 flags = 0;
544 j = 0;
545
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 }
549
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 }
582
583
584 /* construct event text */
585
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 }
618
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;
624
625 fclose(stdin);
626
627 /* NB uses i from above */
628 qsort(evl, i, sizeof(struct event), evcmp);
629 return evl;
630 }
631
632
633
634
635
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++);
639
640 return j;
641 }
642
643
644
645
646
647
648 int main(int argc, char* argv[]) {
649
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 }
660
661 liststrings(readlist(), puts);
662
663 return 0;
664 }