bday

view bday.c @ 14:a56120a4678f

wrapping of long lines
author meillo@marmaro.de
date Thu, 13 Nov 2008 13:31:33 +0100
parents 4f48b4f86e3d
children 032af48d590b
line source
1 /*
2 bday
4 Birthday/Anniversary reminder
6 (c) 2007 markus schnalke <meillo@marmaro.de>
7 (c) 1994-1999 AS Mortimer
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License as
11 published by the Free Software Foundation; either version 2 of the
12 License, or (at your option) any later version. You may also
13 distribute it under the Artistic License, as comes with Perl.
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 You should also have recieved a copy of the Artistic license with
24 this program.
26 ===============================================================================
28 Input is read through standard input. For example: bday < ~/.birthdays
29 The input (file) has to have the following format:
31 text=date flags
33 where:
34 date is yyyy-mm-dd
35 flags is ONE or ZERO of
36 bd for a birthday (default)
37 ann for an anniversary
38 ev for an event
39 and zero or more of
40 w <n> to set the warn-in-advance time to n days (don't include the
41 brackets! :)
42 to <date>
43 for <days>
44 to specify the length of time taken by an event, for example a holiday.
46 Lines preceeded by # are treated as comments.
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 ===============================================================================
53 */
56 /* standard time to warn in advance, when no explicit w flag is given. */
57 #define DEF_WARN 14
60 #include <stdarg.h>
61 #include <stdio.h>
62 #include <stdlib.h>
63 #include <string.h>
64 #include <sys/types.h>
65 #include <time.h>
66 #include <unistd.h>
70 /* ========== Global constants and data types */
73 /* month lengths etc */
74 #define isleapyear(y) ((y)%4==0 && ((y)%100 != 0 || (y)%400 == 0))
75 const unsigned MLENDAT[];
76 #define mlen(m,y) (MLENDAT[(m)-1] != -1 ? MLENDAT[(m)-1] : (isleapyear((y)) ? 29 : 28))
77 #define before(a,b) ((a).month < (b).month || ((a).month == (b).month && (a).day < (b).day))
78 #define ydelta(a,b) ((int) (b).year - (a).year + before((a),(b)))
80 /* -------- modifier flags */
81 #define F_MTYPE 0x07
82 #define F_TBIRTHDAY 1
83 #define F_TANNIVERSARY 2
84 #define F_TEVENT 3
86 /* flags processed immediately on encountering */
87 #define F_MIMMEDIATE 0x24
88 #define F_WTIME_P 0x08
89 #define F_FORDAYS 0x16
90 #define F_TODATE 0x24
92 struct _ftable {char* txt; unsigned flag;};
94 const struct _ftable FTABLE[];
96 struct date {
97 unsigned day;
98 unsigned month;
99 unsigned year;
100 };
102 struct event {
103 char* text;
104 struct date date;
105 struct date enddate;
106 int warn;
107 };
109 typedef int (*prnfunc)(const char *);
111 /* ========== Global Variables */
113 struct event* readlist(void);
114 void gettoday(void);
115 unsigned delta(struct date*);
116 unsigned ddiff(struct date* D1, struct date* D2);
117 void liststrings(struct event* evl, prnfunc outf);
118 char* tdelta(struct date* d);
119 char* ttime(int yr, int mn, int wk, int dy);
120 int skptok(int j, char* ptr);
121 int evcmp(const void* e1, const void* e2);
124 struct date today;
125 int iDWarn = DEF_WARN;
127 const unsigned MLENDAT[]={31,-1,31,30,31,30,31,31,30,31,30,31};
129 const struct _ftable FTABLE[] = {
130 {"bd", F_TBIRTHDAY},
131 {"ann",F_TANNIVERSARY},
132 {"ev", F_TEVENT},
133 {"w", F_WTIME_P},
134 {"to", F_TODATE},
135 {"for", F_FORDAYS},
136 {NULL, 0}
137 };
144 /*
145 xmalloc/xrealloc functions
146 Note: the x* functions are lifted straight from the GNU libc info docs
147 $Id: xmalloc.c,v 1.2 1999/01/16 17:08:59 andy Exp $
148 */
150 void* xmalloc (size_t size) {
151 register void* value = malloc (size);
152 if (value == 0) {
153 fprintf(stderr, "virtual memory exhausted\n");
154 exit(1);
155 }
156 return value;
157 }
159 void* xrealloc (void* ptr, size_t size) {
160 register void* value = realloc (ptr, size);
161 if (value == 0) {
162 fprintf(stderr, "virtual memory exhausted\n");
163 exit(1);
164 }
165 return value;
166 }
169 /* ========== */
172 /* like strcat(), but lets the buffer automagically grow :-)
173 * (needs local variable "size" with the buffer size) */
174 #define append(where, what) do { \
175 if (strlen(what) > (size - strlen(where))) { \
176 xrealloc(where, size + 128 + strlen(what)); \
177 size += 128 + strlen(what); \
178 } \
179 strcat(where, what); \
180 } while(0)
182 /* ========== */
184 /* returns delta(d) in days, weeks, months, etc
185 * the returned buffer is malloc()ed, do not forget to free() it */
186 char* tdelta(struct date* d) {
187 int dy, wk, mn, yr;
188 char* tmp;
189 char* buf = xmalloc(128);
190 int size = 128;
191 *buf = 0;
193 switch (delta(d)) {
194 case 0:
195 append(buf, "today");
196 return buf;
197 case 1:
198 append(buf, "tomorrow");
199 return buf;
200 default:
201 /* like delta(), we ignore the year */
202 yr = -before(*d, today);
203 mn = d->month - today.month;
204 dy = d->day - today.day;
206 if (dy < 0) {
207 dy += mlen(today.month, today.year);
208 mn--;
209 }
210 if (mn < 0) {
211 mn += 12;
212 yr++;
213 }
215 wk = (dy / 7);
216 dy %= 7;
218 append(buf, "in ");
219 tmp = ttime(yr, mn, wk, dy);
220 append(buf, tmp);
221 free(tmp);
223 return buf;
224 }
225 }
231 /*
232 void donum(n,txt) {
233 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)
249 }
250 */
253 #define donum(n,txt) do { \
254 if (n > 0) { \
255 snprintf(tmp, sizeof(tmp), "%d", n); \
256 append(buf, tmp); \
257 append(buf, " " txt); \
258 if (n != 1) \
259 append(buf, "s"); \
260 terms--; \
261 if (orgterms > 1) { \
262 if (terms == 1) \
263 append(buf, " and "); \
264 else if (terms > 1) \
265 append(buf, ", "); \
266 } \
267 } \
268 } while(0)
271 /* returns allocated buffer, don't forget to free() */
272 char* ttime(int yr, int mn, int wk, int dy) {
273 char* buf = xmalloc(128);
274 int size = 128;
275 int terms, orgterms;
276 char tmp[128];
278 *buf = 0; /* Initialize buffer */
279 terms = orgterms = (yr!=0) + (mn!=0) + (wk!=0) + (dy!=0);
281 donum(yr, "year");
282 donum(mn, "month");
283 donum(wk, "week");
284 donum(dy, "day");
286 return buf;
287 }
288 #undef donum
295 /* lists the birthdays in their string format, one by one, and passes the string to a function. */
296 void liststrings(struct event* evl, prnfunc outf) {
297 int i,j;
298 char *buf, *tmp;
299 int size;
301 for (i = 0; evl[i].text != NULL; i++) {
302 buf = xmalloc(128);
303 *buf = '\0';
304 size = 128;
306 if (evl[i].warn == -1 && delta(&(evl[i].date))==0) {
307 append(buf, evl[i].text);
308 } else if (evl[i].enddate.day == 0) {
309 if (delta(&(evl[i].date)) <= evl[i].warn) {
310 append(buf, evl[i].text);
311 append(buf, " ");
312 tmp = tdelta(&(evl[i].date));
313 append(buf, tmp);
314 free(tmp);
315 }
316 } else {
317 if (delta(&(evl[i].date)) <= evl[i].warn) {
318 append(buf, evl[i].text);
319 append(buf, " for ");
320 /* +1 because, if the difference between two dates is one day,
321 then the length of an event on those days is two days */
322 j = ddiff(&(evl[i].date),&(evl[i].enddate)) + 1;
323 tmp = ttime(0, 0, j/7, j%7);
324 append(buf, tmp);
325 free(tmp);
326 append(buf, " ");
327 tmp = tdelta(&(evl[i].date));
328 append(buf, tmp);
329 } else if (delta(&(evl[i].enddate)) <= evl[i].warn) {
330 append(buf, evl[i].text);
331 append(buf, " ");
332 j = delta(&(evl[i].enddate));
333 if (j) {
334 append(buf, "for ");
335 tmp = ttime(0, 0, j/7, j%7);
336 append(buf, tmp);
337 free(tmp);
338 append(buf, " longer");
339 } else {
340 append(buf, "finishes today");
341 }
342 }
343 }
344 if (*buf) {
345 append(buf, ".");
346 outf(buf);
347 }
348 free(buf);
349 }
350 }
359 /* sort the events by the time before the next time they come up, putting those
360 where the start has passed but we are still in the time-period first */
361 int evcmp(const void* p1, const void* p2) {
362 struct event* e1=(struct event*) p1;
363 struct event* e2=(struct event*) p2;
364 unsigned d1, d2;
366 /* if the delta for the enddate is less than that for the start date, then we
367 have passed the start date but not yet the end date, and so we should
368 display the enddate; otherwise, we should display the start date */
370 d1=delta(&(e1->date));
371 if (e1->enddate.day && delta(&(e1->enddate)) < d1)
372 d1=delta(&(e1->enddate));
374 d2=delta(&(e2->date));
375 if (e2->enddate.day && delta(&(e2->enddate)) < d2)
376 d2=delta(&(e2->enddate));
378 if (d1 < d2) return -1;
379 if (d1 > d2) return 1;
381 return strcmp(e1->text, e2->text);
382 }
389 /* difference in days between two dates */
390 /* it is assumed that D1 < D2, and so the result is always positive */
391 unsigned ddiff(struct date* D1, struct date* D2) {
392 struct date d1, d2;
393 int dd, m;
395 /* make working copies */
396 d1 = *D1;
397 d2 = *D2;
399 /* sort out zero years */
400 if (d1.year == 0 || d2.year==0) {
401 if (d1.year != d2.year) {
402 if (d1.year == 0) {
403 if (before(d1,d2))
404 d1.year = d2.year;
405 else
406 d1.year = d2.year - 1;
407 } else {
408 if (before(d1, d2))
409 d2.year = d1.year;
410 else
411 d2.year = d1.year + 1;
412 }
413 } else { /* both years zero */
414 if (before(d1, d2))
415 d1.year = d2.year = today.year;
416 else {
417 d1.year = today.year;
418 d2.year = d1.year + 1;
419 }
420 }
421 }
423 /* now we can actually do the comparison ... */
424 dd = 0;
426 /* to start with, we work in months */
427 for (m=d1.month; m < d2.month + (d2.year-d1.year)*12; m++)
428 dd += mlen(((m-1)%12)+1, d1.year + m/12);
430 /* and then we renormalise for the days within the months */
431 /* the first month was included in our calculations */
432 dd -= d1.day;
433 /* but the last one wasn't */
434 dd += d2.day;
436 return dd;
437 }
446 /* actually until the next anniversary of ... */
447 unsigned delta(struct date *date) {
448 struct date d;
449 unsigned dt, mn;
451 memcpy(&d, date, sizeof(struct date));
453 /* past the end of the year */
454 if (before(d, today)) {
455 d.year = 1;
456 } else {
457 d.year = 0;
458 }
460 for (mn = today.month, dt=0; mn < d.month + 12*d.year; mn++) {
461 dt += mlen(((mn-1)%12) + 1,today.year + mn/12);
462 }
464 dt -= today.day;
465 dt += d.day;
467 return dt;
468 }
475 void gettoday(void) {
476 struct tm *tm;
477 time_t t;
479 time(&t);
480 tm = localtime(&t);
481 today.day = tm->tm_mday;
482 today.month = tm->tm_mon + 1; /* 1-12 instead of 0-11 */
483 today.year = tm->tm_year + 1900;
484 }
495 struct event* readlist() {
496 int i, j, k, l, d;
497 struct event *evl;
498 char buf[1024], buf2[1024];
499 char *ptr;
500 unsigned flags;
502 /* initialise */
503 gettoday();
505 for (i = 0, evl = NULL; fgets(buf, sizeof(buf), stdin) != NULL; i++) {
506 evl = (struct event *) xrealloc(evl, sizeof(struct event) * (i + 1));
508 /* ignore comments and empty lines */
509 if (*buf == '#' || *buf == '\n') {
510 i--;
511 continue;
512 }
514 /* parse string in buf */
515 ptr = strrchr(buf, '='); /* allow '=' in text */
517 /* not a valid line, so ignore it! Cool, huh? */
518 /* Attention: only recognizes lines without '=' */
519 if (ptr == NULL) {
520 fprintf(stderr, "WARNING: Invalid line in input:\n%s", buf);
521 i--;
522 continue;
523 }
525 *(ptr++) = 0;
527 j = sscanf(ptr, "%u-%u-%u", &(evl[i].date.year), &(evl[i].date.month), &(evl[i].date.day));
528 /* ... unless it wasn't read, in which case set it to zero */
529 if (j==2) {
530 evl[i].date.year = 0;
531 }
534 /* parse flags */
536 evl[i].warn = iDWarn;
537 evl[i].enddate.day = 0;
538 evl[i].enddate.month = 0;
539 evl[i].enddate.year = 0;
541 flags = 0;
542 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 }
548 switch (FTABLE[k].flag) {
549 case F_WTIME_P: /* w <n> -- sets warning time */
550 sscanf(ptr + j, "w %u", &(evl[i].warn));
551 break;
552 case F_FORDAYS: /* for <days> -- sets the duration of the event */
553 sscanf(ptr + j, "for %u", &d);
554 evl[i].enddate=evl[i].date;
555 for (l = 1; l < d; l++) {
556 evl[i].enddate.day++;
557 if (evl[i].enddate.day > mlen(evl[i].enddate.month, evl[i].enddate.year)) {
558 evl[i].enddate.month++;
559 evl[i].enddate.day = 1;
560 }
561 if (evl[i].enddate.month > 12) {
562 evl[i].enddate.year++;
563 evl[i].enddate.month = 1;
564 }
565 }
566 break;
567 case F_TODATE: /* to <date> -- sets the end date of the event */
568 l = sscanf(ptr + j, "to %u-%u-%u", &(evl[i].enddate.year), &(evl[i].enddate.month), &(evl[i].enddate.day));
569 if (l == 2) {
570 evl[i].enddate.year = 0;
571 }
572 break;
573 case 0:
574 break;
575 default:
576 flags |= FTABLE[k].flag;
577 break;
578 }
579 }
582 /* construct event text */
584 switch(flags & F_MTYPE) {
585 case F_TBIRTHDAY:
586 default: /* assume it's a birthday */
587 if (evl[i].date.year != 0) {
588 int tmp_age = ydelta(evl[i].date, today);
589 if (tmp_age != 1) {
590 sprintf(buf2, "%s is %d years old", buf, tmp_age);
591 } else {
592 sprintf(buf2, "%s is %d year old", buf, tmp_age);
593 }
594 } else {
595 sprintf(buf2, "%s has a birthday", buf);
596 }
597 break;
598 case F_TANNIVERSARY:
599 if (evl[i].date.year != 0) {
600 sprintf(buf2, "%s %d years ago", buf, ydelta(evl[i].date, today));
601 } else {
602 strcpy(buf2, buf);
603 }
604 break;
605 case F_TEVENT:
606 /* if a year was specified, and this warning isn't for it, ignore! */
607 if ((evl[i].date.year != 0 && ydelta(evl[i].date, today) != 0)
608 && (evl[i].enddate.year == 0 || ydelta(evl[i].enddate, today) != 0)) {
609 i--;
610 continue;
611 }
612 strcpy(buf2, buf);
613 break;
614 }
615 evl[i].text = strdup(buf2);
616 }
618 evl = (struct event *) xrealloc(evl, sizeof(struct event) * (i + 1));
619 evl[i].date.day = 0;
620 evl[i].date.month = 0;
621 evl[i].date.year = 0;
622 evl[i].text = (char *) NULL;
624 fclose(stdin);
626 /* NB uses i from above */
627 qsort(evl, i, sizeof(struct event), evcmp);
628 return evl;
629 }
635 int skptok(int j, char *ptr) {
636 for (; ptr[j] != 0 && ptr[j] != ' ' && ptr[j] != '\t' ; j++);
637 for (; ptr[j] != 0 && (ptr[j] == ' ' || ptr[j] == '\t'); j++);
639 return j;
640 }
647 int main(int argc, char* argv[]) {
649 while (--argc > 0 && (*++argv)[0] == '-') {
650 if (strcmp(argv[0], "-W") == 0) {
651 /* TODO: catch if no value given */
652 iDWarn = atoi((++argv)[0]);
653 argc--;
654 } else {
655 fprintf(stderr, "unknown option %s\n", argv[0]);
656 exit(1);
657 }
658 }
660 liststrings(readlist(), puts);
662 return 0;
663 }