bday
view bday.c @ 24:c621d710d12e
added VERSION to man page
author | markus schnalke <meillo@marmaro.de> |
---|---|
date | Mon, 24 Feb 2014 21:44:02 +0100 |
parents | d18a3b2b76bd |
children |
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 optionally
34 #ann for an anniversary
35 and optionally
36 #w<n> to set the warn-in-advance time to n days
37 (don't include the brackets! :)
38 separated by spaces.
40 Lines preceeded by # are treated as comments.
42 Note: If you deviate from this format, I cannot guarantee anything about
43 it's behaviour. In most cases, it will just quietly ignore the
44 error, which probably isn't ideal behaviour. Oh, well.
46 =====================================================================
47 */
50 /* standard time to warn in advance, when no explicit w flag is given. */
51 #define DEF_WARN 14
54 #include <stdarg.h>
55 #include <stdio.h>
56 #include <stdlib.h>
57 #include <string.h>
58 #include <sys/types.h>
59 #include <time.h>
60 #include <unistd.h>
64 /* ========== Global constants and data types */
67 /* -------- modifier flags */
68 #define F_MTYPE 0x07
69 #define F_TANNIVERSARY 2
71 /* flags processed immediately on encountering */
72 #define F_MIMMEDIATE 0x24
73 #define F_WTIME_P 0x08
75 struct _ftable {
76 char* txt;
77 unsigned flag;
78 };
79 const struct _ftable FTABLE[];
81 struct date {
82 unsigned day;
83 unsigned month;
84 unsigned year;
85 };
87 struct event {
88 char* text;
89 struct date date;
90 int warn;
91 };
93 /* ========== Global Variables */
95 struct event *readlist(void);
96 void gettoday(void);
97 unsigned delta(struct date *);
98 unsigned ddiff(struct date *D1, struct date *D2);
99 void liststrings(struct event *evl);
100 char *tdelta(struct date *d);
101 char *ttime(int yr, int mn, int wk, int dy);
102 char *skptok(char *ptr);
103 int evcmp(const void *e1, const void *e2);
106 struct date today;
107 int def_warn = DEF_WARN;
110 const struct _ftable FTABLE[] = {
111 {"#ann",F_TANNIVERSARY},
112 {"#w", F_WTIME_P},
113 {NULL, 0}
114 };
121 /*
122 xmalloc/xrealloc functions
123 Note: the x* functions are lifted straight from the GNU libc info docs
124 $Id: xmalloc.c,v 1.2 1999/01/16 17:08:59 andy Exp $
125 */
127 void *
128 xmalloc(size_t size)
129 {
130 register void *value = malloc (size);
131 if (value == 0) {
132 fprintf(stderr, "virtual memory exhausted\n");
133 exit(1);
134 }
135 return value;
136 }
138 void *
139 xrealloc(void *ptr, size_t size)
140 {
141 register void *value = realloc (ptr, size);
142 if (value == 0) {
143 fprintf(stderr, "virtual memory exhausted\n");
144 exit(1);
145 }
146 return value;
147 }
150 /* ========== */
153 /*
154 like strcat(), but lets the buffer automagically grow :-)
155 */
156 int
157 append(char *where, int size, char *what)
158 {
159 if (strlen(what) > ((size) - strlen(where))) {
160 xrealloc(where, (size) + 128 + strlen(what));
161 size += 128 + strlen(what);
162 }
163 strcat(where, what);
164 return size;
165 }
167 /* ========== */
170 int
171 before(struct date a, struct date b)
172 {
173 if (a.month < b.month) {
174 return 1;
175 } else if (a.month == b.month && a.day < b.day) {
176 return 1;
177 } else {
178 return 0;
179 }
180 }
182 int
183 ydelta(struct date a, struct date b)
184 {
185 return b.year - a.year + before(a, b);
186 }
188 /*
189 returns the length of the given month
190 */
191 int
192 mlen(int month, int year)
193 {
194 unsigned mlendat[] = {31,0,31,30,31,30,31,31,30,31,30,31};
196 if (mlendat[month - 1]) {
197 return mlendat[month - 1];
198 } else {
199 if (year%4==0 && (year%100!=0 || year%400==0)) {
200 return 29;
201 } else {
202 return 28;
203 }
204 }
205 }
209 /*
210 returns delta(d) in days, weeks, months, etc
211 the returned buffer is malloc()ed, do not forget to free() it
212 */
213 char *
214 tdelta(struct date *d)
215 {
216 int dy, wk, mn, yr;
217 char *tmp;
218 char *buf = xmalloc(128);
219 int size = 128;
221 *buf = 0;
222 switch (delta(d)) {
223 case 0:
224 size = append(buf, size, "TODAY");
225 return buf;
226 case 1:
227 size = append(buf, size, "Tomorrow");
228 return buf;
229 default:
230 /* like delta(), we ignore the year */
231 yr = -before(*d, today);
232 mn = d->month - today.month;
233 dy = d->day - today.day;
235 if (dy < 0) {
236 dy += mlen(today.month, today.year);
237 mn--;
238 }
239 if (mn < 0) {
240 mn += 12;
241 yr++;
242 }
244 wk = (dy / 7);
245 dy %= 7;
247 size = append(buf, size, "In ");
248 tmp = ttime(yr, mn, wk, dy);
249 size = append(buf, size, tmp);
250 free(tmp);
252 return buf;
253 }
254 }
260 void
261 donum(char *buf, int size, int n, char *txt, int *terms)
262 {
263 char tmp[128];
265 if (n <= 0) {
266 return;
267 }
268 snprintf(tmp, sizeof(tmp), "%d", n);
269 size = append(buf, size, tmp);
270 size = append(buf, size, " ");
271 size = append(buf, size, txt);
272 if (n != 1) {
273 size = append(buf, size, "s");
274 }
275 if (--*terms == 1) {
276 size = append(buf, size, " and ");
277 } else if (*terms > 1) {
278 size = append(buf, size, ", ");
279 }
280 }
283 /* returns allocated buffer, don't forget to free() */
284 char *
285 ttime(int yr, int mn, int wk, int dy)
286 {
287 int size = 128;
288 char *buf = xmalloc(size);
289 int terms = (yr!=0) + (mn!=0) + (wk!=0) + (dy!=0);
291 *buf = '\0'; /* Initialize buffer */
293 donum(buf, size, yr, "year", &terms);
294 donum(buf, size, mn, "month", &terms);
295 donum(buf, size, wk, "week", &terms);
296 donum(buf, size, dy, "day", &terms);
298 return buf;
299 }
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)
312 {
313 int i;
314 char *buf, *tmp;
315 int size;
317 for (i=0; evl[i].text; i++) {
318 size = 128;
319 buf = xmalloc(size);
320 *buf = '\0';
322 if (evl[i].warn == -1 && delta(&(evl[i].date))==0) {
323 size = append(buf, size, evl[i].text);
324 } else if (delta(&(evl[i].date)) <= evl[i].warn) {
325 tmp = tdelta(&(evl[i].date));
326 size = append(buf, size, tmp);
327 size = append(buf, size, ": ");
328 size = append(buf, size, evl[i].text);
329 free(tmp);
330 }
331 if (*buf) {
332 size = append(buf, size, ".");
333 puts(buf);
334 }
335 free(buf);
336 }
337 }
346 /*
347 sort the events by the time before the next time they come up
348 */
349 int
350 evcmp(const void *p1, const void *p2)
351 {
352 struct event *e1=(struct event *) p1;
353 struct event *e2=(struct event *) p2;
354 unsigned d1, d2;
356 d1=delta(&(e1->date));
357 d2=delta(&(e2->date));
359 if (d1 < d2) return -1;
360 if (d1 > d2) return 1;
361 return strcmp(e1->text, e2->text);
362 }
369 /*
370 difference in days between two dates
371 it is assumed that D1 < D2, and so the result is always positive
372 */
373 unsigned
374 ddiff(struct date *D1, struct date *D2)
375 {
376 struct date d1, d2;
377 int dd, m;
379 /* make working copies */
380 d1 = *D1;
381 d2 = *D2;
383 /* sort out zero years */
384 if (d1.year == 0 || d2.year==0) {
385 if (d1.year != d2.year) {
386 if (d1.year == 0) {
387 if (before(d1,d2))
388 d1.year = d2.year;
389 else
390 d1.year = d2.year - 1;
391 } else {
392 if (before(d1, d2))
393 d2.year = d1.year;
394 else
395 d2.year = d1.year + 1;
396 }
397 } else { /* both years zero */
398 if (before(d1, d2))
399 d1.year = d2.year = today.year;
400 else {
401 d1.year = today.year;
402 d2.year = d1.year + 1;
403 }
404 }
405 }
407 /* now we can actually do the comparison ... */
408 dd = 0;
410 /* to start with, we work in months */
411 for (m=d1.month; m < d2.month + (d2.year-d1.year)*12; m++)
412 dd += mlen(((m-1)%12)+1, d1.year + m/12);
414 /*
415 and then we renormalise for the days within the months
416 the first month was included in our calculations
417 */
418 dd -= d1.day;
419 /* but the last one wasn't */
420 dd += d2.day;
422 return dd;
423 }
432 /*
433 actually until the next anniversary of ...
434 */
435 unsigned
436 delta(struct date *date)
437 {
438 struct date d;
439 unsigned dt, mn;
441 memcpy(&d, date, sizeof(struct date));
443 /* past the end of the year */
444 if (before(d, today)) {
445 d.year = 1;
446 } else {
447 d.year = 0;
448 }
450 for (mn = today.month, dt=0; mn < d.month + 12*d.year; mn++) {
451 dt += mlen(((mn-1)%12) + 1,today.year + mn/12);
452 }
454 dt -= today.day;
455 dt += d.day;
457 return dt;
458 }
465 void
466 gettoday(void)
467 {
468 struct tm *tm;
469 time_t t;
471 time(&t);
472 tm = localtime(&t);
473 today.day = tm->tm_mday;
474 today.month = tm->tm_mon + 1; /* 1-12 instead of 0-11 */
475 today.year = tm->tm_year + 1900;
476 }
487 struct event *
488 readlist()
489 {
490 int i, j, k;
491 struct event *evl;
492 char buf[1024], buf2[1024];
493 char *ptr, *cp;
494 unsigned flags;
496 /* initialise */
497 gettoday();
499 for (i=0, evl=NULL; fgets(buf, sizeof(buf), stdin) != NULL; i++) {
500 evl = (struct event *) xrealloc(evl, sizeof(struct event) * (i + 1));
502 /* ignore comments and empty lines */
503 if (*buf == '#' || *buf == '\n') {
504 i--;
505 continue;
506 }
508 /* parse string in buf */
510 ptr = strchr(buf, ' '); /* start of text */
512 /* not a valid line, so ignore it! Cool, huh? */
513 /* Attention: only recognizes lines without '=' */
514 if (!ptr) {
515 fprintf(stderr, "WARNING: Invalid input line:\n\t%s", buf);
516 i--;
517 continue;
518 }
520 *(ptr++) = '\0';
521 ptr[strlen(ptr)-1] = '\0';
523 j = sscanf(buf, "%u-%u-%u", &(evl[i].date.year),
524 &(evl[i].date.month), &(evl[i].date.day));
525 if (j != 3) {
526 fprintf(stderr, "Error: Invalid date:\t%s\n", buf);
527 i--;
528 continue;
529 }
531 /* parse flags */
533 evl[i].warn = def_warn;
534 flags = 0;
535 j = 0;
536 cp = skptok(ptr);
537 for (cp=ptr; *cp && *cp=='#'; cp=skptok(cp)) {
538 for (k = 0; FTABLE[k].txt && strncmp(FTABLE[k].txt, cp, strlen(FTABLE[k].txt)); k++) {
539 }
541 switch (FTABLE[k].flag) {
542 case F_WTIME_P: /* #w<n> -- sets warning time */
543 sscanf(cp, "#w%u", &(evl[i].warn));
544 break;
545 case 0:
546 break;
547 default:
548 flags |= FTABLE[k].flag;
549 break;
550 }
551 }
554 /* construct event text */
556 switch(flags & F_MTYPE) {
557 default: /* assume it's a birthday */
558 if (!evl[i].date.year) {
559 sprintf(buf2, "%s has a birthday", cp);
560 break;
561 }
562 int tmp_age = ydelta(evl[i].date, today);
563 sprintf(buf2, "%s is %d year%s old",
564 cp, tmp_age, (tmp_age>1)?"s":"");
565 break;
566 case F_TANNIVERSARY:
567 if (evl[i].date.year) {
568 sprintf(buf2, "%s %d years ago",
569 cp, ydelta(evl[i].date, today));
570 } else {
571 strcpy(buf2, cp);
572 }
573 break;
574 }
575 evl[i].text = strdup(buf2);
576 }
578 evl = (struct event *) xrealloc(evl, sizeof(struct event) * (i + 1));
579 evl[i].date.day = 0;
580 evl[i].date.month = 0;
581 evl[i].date.year = 0;
582 evl[i].text = (char *) NULL;
584 fclose(stdin);
586 /* NB uses i from above */
587 qsort(evl, i, sizeof(struct event), evcmp);
588 return evl;
589 }
595 char *
596 skptok(char *ptr)
597 {
598 while (*ptr && (*ptr!=' ' && *ptr!='\t')) {
599 ptr++;
600 }
601 while (*ptr && (*ptr==' ' || *ptr=='\t')) {
602 ptr++;
603 }
604 return ptr;
605 }
612 int
613 main(int argc, char *argv[])
614 {
615 while (--argc > 0 && **++argv == '-') {
616 if (strcmp(argv[0], "-w") == 0) {
617 /* TODO: catch if no value given */
618 def_warn = atoi((++argv)[0]);
619 argc--;
620 } else {
621 fprintf(stderr, "unknown option %s\n", argv[0]);
622 exit(1);
623 }
624 }
625 liststrings(readlist());
626 return 0;
627 }