bday
view bday.c @ 8:19c1ad697022
beautifing :-)
author | meillo@marmaro.de |
---|---|
date | Tue, 18 Dec 2007 14:56:05 +0100 |
parents | b6f4c7fba64a |
children | 4f48b4f86e3d |
line source
1 /*
2 bday
4 Birthday/Anniversary reminder
6 (c) 1996 AS Mortimer
7 (c) 2007 markus schnalke <meillo@marmaro.de>
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.
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 ===============================================================================
24 Input is read through standard input. For example: bday < ~/.birthdays
25 The input (file) has to have the following format:
27 text=date flags
29 where:
30 date is yyyy-mm-dd
31 flags is ONE or ZERO of
32 bd for a birthday (default)
33 ann for an anniversary
34 ev for an event
35 and zero or more of
36 w <n> to set the warn-in-advance time to n days (don't include the
37 brackets! :)
38 to <date>
39 for <days>
40 to specify the length of time taken by an event, for example a holiday.
42 Lines preceeded by # are treated as comments.
44 Note: If you deviate from this format, I cannot guarantee anything about
45 it's behaviour. In most cases, it will just quietly ignore the error,
46 which probably isn't ideal behaviour. Oh, well.
48 ===============================================================================
49 */
52 /* standard time to warn in advance, when no explicit w flag is given. */
53 #define DEF_WARN 14
56 #include <stdarg.h>
57 #include <stdio.h>
58 #include <stdlib.h>
59 #include <string.h>
60 #include <sys/types.h>
61 #include <time.h>
62 #include <unistd.h>
66 /* ========== Global constants and data types */
69 /* month lengths etc */
70 #define isleapyear(y) ((y)%4==0 && ((y)%100 != 0 || (y)%400 == 0))
71 const unsigned MLENDAT[];
72 #define mlen(m,y) (MLENDAT[(m)-1] != -1 ? MLENDAT[(m)-1] : (isleapyear((y)) ? 29 : 28))
73 #define before(a,b) ((a).month < (b).month || ((a).month == (b).month && (a).day < (b).day))
74 #define ydelta(a,b) ((int) (b).year - (a).year + before((a),(b)))
76 /* -------- modifier flags */
77 #define F_MTYPE 0x07
78 #define F_TBIRTHDAY 1
79 #define F_TANNIVERSARY 2
80 #define F_TEVENT 3
82 /* flags processed immediately on encountering */
83 #define F_MIMMEDIATE 0x24
84 #define F_WTIME_P 0x08
85 #define F_FORDAYS 0x16
86 #define F_TODATE 0x24
88 struct _ftable {char* txt; unsigned flag;};
90 const struct _ftable FTABLE[];
92 struct date {
93 unsigned day;
94 unsigned month;
95 unsigned year;
96 };
98 struct event {
99 char* text;
100 struct date date;
101 struct date enddate;
102 int warn;
103 };
105 typedef int (*prnfunc)(const char *);
107 /* ========== Global Variables */
109 struct event* readlist(void);
110 void gettoday(void);
111 unsigned delta(struct date*);
112 unsigned ddiff(struct date* D1, struct date* D2);
113 void liststrings(struct event* evl, prnfunc outf);
114 char* tdelta(struct date* d);
115 char* ttime(int yr, int mn, int wk, int dy);
116 int skptok(int j, char* ptr);
117 int evcmp(const void* e1, const void* e2);
120 struct date today;
121 int iDWarn = DEF_WARN;
123 const unsigned MLENDAT[]={31,-1,31,30,31,30,31,31,30,31,30,31};
125 const struct _ftable FTABLE[] = {
126 {"bd", F_TBIRTHDAY},
127 {"ann",F_TANNIVERSARY},
128 {"ev", F_TEVENT},
129 {"w", F_WTIME_P},
130 {"to", F_TODATE},
131 {"for", F_FORDAYS},
132 {NULL, 0}
133 };
140 /*
141 xmalloc/xrealloc functions
142 Note: the x* functions are lifted straight from the GNU libc info docs
143 $Id: xmalloc.c,v 1.2 1999/01/16 17:08:59 andy Exp $
144 */
146 void* xmalloc (size_t size) {
147 register void* value = malloc (size);
148 if (value == 0) {
149 fprintf(stderr, "virtual memory exhausted\n");
150 exit(1);
151 }
152 return value;
153 }
155 void* xrealloc (void* ptr, size_t size) {
156 register void* value = realloc (ptr, size);
157 if (value == 0) {
158 fprintf(stderr, "virtual memory exhausted\n");
159 exit(1);
160 }
161 return value;
162 }
165 /* ========== */
168 /* like strcat(), but lets the buffer automagically grow :-)
169 * (needs local variable "size" with the buffer size) */
170 #define append(where, what) do { \
171 if (strlen(what) > (size - strlen(where))) { \
172 xrealloc(where, size + 128 + strlen(what)); \
173 size += 128 + strlen(what); \
174 } \
175 strcat(where, what); \
176 } while(0)
178 /* ========== */
180 /* returns delta(d) in days, weeks, months, etc
181 * the returned buffer is malloc()ed, do not forget to free() it */
182 char* tdelta(struct date* d) {
183 int dy, wk, mn, yr;
184 char* tmp;
185 char* buf = xmalloc(128);
186 int size = 128;
187 *buf = 0;
189 switch (delta(d)) {
190 case 0:
191 append(buf, "today");
192 return buf;
193 case 1:
194 append(buf, "tomorrow");
195 return buf;
196 default:
197 /* like delta(), we ignore the year */
198 yr = -before(*d, today);
199 mn = d->month - today.month;
200 dy = d->day - today.day;
202 if (dy < 0) {
203 dy += mlen(today.month, today.year);
204 mn--;
205 }
206 if (mn < 0) {
207 mn += 12;
208 yr++;
209 }
211 wk = (dy / 7);
212 dy %= 7;
214 append(buf, "in ");
215 tmp = ttime(yr, mn, wk, dy);
216 append(buf, tmp);
217 free(tmp);
219 return buf;
220 }
221 }
227 /*
228 void donum(n,txt) {
229 do {
230 if (n > 0) {
231 snprintf(tmp, sizeof(tmp), "%d", n);
232 append(buf, tmp);
233 append(buf, " " txt);
234 if (n != 1)
235 append(buf, "s");
236 terms--;
237 if (orgterms > 1) {
238 if (terms == 1)
239 append(buf, " and ");
240 else if (terms > 1)
241 append(buf, ", ");
242 }
243 }
244 } while(0)
245 }
246 */
249 #define donum(n,txt) do { \
250 if (n > 0) { \
251 snprintf(tmp, sizeof(tmp), "%d", n); \
252 append(buf, tmp); \
253 append(buf, " " txt); \
254 if (n != 1) \
255 append(buf, "s"); \
256 terms--; \
257 if (orgterms > 1) { \
258 if (terms == 1) \
259 append(buf, " and "); \
260 else if (terms > 1) \
261 append(buf, ", "); \
262 } \
263 } \
264 } while(0)
267 /* returns allocated buffer, don't forget to free() */
268 char* ttime(int yr, int mn, int wk, int dy) {
269 char* buf = xmalloc(128);
270 int size = 128;
271 int terms, orgterms;
272 char tmp[128];
274 *buf = 0; /* Initialize buffer */
275 terms = orgterms = (yr!=0) + (mn!=0) + (wk!=0) + (dy!=0);
277 donum(yr, "year");
278 donum(mn, "month");
279 donum(wk, "week");
280 donum(dy, "day");
282 return buf;
283 }
284 #undef donum
291 /* lists the birthdays in their string format, one by one, and passes the string to a function. */
292 void liststrings(struct event* evl, prnfunc outf) {
293 int i,j;
294 char *buf, *tmp;
295 int size;
297 for (i = 0; evl[i].text != NULL; i++) {
298 buf = xmalloc(128);
299 *buf = '\0';
300 size = 128;
302 if (evl[i].warn == -1 && delta(&(evl[i].date))==0) {
303 append(buf, evl[i].text);
304 } else if (evl[i].enddate.day == 0) {
305 if (delta(&(evl[i].date)) <= evl[i].warn) {
306 append(buf, evl[i].text);
307 append(buf, " ");
308 tmp = tdelta(&(evl[i].date));
309 append(buf, tmp);
310 free(tmp);
311 }
312 } else {
313 if (delta(&(evl[i].date)) <= evl[i].warn) {
314 append(buf, evl[i].text);
315 append(buf, " for ");
316 /* +1 because, if the difference between two dates is one day, then the length of an event on those days is two days */
317 j = ddiff(&(evl[i].date),&(evl[i].enddate)) + 1;
318 tmp = ttime(0, 0, j/7, j%7);
319 append(buf, tmp);
320 free(tmp);
321 append(buf, " ");
322 tmp = tdelta(&(evl[i].date));
323 append(buf, tmp);
324 } else if (delta(&(evl[i].enddate)) <= evl[i].warn) {
325 append(buf, evl[i].text);
326 append(buf, " ");
327 j = delta(&(evl[i].enddate));
328 if (j) {
329 append(buf, "for ");
330 tmp = ttime(0, 0, j/7, j%7);
331 append(buf, tmp);
332 free(tmp);
333 append(buf, " longer");
334 } else {
335 append(buf, "finishes today");
336 }
337 }
338 }
339 if (*buf) {
340 append(buf, ".");
341 outf(buf);
342 }
343 free(buf);
344 }
345 }
354 /* sort the events by the time before the next time they come up, putting those
355 where the start has passed but we are still in the time-period first */
356 int evcmp(const void* p1, const void* p2) {
357 struct event* e1=(struct event*) p1;
358 struct event* e2=(struct event*) p2;
359 unsigned d1, d2;
361 /* if the delta for the enddate is less than that for the start date, then we
362 have passed the start date but not yet the end date, and so we should
363 display the enddate; otherwise, we should display the start date */
365 d1=delta(&(e1->date));
366 if (e1->enddate.day && delta(&(e1->enddate)) < d1)
367 d1=delta(&(e1->enddate));
369 d2=delta(&(e2->date));
370 if (e2->enddate.day && delta(&(e2->enddate)) < d2)
371 d2=delta(&(e2->enddate));
373 if (d1 < d2) return -1;
374 if (d1 > d2) return 1;
376 return strcmp(e1->text, e2->text);
377 }
384 /* difference in days between two dates */
385 /* it is assumed that D1 < D2, and so the result is always positive */
386 unsigned ddiff(struct date* D1, struct date* D2) {
387 struct date d1, d2;
388 int dd, m;
390 /* make working copies */
391 d1 = *D1;
392 d2 = *D2;
394 /* sort out zero years */
395 if (d1.year == 0 || d2.year==0) {
396 if (d1.year != d2.year) {
397 if (d1.year == 0) {
398 if (before(d1,d2))
399 d1.year = d2.year;
400 else
401 d1.year = d2.year - 1;
402 } else {
403 if (before(d1, d2))
404 d2.year = d1.year;
405 else
406 d2.year = d1.year + 1;
407 }
408 } else { /* both years zero */
409 if (before(d1, d2))
410 d1.year = d2.year = today.year;
411 else {
412 d1.year = today.year;
413 d2.year = d1.year + 1;
414 }
415 }
416 }
418 /* now we can actually do the comparison ... */
419 dd = 0;
421 /* to start with, we work in months */
422 for (m=d1.month; m < d2.month + (d2.year-d1.year)*12; m++)
423 dd += mlen(((m-1)%12)+1, d1.year + m/12);
425 /* and then we renormalise for the days within the months */
426 /* the first month was included in our calculations */
427 dd -= d1.day;
428 /* but the last one wasn't */
429 dd += d2.day;
431 return dd;
432 }
441 /* actually until the next anniversary of ... */
442 unsigned delta(struct date *date) {
443 struct date d;
444 unsigned dt, mn;
446 memcpy(&d, date, sizeof(struct date));
448 /* past the end of the year */
449 if (before(d, today)) {
450 d.year = 1;
451 } else {
452 d.year = 0;
453 }
455 for (mn = today.month, dt=0; mn < d.month + 12*d.year; mn++) {
456 dt += mlen(((mn-1)%12) + 1,today.year + mn/12);
457 }
459 dt -= today.day;
460 dt += d.day;
462 return dt;
463 }
470 void gettoday(void) {
471 struct tm *tm;
472 time_t t;
474 time(&t);
475 tm = localtime(&t);
476 today.day = tm->tm_mday;
477 today.month = tm->tm_mon + 1; /* 1-12 instead of 0-11 */
478 today.year = tm->tm_year + 1900;
479 }
490 struct event* readlist() {
491 int i, j, k, l, d;
492 struct event *evl;
493 char buf[1024], buf2[1024];
494 char *ptr;
495 unsigned flags;
497 /* initialise */
498 gettoday();
500 for (i = 0, evl = NULL; fgets(buf, sizeof(buf), stdin) != NULL; i++) {
501 evl = (struct event *) xrealloc(evl, sizeof(struct event) * (i + 1));
503 /* ignore comments and empty lines */
504 if (*buf == '#' || *buf == '\n') {
505 i--;
506 continue;
507 }
509 /* parse string in buf */
510 ptr = strrchr(buf, '='); /* allow '=' in text */
512 /* not a valid line, so ignore it! Cool, huh? */
513 /* Attention: only recognizes lines without '=' */
514 if (ptr == NULL) {
515 fprintf(stderr, "WARNING: Invalid line in input:\n%s", buf);
516 i--;
517 continue;
518 }
520 *(ptr++) = 0;
522 j = sscanf(ptr, "%u-%u-%u", &(evl[i].date.year), &(evl[i].date.month), &(evl[i].date.day));
523 /* ... unless it wasn't read, in which case set it to zero */
524 if (j==2) {
525 evl[i].date.year = 0;
526 }
529 /* parse flags */
531 evl[i].warn = iDWarn;
532 evl[i].enddate.day = 0;
533 evl[i].enddate.month = 0;
534 evl[i].enddate.year = 0;
536 flags = 0;
537 j = 0;
539 while(j = skptok(j, ptr), ptr[j] != 0) {
540 for (k = 0; FTABLE[k].txt != NULL && strncmp(FTABLE[k].txt, ptr + j, strlen(FTABLE[k].txt)); k++) {
541 }
543 switch (FTABLE[k].flag) {
544 case F_WTIME_P: /* w <n> -- sets warning time */
545 sscanf(ptr + j, "w %u", &(evl[i].warn));
546 break;
547 case F_FORDAYS: /* for <days> -- sets the duration of the event */
548 sscanf(ptr + j, "for %u", &d);
549 evl[i].enddate=evl[i].date;
550 for (l = 1; l < d; l++) {
551 evl[i].enddate.day++;
552 if (evl[i].enddate.day > mlen(evl[i].enddate.month, evl[i].enddate.year)) {
553 evl[i].enddate.month++;
554 evl[i].enddate.day = 1;
555 }
556 if (evl[i].enddate.month > 12) {
557 evl[i].enddate.year++;
558 evl[i].enddate.month = 1;
559 }
560 }
561 break;
562 case F_TODATE: /* to <date> -- sets the end date of the event */
563 l = sscanf(ptr + j, "to %u-%u-%u", &(evl[i].enddate.year), &(evl[i].enddate.month), &(evl[i].enddate.day));
564 if (l == 2) {
565 evl[i].enddate.year = 0;
566 }
567 break;
568 case 0:
569 break;
570 default:
571 flags |= FTABLE[k].flag;
572 break;
573 }
574 }
577 /* construct event text */
579 switch(flags & F_MTYPE) {
580 case F_TBIRTHDAY:
581 default: /* assume it's a birthday */
582 if (evl[i].date.year != 0) {
583 int tmp_age = ydelta(evl[i].date, today);
584 if (tmp_age != 1) {
585 sprintf(buf2, "%s is %d years old", buf, tmp_age);
586 } else {
587 sprintf(buf2, "%s is %d year old", buf, tmp_age);
588 }
589 } else {
590 sprintf(buf2, "%s has a birthday", buf);
591 }
592 break;
593 case F_TANNIVERSARY:
594 if (evl[i].date.year != 0) {
595 sprintf(buf2, "%s %d years ago", buf, ydelta(evl[i].date, today));
596 } else {
597 strcpy(buf2, buf);
598 }
599 break;
600 case F_TEVENT:
601 /* if a year was specified, and this warning isn't for it, ignore! */
602 if ((evl[i].date.year != 0 && ydelta(evl[i].date, today) != 0) && (evl[i].enddate.year == 0 || ydelta(evl[i].enddate, today) != 0)) {
603 i--;
604 continue;
605 }
606 strcpy(buf2, buf);
607 break;
608 }
609 evl[i].text = strdup(buf2);
610 }
612 evl = (struct event *) xrealloc(evl, sizeof(struct event) * (i + 1));
613 evl[i].date.day = 0;
614 evl[i].date.month = 0;
615 evl[i].date.year = 0;
616 evl[i].text = (char *) NULL;
618 fclose(stdin);
620 /* NB uses i from above */
621 qsort(evl, i, sizeof(struct event), evcmp);
622 return evl;
623 }
629 int skptok(int j, char *ptr) {
630 for (; ptr[j] != 0 && ptr[j] != ' ' && ptr[j] != '\t' ; j++);
631 for (; ptr[j] != 0 && (ptr[j] == ' ' || ptr[j] == '\t'); j++);
633 return j;
634 }
641 int main(int argc, char* argv[]) {
643 while (--argc > 0 && (*++argv)[0] == '-') {
644 if (strcmp(argv[0], "-W") == 0) {
645 /* TODO: catch if no value given */
646 iDWarn = atoi((++argv)[0]);
647 argc--;
648 } else {
649 fprintf(stderr, "unknown option %s\n", argv[0]);
650 exit(1);
651 }
652 }
654 liststrings(readlist(), puts);
656 return 0;
657 }