bday

view bday.c @ 15:032af48d590b

reformating of the source code
author markus schnalke <meillo@marmaro.de>
date Mon, 24 Feb 2014 17:46:57 +0100
parents a56120a4678f
children 79d22407a6be
line source
1 /*
2 bday -- Birthday/Anniversary reminder
4 (c) 2007,2014 markus schnalke <meillo@marmaro.de>
5 (c) 1994-1999 AS Mortimer
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License as
9 published by the Free Software Foundation; either version 2 of the
10 License, or (at your option) any later version. You may also
11 distribute it under the Artistic License, as comes with Perl.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 You should also have recieved a copy of the Artistic license with
22 this program.
24 =====================================================================
26 Input is read through standard input. For example: bday < ~/.birthdays
27 The input (file) has to have the following format:
29 text=date flags
31 where:
32 date is yyyy-mm-dd
33 flags is ONE or ZERO of
34 bd for a birthday (default)
35 ann for an anniversary
36 ev for an event
37 and zero or more of
38 w <n> to set the warn-in-advance time to n days
39 (don't include the brackets! :)
40 to <date>
41 for <days>
42 to specify the length of time taken by an
43 event, for example a holiday.
45 Lines preceeded by # are treated as comments.
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
49 error, which probably isn't ideal behaviour. Oh, well.
51 =====================================================================
52 */
55 /* standard time to warn in advance, when no explicit w flag is given. */
56 #define DEF_WARN 14
59 #include <stdarg.h>
60 #include <stdio.h>
61 #include <stdlib.h>
62 #include <string.h>
63 #include <sys/types.h>
64 #include <time.h>
65 #include <unistd.h>
69 /* ========== Global constants and data types */
72 /* month lengths etc */
73 #define isleapyear(y) ((y)%4==0 && ((y)%100 != 0 || (y)%400 == 0))
74 const unsigned MLENDAT[];
75 #define mlen(m,y) (MLENDAT[(m)-1] != -1 ? MLENDAT[(m)-1] : (isleapyear((y)) ? 29 : 28))
76 #define before(a,b) ((a).month < (b).month || ((a).month == (b).month && (a).day < (b).day))
77 #define ydelta(a,b) ((int) (b).year - (a).year + before((a),(b)))
79 /* -------- modifier flags */
80 #define F_MTYPE 0x07
81 #define F_TBIRTHDAY 1
82 #define F_TANNIVERSARY 2
83 #define F_TEVENT 3
85 /* flags processed immediately on encountering */
86 #define F_MIMMEDIATE 0x24
87 #define F_WTIME_P 0x08
88 #define F_FORDAYS 0x16
89 #define F_TODATE 0x24
91 struct _ftable {char* txt; unsigned flag;};
93 const struct _ftable FTABLE[];
95 struct date {
96 unsigned day;
97 unsigned month;
98 unsigned year;
99 };
101 struct event {
102 char* text;
103 struct date date;
104 struct date enddate;
105 int warn;
106 };
108 typedef int (*prnfunc)(const char *);
110 /* ========== Global Variables */
112 struct event *readlist(void);
113 void gettoday(void);
114 unsigned delta(struct date *);
115 unsigned ddiff(struct date *D1, struct date *D2);
116 void liststrings(struct event *evl, prnfunc outf);
117 char *tdelta(struct date *d);
118 char *ttime(int yr, int mn, int wk, int dy);
119 int skptok(int j, char *ptr);
120 int evcmp(const void *e1, const void *e2);
123 struct date today;
124 int iDWarn = DEF_WARN;
126 const unsigned MLENDAT[] = {31,-1,31,30,31,30,31,31,30,31,30,31};
128 const struct _ftable FTABLE[] = {
129 {"bd", F_TBIRTHDAY},
130 {"ann",F_TANNIVERSARY},
131 {"ev", F_TEVENT},
132 {"w", F_WTIME_P},
133 {"to", F_TODATE},
134 {"for", F_FORDAYS},
135 {NULL, 0}
136 };
143 /*
144 xmalloc/xrealloc functions
145 Note: the x* functions are lifted straight from the GNU libc info docs
146 $Id: xmalloc.c,v 1.2 1999/01/16 17:08:59 andy Exp $
147 */
149 void *
150 xmalloc(size_t size)
151 {
152 register void *value = malloc (size);
153 if (value == 0) {
154 fprintf(stderr, "virtual memory exhausted\n");
155 exit(1);
156 }
157 return value;
158 }
160 void *
161 xrealloc(void *ptr, size_t size)
162 {
163 register void *value = realloc (ptr, size);
164 if (value == 0) {
165 fprintf(stderr, "virtual memory exhausted\n");
166 exit(1);
167 }
168 return value;
169 }
172 /* ========== */
175 /*
176 like strcat(), but lets the buffer automagically grow :-)
177 (needs local variable "size" with the buffer size)
178 */
179 #define append(where, what) do { \
180 if (strlen(what) > (size - strlen(where))) { \
181 xrealloc(where, size + 128 + strlen(what)); \
182 size += 128 + strlen(what); \
183 } \
184 strcat(where, what); \
185 } while(0)
187 /* ========== */
189 /*
190 returns delta(d) in days, weeks, months, etc
191 the returned buffer is malloc()ed, do not forget to free() it
192 */
193 char *
194 tdelta(struct date *d)
195 {
196 int dy, wk, mn, yr;
197 char *tmp;
198 char *buf = xmalloc(128);
199 int size = 128;
201 *buf = 0;
202 switch (delta(d)) {
203 case 0:
204 append(buf, "today");
205 return buf;
206 case 1:
207 append(buf, "tomorrow");
208 return buf;
209 default:
210 /* like delta(), we ignore the year */
211 yr = -before(*d, today);
212 mn = d->month - today.month;
213 dy = d->day - today.day;
215 if (dy < 0) {
216 dy += mlen(today.month, today.year);
217 mn--;
218 }
219 if (mn < 0) {
220 mn += 12;
221 yr++;
222 }
224 wk = (dy / 7);
225 dy %= 7;
227 append(buf, "in ");
228 tmp = ttime(yr, mn, wk, dy);
229 append(buf, tmp);
230 free(tmp);
232 return buf;
233 }
234 }
240 /*
241 void
242 donum(n,txt)
243 {
244 if (n > 0) {
245 snprintf(tmp, sizeof(tmp), "%d", n);
246 append(buf, tmp);
247 append(buf, " " txt);
248 if (n != 1)
249 append(buf, "s");
250 terms--;
251 if (orgterms > 1) {
252 if (terms == 1)
253 append(buf, " and ");
254 else if (terms > 1)
255 append(buf, ", ");
256 }
257 }
258 }
259 */
262 #define donum(n,txt) do { \
263 if (n > 0) { \
264 snprintf(tmp, sizeof(tmp), "%d", n); \
265 append(buf, tmp); \
266 append(buf, " " txt); \
267 if (n != 1) \
268 append(buf, "s"); \
269 terms--; \
270 if (orgterms > 1) { \
271 if (terms == 1) \
272 append(buf, " and "); \
273 else if (terms > 1) \
274 append(buf, ", "); \
275 } \
276 } \
277 } while(0)
280 /* returns allocated buffer, don't forget to free() */
281 char *
282 ttime(int yr, int mn, int wk, int dy)
283 {
284 char *buf = xmalloc(128);
285 int size = 128;
286 int terms, orgterms;
287 char tmp[128];
289 *buf = 0; /* Initialize buffer */
290 terms = orgterms = (yr!=0) + (mn!=0) + (wk!=0) + (dy!=0);
292 donum(yr, "year");
293 donum(mn, "month");
294 donum(wk, "week");
295 donum(dy, "day");
297 return buf;
298 }
299 #undef donum
306 /*
307 lists the birthdays in their string format, one by one, and passes
308 the string to a function.
309 */
310 void
311 liststrings(struct event *evl, prnfunc outf)
312 {
313 int i,j;
314 char *buf, *tmp;
315 int size;
317 for (i = 0; evl[i].text != NULL; i++) {
318 buf = xmalloc(128);
319 *buf = '\0';
320 size = 128;
322 if (evl[i].warn == -1 && delta(&(evl[i].date))==0) {
323 append(buf, evl[i].text);
324 } else if (evl[i].enddate.day == 0) {
325 if (delta(&(evl[i].date)) <= evl[i].warn) {
326 append(buf, evl[i].text);
327 append(buf, " ");
328 tmp = tdelta(&(evl[i].date));
329 append(buf, tmp);
330 free(tmp);
331 }
332 } else {
333 if (delta(&(evl[i].date)) <= evl[i].warn) {
334 append(buf, evl[i].text);
335 append(buf, " for ");
336 /* +1 because, if the difference between two dates is one day,
337 then the length of an event on those days is two days */
338 j = ddiff(&(evl[i].date),&(evl[i].enddate)) + 1;
339 tmp = ttime(0, 0, j/7, j%7);
340 append(buf, tmp);
341 free(tmp);
342 append(buf, " ");
343 tmp = tdelta(&(evl[i].date));
344 append(buf, tmp);
345 } else if (delta(&(evl[i].enddate)) <= evl[i].warn) {
346 append(buf, evl[i].text);
347 append(buf, " ");
348 j = delta(&(evl[i].enddate));
349 if (j) {
350 append(buf, "for ");
351 tmp = ttime(0, 0, j/7, j%7);
352 append(buf, tmp);
353 free(tmp);
354 append(buf, " longer");
355 } else {
356 append(buf, "finishes today");
357 }
358 }
359 }
360 if (*buf) {
361 append(buf, ".");
362 outf(buf);
363 }
364 free(buf);
365 }
366 }
375 /*
376 sort the events by the time before the next time they come up,
377 putting those where the start has passed but we are still in the
378 time-period first
379 */
380 int
381 evcmp(const void *p1, const void *p2)
382 {
383 struct event *e1=(struct event *) p1;
384 struct event *e2=(struct event *) p2;
385 unsigned d1, d2;
387 /*
388 if the delta for the enddate is less than that for the start
389 date, then we have passed the start date but not yet the end
390 date, and so we should display the enddate; otherwise, we
391 should display the start date
392 */
394 d1=delta(&(e1->date));
395 if (e1->enddate.day && delta(&(e1->enddate)) < d1)
396 d1=delta(&(e1->enddate));
398 d2=delta(&(e2->date));
399 if (e2->enddate.day && delta(&(e2->enddate)) < d2)
400 d2=delta(&(e2->enddate));
402 if (d1 < d2) return -1;
403 if (d1 > d2) return 1;
405 return strcmp(e1->text, e2->text);
406 }
413 /*
414 difference in days between two dates
415 it is assumed that D1 < D2, and so the result is always positive
416 */
417 unsigned
418 ddiff(struct date *D1, struct date *D2)
419 {
420 struct date d1, d2;
421 int dd, m;
423 /* make working copies */
424 d1 = *D1;
425 d2 = *D2;
427 /* sort out zero years */
428 if (d1.year == 0 || d2.year==0) {
429 if (d1.year != d2.year) {
430 if (d1.year == 0) {
431 if (before(d1,d2))
432 d1.year = d2.year;
433 else
434 d1.year = d2.year - 1;
435 } else {
436 if (before(d1, d2))
437 d2.year = d1.year;
438 else
439 d2.year = d1.year + 1;
440 }
441 } else { /* both years zero */
442 if (before(d1, d2))
443 d1.year = d2.year = today.year;
444 else {
445 d1.year = today.year;
446 d2.year = d1.year + 1;
447 }
448 }
449 }
451 /* now we can actually do the comparison ... */
452 dd = 0;
454 /* to start with, we work in months */
455 for (m=d1.month; m < d2.month + (d2.year-d1.year)*12; m++)
456 dd += mlen(((m-1)%12)+1, d1.year + m/12);
458 /*
459 and then we renormalise for the days within the months
460 the first month was included in our calculations
461 */
462 dd -= d1.day;
463 /* but the last one wasn't */
464 dd += d2.day;
466 return dd;
467 }
476 /*
477 actually until the next anniversary of ...
478 */
479 unsigned
480 delta(struct date *date)
481 {
482 struct date d;
483 unsigned dt, mn;
485 memcpy(&d, date, sizeof(struct date));
487 /* past the end of the year */
488 if (before(d, today)) {
489 d.year = 1;
490 } else {
491 d.year = 0;
492 }
494 for (mn = today.month, dt=0; mn < d.month + 12*d.year; mn++) {
495 dt += mlen(((mn-1)%12) + 1,today.year + mn/12);
496 }
498 dt -= today.day;
499 dt += d.day;
501 return dt;
502 }
509 void
510 gettoday(void)
511 {
512 struct tm *tm;
513 time_t t;
515 time(&t);
516 tm = localtime(&t);
517 today.day = tm->tm_mday;
518 today.month = tm->tm_mon + 1; /* 1-12 instead of 0-11 */
519 today.year = tm->tm_year + 1900;
520 }
531 struct event *
532 readlist()
533 {
534 int i, j, k, l, d;
535 struct event *evl;
536 char buf[1024], buf2[1024];
537 char *ptr;
538 unsigned flags;
540 /* initialise */
541 gettoday();
543 for (i = 0, evl = NULL; fgets(buf, sizeof(buf), stdin) != NULL; i++) {
544 evl = (struct event *) xrealloc(evl, sizeof(struct event) * (i + 1));
546 /* ignore comments and empty lines */
547 if (*buf == '#' || *buf == '\n') {
548 i--;
549 continue;
550 }
552 /* parse string in buf */
553 ptr = strrchr(buf, '='); /* allow '=' in text */
555 /* not a valid line, so ignore it! Cool, huh? */
556 /* Attention: only recognizes lines without '=' */
557 if (ptr == NULL) {
558 fprintf(stderr, "WARNING: Invalid line in input:\n%s", buf);
559 i--;
560 continue;
561 }
563 *(ptr++) = 0;
565 j = sscanf(ptr, "%u-%u-%u", &(evl[i].date.year),
566 &(evl[i].date.month), &(evl[i].date.day));
567 /* ... unless it wasn't read, in which case set it to zero */
568 if (j==2) {
569 evl[i].date.year = 0;
570 }
573 /* parse flags */
575 evl[i].warn = iDWarn;
576 evl[i].enddate.day = 0;
577 evl[i].enddate.month = 0;
578 evl[i].enddate.year = 0;
580 flags = 0;
581 j = 0;
583 while(j = skptok(j, ptr), ptr[j] != 0) {
584 for (k = 0; FTABLE[k].txt != NULL && strncmp(FTABLE[k].txt, ptr + j, strlen(FTABLE[k].txt)); k++) {
585 }
587 switch (FTABLE[k].flag) {
588 case F_WTIME_P: /* w <n> -- sets warning time */
589 sscanf(ptr + j, "w %u", &(evl[i].warn));
590 break;
591 case F_FORDAYS: /* for <days> -- sets the duration of the event */
592 sscanf(ptr + j, "for %u", &d);
593 evl[i].enddate=evl[i].date;
594 for (l = 1; l < d; l++) {
595 evl[i].enddate.day++;
596 if (evl[i].enddate.day > mlen(evl[i].enddate.month, evl[i].enddate.year)) {
597 evl[i].enddate.month++;
598 evl[i].enddate.day = 1;
599 }
600 if (evl[i].enddate.month > 12) {
601 evl[i].enddate.year++;
602 evl[i].enddate.month = 1;
603 }
604 }
605 break;
606 case F_TODATE: /* to <date> -- sets the end date of the event */
607 l = sscanf(ptr + j, "to %u-%u-%u", &(evl[i].enddate.year), &(evl[i].enddate.month), &(evl[i].enddate.day));
608 if (l == 2) {
609 evl[i].enddate.year = 0;
610 }
611 break;
612 case 0:
613 break;
614 default:
615 flags |= FTABLE[k].flag;
616 break;
617 }
618 }
621 /* construct event text */
623 switch(flags & F_MTYPE) {
624 case F_TBIRTHDAY:
625 default: /* assume it's a birthday */
626 if (evl[i].date.year != 0) {
627 int tmp_age = ydelta(evl[i].date, today);
628 if (tmp_age != 1) {
629 sprintf(buf2, "%s is %d years old", buf, tmp_age);
630 } else {
631 sprintf(buf2, "%s is %d year old", buf, tmp_age);
632 }
633 } else {
634 sprintf(buf2, "%s has a birthday", buf);
635 }
636 break;
637 case F_TANNIVERSARY:
638 if (evl[i].date.year != 0) {
639 sprintf(buf2, "%s %d years ago", buf, ydelta(evl[i].date, today));
640 } else {
641 strcpy(buf2, buf);
642 }
643 break;
644 case F_TEVENT:
645 /* if a year was specified, and this warning isn't for it, ignore! */
646 if ((evl[i].date.year != 0 && ydelta(evl[i].date, today) != 0)
647 && (evl[i].enddate.year == 0 || ydelta(evl[i].enddate, today) != 0)) {
648 i--;
649 continue;
650 }
651 strcpy(buf2, buf);
652 break;
653 }
654 evl[i].text = strdup(buf2);
655 }
657 evl = (struct event *) xrealloc(evl, sizeof(struct event) * (i + 1));
658 evl[i].date.day = 0;
659 evl[i].date.month = 0;
660 evl[i].date.year = 0;
661 evl[i].text = (char *) NULL;
663 fclose(stdin);
665 /* NB uses i from above */
666 qsort(evl, i, sizeof(struct event), evcmp);
667 return evl;
668 }
674 int
675 skptok(int j, char *ptr)
676 {
677 for (; ptr[j] != 0 && ptr[j] != ' ' && ptr[j] != '\t' ; j++);
678 for (; ptr[j] != 0 && (ptr[j] == ' ' || ptr[j] == '\t'); j++);
680 return j;
681 }
688 int
689 main(int argc, char *argv[])
690 {
691 while (--argc > 0 && (*++argv)[0] == '-') {
692 if (strcmp(argv[0], "-W") == 0) {
693 /* TODO: catch if no value given */
694 iDWarn = atoi((++argv)[0]);
695 argc--;
696 } else {
697 fprintf(stderr, "unknown option %s\n", argv[0]);
698 exit(1);
699 }
700 }
702 liststrings(readlist(), puts);
704 return 0;
705 }