bday

view bday.c @ 16:79d22407a6be

a lot of refactoring
author markus schnalke <meillo@marmaro.de>
date Mon, 24 Feb 2014 21:11:38 +0100
parents 032af48d590b
children d18a3b2b76bd
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 date flags text
31 where:
32 date is YYYY-MM-DD
33 flags is ONE or ZERO of
34 #ann for an anniversary
35 #ev for an event
36 and zero or more of
37 #w<n> to set the warn-in-advance time to n days
38 (don't include the brackets! :)
39 #to<date>
40 #for<days>
41 to specify the length of time taken by an
42 event, for example a holiday
43 separated by spaces.
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 /* -------- modifier flags */
73 #define F_MTYPE 0x07
74 #define F_TANNIVERSARY 2
75 #define F_TEVENT 3
77 /* flags processed immediately on encountering */
78 #define F_MIMMEDIATE 0x24
79 #define F_WTIME_P 0x08
80 #define F_FORDAYS 0x16
81 #define F_TODATE 0x24
83 struct _ftable {
84 char* txt;
85 unsigned flag;
86 };
87 const struct _ftable FTABLE[];
89 struct date {
90 unsigned day;
91 unsigned month;
92 unsigned year;
93 };
95 struct event {
96 char* text;
97 struct date date;
98 struct date enddate;
99 int warn;
100 };
102 /* ========== Global Variables */
104 struct event *readlist(void);
105 void gettoday(void);
106 unsigned delta(struct date *);
107 unsigned ddiff(struct date *D1, struct date *D2);
108 void liststrings(struct event *evl);
109 char *tdelta(struct date *d);
110 char *ttime(int yr, int mn, int wk, int dy);
111 char *skptok(char *ptr);
112 int evcmp(const void *e1, const void *e2);
115 struct date today;
116 int def_warn = DEF_WARN;
119 const struct _ftable FTABLE[] = {
120 {"#ann",F_TANNIVERSARY},
121 {"#ev", F_TEVENT},
122 {"#w", F_WTIME_P},
123 {"#to", F_TODATE},
124 {"#for", F_FORDAYS},
125 {NULL, 0}
126 };
133 /*
134 xmalloc/xrealloc functions
135 Note: the x* functions are lifted straight from the GNU libc info docs
136 $Id: xmalloc.c,v 1.2 1999/01/16 17:08:59 andy Exp $
137 */
139 void *
140 xmalloc(size_t size)
141 {
142 register void *value = malloc (size);
143 if (value == 0) {
144 fprintf(stderr, "virtual memory exhausted\n");
145 exit(1);
146 }
147 return value;
148 }
150 void *
151 xrealloc(void *ptr, size_t size)
152 {
153 register void *value = realloc (ptr, size);
154 if (value == 0) {
155 fprintf(stderr, "virtual memory exhausted\n");
156 exit(1);
157 }
158 return value;
159 }
162 /* ========== */
165 /*
166 like strcat(), but lets the buffer automagically grow :-)
167 */
168 int
169 append(char *where, int size, char *what)
170 {
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 return size;
177 }
179 /* ========== */
182 int
183 before(struct date a, struct date b)
184 {
185 if (a.month < b.month) {
186 return 1;
187 } else if (a.month == b.month && a.day < b.day) {
188 return 1;
189 } else {
190 return 0;
191 }
192 }
194 int
195 ydelta(struct date a, struct date b)
196 {
197 return b.year - a.year + before(a, b);
198 }
200 /*
201 returns the length of the given month
202 */
203 int
204 mlen(int month, int year)
205 {
206 unsigned mlendat[] = {31,0,31,30,31,30,31,31,30,31,30,31};
208 if (mlendat[month - 1]) {
209 return mlendat[month - 1];
210 } else {
211 if (year%4==0 && (year%100!=0 || year%400==0)) {
212 return 29;
213 } else {
214 return 28;
215 }
216 }
217 }
221 /*
222 returns delta(d) in days, weeks, months, etc
223 the returned buffer is malloc()ed, do not forget to free() it
224 */
225 char *
226 tdelta(struct date *d)
227 {
228 int dy, wk, mn, yr;
229 char *tmp;
230 char *buf = xmalloc(128);
231 int size = 128;
233 *buf = 0;
234 switch (delta(d)) {
235 case 0:
236 size = append(buf, size, "TODAY");
237 return buf;
238 case 1:
239 size = append(buf, size, "Tomorrow");
240 return buf;
241 default:
242 /* like delta(), we ignore the year */
243 yr = -before(*d, today);
244 mn = d->month - today.month;
245 dy = d->day - today.day;
247 if (dy < 0) {
248 dy += mlen(today.month, today.year);
249 mn--;
250 }
251 if (mn < 0) {
252 mn += 12;
253 yr++;
254 }
256 wk = (dy / 7);
257 dy %= 7;
259 size = append(buf, size, "In ");
260 tmp = ttime(yr, mn, wk, dy);
261 size = append(buf, size, tmp);
262 free(tmp);
264 return buf;
265 }
266 }
272 void
273 donum(char *buf, int size, int n, char *txt, int *terms)
274 {
275 char tmp[128];
277 if (n <= 0) {
278 return;
279 }
280 snprintf(tmp, sizeof(tmp), "%d", n);
281 size = append(buf, size, tmp);
282 size = append(buf, size, " ");
283 size = append(buf, size, txt);
284 if (n != 1) {
285 size = append(buf, size, "s");
286 }
287 if (--*terms == 1) {
288 size = append(buf, size, " and ");
289 } else if (*terms > 1) {
290 size = append(buf, size, ", ");
291 }
292 }
295 /* returns allocated buffer, don't forget to free() */
296 char *
297 ttime(int yr, int mn, int wk, int dy)
298 {
299 int size = 128;
300 char *buf = xmalloc(size);
301 int terms = (yr!=0) + (mn!=0) + (wk!=0) + (dy!=0);
303 *buf = '\0'; /* Initialize buffer */
305 donum(buf, size, yr, "year", &terms);
306 donum(buf, size, mn, "month", &terms);
307 donum(buf, size, wk, "week", &terms);
308 donum(buf, size, dy, "day", &terms);
310 return buf;
311 }
318 /*
319 lists the birthdays in their string format, one by one, and passes
320 the string to a function.
321 */
322 void
323 liststrings(struct event *evl)
324 {
325 int i,j;
326 char *buf, *tmp;
327 int size;
329 for (i=0; evl[i].text; i++) {
330 size = 128;
331 buf = xmalloc(size);
332 *buf = '\0';
334 if (evl[i].warn == -1 && delta(&(evl[i].date))==0) {
335 size = append(buf, size, evl[i].text);
336 } else if (evl[i].enddate.day == 0) {
338 if (delta(&(evl[i].date)) <= evl[i].warn) {
339 tmp = tdelta(&(evl[i].date));
340 size = append(buf, size, tmp);
341 size = append(buf, size, ": ");
342 size = append(buf, size, evl[i].text);
343 free(tmp);
344 }
345 } else {
346 if (delta(&(evl[i].date)) <= evl[i].warn) {
347 size = append(buf, size, evl[i].text);
348 size = append(buf, size, " for ");
349 /* +1 because, if the difference between
350 two dates is one day, then the length of
351 an event on those days is two days */
352 j = ddiff(&(evl[i].date),&(evl[i].enddate)) + 1;
353 tmp = ttime(0, 0, j/7, j%7);
354 size = append(buf, size, tmp);
355 free(tmp);
356 size = append(buf, size, " ");
357 tmp = tdelta(&(evl[i].date));
358 size = append(buf, size, tmp);
359 } else if (delta(&(evl[i].enddate)) <= evl[i].warn) {
360 size = append(buf, size, evl[i].text);
361 size = append(buf, size, " ");
362 j = delta(&(evl[i].enddate));
363 if (j) {
364 size = append(buf, size, "for ");
365 tmp = ttime(0, 0, j/7, j%7);
366 size = append(buf, size, tmp);
367 free(tmp);
368 size = append(buf, size, " longer");
369 } else {
370 size = append(buf, size, "finishes today");
371 }
372 }
373 }
374 if (*buf) {
375 size = append(buf, size, ".");
376 puts(buf);
377 }
378 free(buf);
379 }
380 }
389 /*
390 sort the events by the time before the next time they come up,
391 putting those where the start has passed but we are still in the
392 time-period first
393 */
394 int
395 evcmp(const void *p1, const void *p2)
396 {
397 struct event *e1=(struct event *) p1;
398 struct event *e2=(struct event *) p2;
399 unsigned d1, d2;
401 /*
402 if the delta for the enddate is less than that for the start
403 date, then we have passed the start date but not yet the end
404 date, and so we should display the enddate; otherwise, we
405 should display the start date
406 */
408 d1=delta(&(e1->date));
409 if (e1->enddate.day && delta(&(e1->enddate)) < d1)
410 d1=delta(&(e1->enddate));
412 d2=delta(&(e2->date));
413 if (e2->enddate.day && delta(&(e2->enddate)) < d2)
414 d2=delta(&(e2->enddate));
416 if (d1 < d2) return -1;
417 if (d1 > d2) return 1;
419 return strcmp(e1->text, e2->text);
420 }
427 /*
428 difference in days between two dates
429 it is assumed that D1 < D2, and so the result is always positive
430 */
431 unsigned
432 ddiff(struct date *D1, struct date *D2)
433 {
434 struct date d1, d2;
435 int dd, m;
437 /* make working copies */
438 d1 = *D1;
439 d2 = *D2;
441 /* sort out zero years */
442 if (d1.year == 0 || d2.year==0) {
443 if (d1.year != d2.year) {
444 if (d1.year == 0) {
445 if (before(d1,d2))
446 d1.year = d2.year;
447 else
448 d1.year = d2.year - 1;
449 } else {
450 if (before(d1, d2))
451 d2.year = d1.year;
452 else
453 d2.year = d1.year + 1;
454 }
455 } else { /* both years zero */
456 if (before(d1, d2))
457 d1.year = d2.year = today.year;
458 else {
459 d1.year = today.year;
460 d2.year = d1.year + 1;
461 }
462 }
463 }
465 /* now we can actually do the comparison ... */
466 dd = 0;
468 /* to start with, we work in months */
469 for (m=d1.month; m < d2.month + (d2.year-d1.year)*12; m++)
470 dd += mlen(((m-1)%12)+1, d1.year + m/12);
472 /*
473 and then we renormalise for the days within the months
474 the first month was included in our calculations
475 */
476 dd -= d1.day;
477 /* but the last one wasn't */
478 dd += d2.day;
480 return dd;
481 }
490 /*
491 actually until the next anniversary of ...
492 */
493 unsigned
494 delta(struct date *date)
495 {
496 struct date d;
497 unsigned dt, mn;
499 memcpy(&d, date, sizeof(struct date));
501 /* past the end of the year */
502 if (before(d, today)) {
503 d.year = 1;
504 } else {
505 d.year = 0;
506 }
508 for (mn = today.month, dt=0; mn < d.month + 12*d.year; mn++) {
509 dt += mlen(((mn-1)%12) + 1,today.year + mn/12);
510 }
512 dt -= today.day;
513 dt += d.day;
515 return dt;
516 }
523 void
524 gettoday(void)
525 {
526 struct tm *tm;
527 time_t t;
529 time(&t);
530 tm = localtime(&t);
531 today.day = tm->tm_mday;
532 today.month = tm->tm_mon + 1; /* 1-12 instead of 0-11 */
533 today.year = tm->tm_year + 1900;
534 }
545 struct event *
546 readlist()
547 {
548 int i, j, k, l, d;
549 struct event *evl;
550 char buf[1024], buf2[1024];
551 char *ptr, *cp;
552 unsigned flags;
554 /* initialise */
555 gettoday();
557 for (i = 0, evl = NULL; fgets(buf, sizeof(buf), stdin) != NULL; i++) {
558 evl = (struct event *) xrealloc(evl, sizeof(struct event) * (i + 1));
560 /* ignore comments and empty lines */
561 if (*buf == '#' || *buf == '\n') {
562 i--;
563 continue;
564 }
566 /* parse string in buf */
568 ptr = strchr(buf, ' '); /* start of text */
570 /* not a valid line, so ignore it! Cool, huh? */
571 /* Attention: only recognizes lines without '=' */
572 if (!ptr) {
573 fprintf(stderr, "WARNING: Invalid input line:\n\t%s", buf);
574 i--;
575 continue;
576 }
578 *(ptr++) = '\0';
579 ptr[strlen(ptr)-1] = '\0';
581 j = sscanf(buf, "%u-%u-%u", &(evl[i].date.year),
582 &(evl[i].date.month), &(evl[i].date.day));
583 if (j != 3) {
584 fprintf(stderr, "Error: Invalid date:\t%s\n", buf);
585 i--;
586 continue;
587 }
589 /* parse flags */
591 evl[i].warn = def_warn;
592 evl[i].enddate.day = 0;
593 evl[i].enddate.month = 0;
594 evl[i].enddate.year = 0;
596 flags = 0;
597 j = 0;
598 cp = skptok(ptr);
599 for (cp=ptr; *cp && *cp=='#'; cp=skptok(cp)) {
600 for (k = 0; FTABLE[k].txt && strncmp(FTABLE[k].txt, cp, strlen(FTABLE[k].txt)); k++) {
601 }
603 switch (FTABLE[k].flag) {
604 case F_WTIME_P: /* #w<n> -- sets warning time */
605 sscanf(cp, "#w%u", &(evl[i].warn));
606 break;
607 case F_FORDAYS: /* #for<days> -- sets the duration of the event */
608 sscanf(cp, "#for%u", &d);
609 evl[i].enddate=evl[i].date;
610 for (l = 1; l < d; l++) {
611 evl[i].enddate.day++;
612 if (evl[i].enddate.day > mlen(evl[i].enddate.month, evl[i].enddate.year)) {
613 evl[i].enddate.month++;
614 evl[i].enddate.day = 1;
615 }
616 if (evl[i].enddate.month > 12) {
617 evl[i].enddate.year++;
618 evl[i].enddate.month = 1;
619 }
620 }
621 break;
622 case F_TODATE: /* #to<date> -- sets the end date of the event */
623 l = sscanf(cp, "#to%u-%u-%u", &(evl[i].enddate.year), &(evl[i].enddate.month), &(evl[i].enddate.day));
624 if (l == 2) {
625 evl[i].enddate.year = 0;
626 }
627 break;
628 case 0:
629 break;
630 default:
631 flags |= FTABLE[k].flag;
632 break;
633 }
634 }
637 /* construct event text */
639 switch(flags & F_MTYPE) {
640 default: /* assume it's a birthday */
641 if (!evl[i].date.year) {
642 sprintf(buf2, "%s has a birthday", cp);
643 break;
644 }
645 int tmp_age = ydelta(evl[i].date, today);
646 sprintf(buf2, "%s is %d year%s old",
647 cp, tmp_age, (tmp_age>1)?"s":"");
648 break;
649 case F_TANNIVERSARY:
650 if (evl[i].date.year) {
651 sprintf(buf2, "%s %d years ago",
652 cp, ydelta(evl[i].date, today));
653 } else {
654 strcpy(buf2, cp);
655 }
656 break;
657 case F_TEVENT:
658 /* if a year was specified, and this
659 warning isn't for it, ignore! */
660 if ((evl[i].date.year && ydelta(evl[i].date, today))
661 && (!evl[i].enddate.year || ydelta(evl[i].enddate, today))) {
662 i--;
663 continue;
664 }
665 strcpy(buf2, cp);
666 break;
667 }
668 evl[i].text = strdup(buf2);
669 }
671 evl = (struct event *) xrealloc(evl, sizeof(struct event) * (i + 1));
672 evl[i].date.day = 0;
673 evl[i].date.month = 0;
674 evl[i].date.year = 0;
675 evl[i].text = (char *) NULL;
677 fclose(stdin);
679 /* NB uses i from above */
680 qsort(evl, i, sizeof(struct event), evcmp);
681 return evl;
682 }
688 char *
689 skptok(char *ptr)
690 {
691 while (*ptr && (*ptr!=' ' && *ptr!='\t')) {
692 ptr++;
693 }
694 while (*ptr && (*ptr==' ' || *ptr=='\t')) {
695 ptr++;
696 }
697 return ptr;
698 }
705 int
706 main(int argc, char *argv[])
707 {
708 while (--argc > 0 && **++argv == '-') {
709 if (strcmp(argv[0], "-W") == 0) {
710 /* TODO: catch if no value given */
711 def_warn = atoi((++argv)[0]);
712 argc--;
713 } else {
714 fprintf(stderr, "unknown option %s\n", argv[0]);
715 exit(1);
716 }
717 }
718 liststrings(readlist());
719 return 0;
720 }