bday

view bdengine.c @ 5:5af6bf2cb271

reads only stdin now, no files anymore; removed -f option aswell; code beatifing
author meillo@marmaro.de
date Mon, 17 Dec 2007 15:09:03 +0100
parents 5326c222cd4e
children fc6e40f7bd5a
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>
71 #include "birthday.h"
73 /* ========== */
77 /*
78 xmalloc/xrealloc functions
79 Note: the x* functions are lifted straight from the GNU libc info docs
80 $Id: xmalloc.c,v 1.2 1999/01/16 17:08:59 andy Exp $
81 */
83 void* xmalloc (size_t size) {
84 register void* value = malloc (size);
85 if (value == 0) {
86 fprintf(stderr, "virtual memory exhausted\n");
87 exit(1);
88 }
89 return value;
90 }
93 void* xrealloc (void* ptr, size_t size) {
94 register void* value = realloc (ptr, size);
95 if (value == 0) {
96 fprintf(stderr, "virtual memory exhausted\n");
97 exit(1);
98 }
99 return value;
100 }
102 /* ========== */
109 int skptok(int j, char *ptr);
110 int evcmp(const void *e1, const void *e2);
113 /* ========== Global variables */
115 struct date today;
116 int iDWarn = DEF_WARN;
118 const unsigned MLENDAT[]={31,-1,31,30,31,30,31,31,30,31,30,31};
120 const struct _ftable FTABLE[] = {
121 {"bir",F_TBIRTHDAY},
122 {"bd", F_TBIRTHDAY},
123 {"ann",F_TANNIVERSARY},
124 {"ev", F_TEVENT},
125 {"w", F_WTIME_P},
126 {"to", F_TODATE},
127 {"for", F_FORDAYS},
128 {NULL, 0}
129 };
133 /* ========== */
137 /* like strcat(), but lets the buffer automagically grow :-)
138 * (needs local variable "size" with the buffer size) */
139 #define append(where, what) do { \
140 if (strlen(what) > (size - strlen(where))) { \
141 xrealloc(where, size + 128 + strlen(what)); \
142 size += 128 + strlen(what); \
143 } \
144 strcat(where, what); \
145 } while(0)
147 /* ========== */
149 /* returns delta(d) in days, weeks, months, etc
150 * the returned buffer is malloc()ed, do not forget to free() it */
151 char *tdelta(struct date *d) {
152 int dy, wk, mn, yr;
153 char *tmp;
154 char *buf = xmalloc(128);
155 int size = 128;
156 *buf = 0;
158 switch (delta(d)) {
159 case 0:
160 append(buf, "today");
161 return buf;
162 case 1:
163 append(buf, "tomorrow");
164 return buf;
165 default:
166 /* like delta(), we ignore the year */
167 yr=-before(*d,today);
168 mn=d->month - today.month;
169 dy=d->day - today.day;
171 if (dy < 0) {
172 dy += mlen(today.month, today.year);
173 mn--;
174 }
175 if (mn < 0) {
176 mn += 12;
177 yr++;
178 }
180 wk = (dy / 7);
181 dy %= 7;
183 append(buf, "in ");
184 tmp = ttime(yr, mn, wk, dy);
185 append(buf, tmp);
186 free(tmp);
188 return buf;
189 }
190 }
196 /*
197 void donum(n,txt) {
198 do {
199 if (n > 0) {
200 snprintf(tmp, sizeof(tmp), "%d", n);
201 append(buf, tmp);
202 append(buf, " " txt);
203 if (n != 1)
204 append(buf, "s");
205 terms--;
206 if (orgterms > 1) {
207 if (terms == 1)
208 append(buf, " and ");
209 else if (terms > 1)
210 append(buf, ", ");
211 }
212 }
213 } while(0)
214 }
215 */
218 #define donum(n,txt) do { \
219 if (n > 0) { \
220 snprintf(tmp, sizeof(tmp), "%d", n); \
221 append(buf, tmp); \
222 append(buf, " " txt); \
223 if (n != 1) \
224 append(buf, "s"); \
225 terms--; \
226 if (orgterms > 1) { \
227 if (terms == 1) \
228 append(buf, " and "); \
229 else if (terms > 1) \
230 append(buf, ", "); \
231 } \
232 } \
233 } while(0)
236 /* returns allocated buffer, don't forget to free() */
237 char* ttime(int yr, int mn, int wk, int dy) {
238 char* buf = xmalloc(128);
239 int size = 128;
240 int terms, orgterms;
241 char tmp[128];
243 *buf = 0; /* Initialize buffer */
244 terms = orgterms = (yr!=0) + (mn!=0) + (wk!=0) + (dy!=0);
246 donum(yr, "year");
247 donum(mn, "month");
248 donum(wk, "week");
249 donum(dy, "day");
251 return buf;
252 }
253 #undef donum
260 /* lists the birthdays in their string format, one by one, and passes the string to a function. */
261 void liststrings(struct event *evl, prnfunc outf) {
262 int i,j;
263 char *buf, *tmp;
264 int size;
266 for (i = 0; evl[i].text != NULL; i++) {
267 buf = xmalloc(128);
268 *buf = '\0';
269 size = 128;
271 if (evl[i].warn == -1 && delta(&(evl[i].date))==0) {
272 append(buf, evl[i].text);
273 } else if (evl[i].enddate.day == 0) {
274 if (delta(&(evl[i].date)) <= evl[i].warn) {
275 append(buf, evl[i].text);
276 append(buf, " ");
277 tmp = tdelta(&(evl[i].date));
278 append(buf, tmp);
279 free(tmp);
280 }
281 } else {
282 if (delta(&(evl[i].date)) <= evl[i].warn) {
283 append(buf, evl[i].text);
284 append(buf, " for ");
285 /* +1 because, if the difference between two dates is one day, then the length of an event on those days is two days */
286 j = ddiff(&(evl[i].date),&(evl[i].enddate)) + 1;
287 tmp = ttime(0, 0, j/7, j%7);
288 append(buf, tmp);
289 free(tmp);
290 append(buf, " ");
291 tmp = tdelta(&(evl[i].date));
292 append(buf, tmp);
293 } else if (delta(&(evl[i].enddate)) <= evl[i].warn) {
294 append(buf, evl[i].text);
295 append(buf, " ");
296 j = delta(&(evl[i].enddate));
297 if (j) {
298 append(buf, "for ");
299 tmp = ttime(0, 0, j/7, j%7);
300 append(buf, tmp);
301 free(tmp);
302 append(buf, " longer");
303 } else {
304 append(buf, "finishes today");
305 }
306 }
307 }
308 if (*buf) {
309 append(buf, ".");
310 outf(buf);
311 }
312 free(buf);
313 }
314 }
323 /* sort the events by the time before the next time they come up, putting those
324 where the start has passed but we are still in the time-period first */
325 int evcmp(const void *p1, const void *p2) {
326 struct event *e1=(struct event *)p1;
327 struct event *e2=(struct event *)p2;
328 unsigned d1,d2;
330 /* if the delta for the enddate is less than that for the start date, then we
331 have passed the start date but not yet the end date, and so we should
332 display the enddate; otherwise, we should display the start date */
334 d1=delta(&(e1->date));
335 if (e1->enddate.day && delta(&(e1->enddate)) < d1)
336 d1=delta(&(e1->enddate));
338 d2=delta(&(e2->date));
339 if (e2->enddate.day && delta(&(e2->enddate)) < d2)
340 d2=delta(&(e2->enddate));
342 if (d1 < d2) return -1;
343 if (d1 > d2) return 1;
345 return strcmp(e1->text, e2->text);
346 }
353 /* difference in days between two dates */
354 /* it is assumed that D1 < D2, and so the result is always positive */
355 unsigned ddiff(struct date *D1, struct date *D2) {
356 struct date d1,d2;
357 int dd,m;
359 /* make working copies */
360 d1=*D1;
361 d2=*D2;
363 /* sort out zero years */
364 if (d1.year == 0 || d2.year==0) {
365 if (d1.year != d2.year) {
366 if (d1.year == 0) {
367 if (before(d1,d2))
368 d1.year=d2.year;
369 else
370 d1.year=d2.year-1;
371 } else {
372 if (before(d1,d2))
373 d2.year=d1.year;
374 else
375 d2.year=d1.year+1;
376 }
377 } else { /* both years zero */
378 if (before(d1,d2))
379 d1.year=d2.year=today.year;
380 else {
381 d1.year=today.year;
382 d2.year=d1.year+1;
383 }
384 }
385 }
387 /* now we can actually do the comparison ... */
388 dd=0;
390 /* to start with, we work in months */
391 for (m=d1.month; m < d2.month + (d2.year-d1.year)*12; m++)
392 dd += mlen(((m-1)%12)+1, d1.year + m/12);
394 /* and then we renormalise for the days within the months */
395 /* the first month was included in our calculations */
396 dd -= d1.day;
397 /* but the last one wasn't */
398 dd += d2.day;
400 return dd;
401 }
410 /* actually until the next anniversary of ... */
411 unsigned delta(struct date *date) {
412 struct date d;
413 unsigned dt, mn;
415 memcpy(&d, date, sizeof(struct date));
417 /* past the end of the year */
418 if (before(d, today)) {
419 d.year = 1;
420 } else {
421 d.year = 0;
422 }
424 for (mn = today.month, dt=0; mn < d.month + 12*d.year; mn++)
425 dt += mlen(((mn-1)%12) + 1,today.year + mn/12);
427 dt -= today.day;
428 dt += d.day;
430 return dt;
431 }
438 void gettoday(void) {
439 struct tm *tm;
440 time_t t;
442 time(&t);
443 tm = localtime(&t);
444 today.day = tm->tm_mday;
445 today.month = tm->tm_mon + 1; /* 1-12 instead of 0-11 */
446 today.year = tm->tm_year + 1900;
447 }
453 struct event* readlist() {
454 int i, j, k, l, d;
455 struct event *evl;
456 char buf[1024], buf2[1024];
457 char *ptr;
458 unsigned flags;
460 /* initialise */
461 gettoday();
463 for (i = 0, evl = NULL; fgets(buf, sizeof(buf), stdin) != NULL; i++) {
464 evl = (struct event *) xrealloc(evl, sizeof(struct event) * (i + 1));
466 /* ignore comments and empty lines */
467 if (*buf == '#' || *buf == '\n') {
468 i--;
469 continue;
470 }
472 /* parse string in buf */
473 ptr = strrchr(buf, '='); /* allow '=' in text */
475 /* not a valid line, so ignore it! Cool, huh? */
476 /* Attention: only recognizes lines without '=' */
477 if (ptr == NULL) {
478 fprintf(stderr, "WARNING: Invalid line in input:\n%s", buf);
479 i--;
480 continue;
481 }
483 *(ptr++) = 0;
485 j = sscanf(ptr, "%u-%u-%u", &(evl[i].date.year), &(evl[i].date.month), &(evl[i].date.day));
486 /* ... unless it wasn't read, in which case set it to zero */
487 if (j==2) {
488 evl[i].date.year = 0;
489 }
492 /* parse flags */
494 evl[i].warn = iDWarn;
495 evl[i].enddate.day = 0;
496 evl[i].enddate.month = 0;
497 evl[i].enddate.year = 0;
499 flags = 0;
500 j = 0;
502 while(j = skptok(j, ptr), ptr[j] != 0) {
503 for (k = 0; FTABLE[k].txt != NULL && strncmp(FTABLE[k].txt, ptr + j, strlen(FTABLE[k].txt)); k++) {
504 }
506 switch (FTABLE[k].flag) {
507 case F_WTIME_P: /* w <n> -- sets warning time */
508 sscanf(ptr + j, "w %u", &(evl[i].warn));
509 break;
510 case F_FORDAYS: /* for <days> -- sets the duration of the event */
511 sscanf(ptr + j, "for %u", &d);
512 evl[i].enddate=evl[i].date;
513 for (l = 1; l < d; l++) {
514 evl[i].enddate.day++;
515 if (evl[i].enddate.day > mlen(evl[i].enddate.month, evl[i].enddate.year)) {
516 evl[i].enddate.month++;
517 evl[i].enddate.day = 1;
518 }
519 if (evl[i].enddate.month > 12) {
520 evl[i].enddate.year++;
521 evl[i].enddate.month = 1;
522 }
523 }
524 break;
525 case F_TODATE: /* to <date> -- sets the end date of the event */
526 l = sscanf(ptr + j, "to %u-%u-%u", &(evl[i].enddate.year), &(evl[i].enddate.month), &(evl[i].enddate.day));
527 if (l == 2) {
528 evl[i].enddate.year = 0;
529 }
530 break;
531 case 0:
532 break;
533 default:
534 flags |= FTABLE[k].flag;
535 break;
536 }
537 }
540 /* construct event text */
542 switch(flags & F_MTYPE) {
543 case F_TBIRTHDAY:
544 default: /* assume it's a birthday */
545 if (evl[i].date.year != 0) {
546 int tmp_age = ydelta(evl[i].date, today);
547 if (tmp_age != 1) {
548 sprintf(buf2, "%s is %d years old", buf, tmp_age);
549 } else {
550 sprintf(buf2, "%s is %d year old", buf, tmp_age);
551 }
552 } else {
553 sprintf(buf2, "%s has a birthday", buf);
554 }
555 break;
556 case F_TANNIVERSARY:
557 if (evl[i].date.year != 0) {
558 sprintf(buf2, "%s %d years ago", buf, ydelta(evl[i].date, today));
559 } else {
560 strcpy(buf2, buf);
561 }
562 break;
563 case F_TEVENT:
564 /* if a year was specified, and this warning isn't for it, ignore! */
565 if ((evl[i].date.year != 0 && ydelta(evl[i].date, today) != 0) && (evl[i].enddate.year == 0 || ydelta(evl[i].enddate, today) != 0)) {
566 i--;
567 continue;
568 }
569 strcpy(buf2, buf);
570 break;
571 }
572 evl[i].text = strdup(buf2);
573 }
575 evl = (struct event *) xrealloc(evl, sizeof(struct event) * (i + 1));
576 evl[i].date.day = 0;
577 evl[i].date.month = 0;
578 evl[i].date.year = 0;
579 evl[i].text = (char *) NULL;
581 fclose(stdin);
583 /* NB uses i from above */
584 qsort(evl, i, sizeof(struct event), evcmp);
585 return evl;
586 }
595 int skptok(int j, char *ptr) {
596 for (; ptr[j] != 0 && ptr[j] != ' ' && ptr[j] != '\t' ; j++);
597 for (; ptr[j] != 0 && (ptr[j] == ' ' || ptr[j] == '\t'); j++);
599 return j;
600 }