comparison 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
comparison
equal deleted inserted replaced
15:032af48d590b 16:79d22407a6be
24 ===================================================================== 24 =====================================================================
25 25
26 Input is read through standard input. For example: bday < ~/.birthdays 26 Input is read through standard input. For example: bday < ~/.birthdays
27 The input (file) has to have the following format: 27 The input (file) has to have the following format:
28 28
29 text=date flags 29 date flags text
30 30
31 where: 31 where:
32 date is yyyy-mm-dd 32 date is YYYY-MM-DD
33 flags is ONE or ZERO of 33 flags is ONE or ZERO of
34 bd for a birthday (default) 34 #ann for an anniversary
35 ann for an anniversary 35 #ev for an event
36 ev for an event
37 and zero or more of 36 and zero or more of
38 w <n> to set the warn-in-advance time to n days 37 #w<n> to set the warn-in-advance time to n days
39 (don't include the brackets! :) 38 (don't include the brackets! :)
40 to <date> 39 #to<date>
41 for <days> 40 #for<days>
42 to specify the length of time taken by an 41 to specify the length of time taken by an
43 event, for example a holiday. 42 event, for example a holiday
43 separated by spaces.
44 44
45 Lines preceeded by # are treated as comments. 45 Lines preceeded by # are treated as comments.
46 46
47 Note: If you deviate from this format, I cannot guarantee anything about 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 48 it's behaviour. In most cases, it will just quietly ignore the
67 67
68 68
69 /* ========== Global constants and data types */ 69 /* ========== Global constants and data types */
70 70
71 71
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)))
78
79 /* -------- modifier flags */ 72 /* -------- modifier flags */
80 #define F_MTYPE 0x07 73 #define F_MTYPE 0x07
81 #define F_TBIRTHDAY 1
82 #define F_TANNIVERSARY 2 74 #define F_TANNIVERSARY 2
83 #define F_TEVENT 3 75 #define F_TEVENT 3
84 76
85 /* flags processed immediately on encountering */ 77 /* flags processed immediately on encountering */
86 #define F_MIMMEDIATE 0x24 78 #define F_MIMMEDIATE 0x24
87 #define F_WTIME_P 0x08 79 #define F_WTIME_P 0x08
88 #define F_FORDAYS 0x16 80 #define F_FORDAYS 0x16
89 #define F_TODATE 0x24 81 #define F_TODATE 0x24
90 82
91 struct _ftable {char* txt; unsigned flag;}; 83 struct _ftable {
92 84 char* txt;
85 unsigned flag;
86 };
93 const struct _ftable FTABLE[]; 87 const struct _ftable FTABLE[];
94 88
95 struct date { 89 struct date {
96 unsigned day; 90 unsigned day;
97 unsigned month; 91 unsigned month;
103 struct date date; 97 struct date date;
104 struct date enddate; 98 struct date enddate;
105 int warn; 99 int warn;
106 }; 100 };
107 101
108 typedef int (*prnfunc)(const char *);
109
110 /* ========== Global Variables */ 102 /* ========== Global Variables */
111 103
112 struct event *readlist(void); 104 struct event *readlist(void);
113 void gettoday(void); 105 void gettoday(void);
114 unsigned delta(struct date *); 106 unsigned delta(struct date *);
115 unsigned ddiff(struct date *D1, struct date *D2); 107 unsigned ddiff(struct date *D1, struct date *D2);
116 void liststrings(struct event *evl, prnfunc outf); 108 void liststrings(struct event *evl);
117 char *tdelta(struct date *d); 109 char *tdelta(struct date *d);
118 char *ttime(int yr, int mn, int wk, int dy); 110 char *ttime(int yr, int mn, int wk, int dy);
119 int skptok(int j, char *ptr); 111 char *skptok(char *ptr);
120 int evcmp(const void *e1, const void *e2); 112 int evcmp(const void *e1, const void *e2);
121 113
122 114
123 struct date today; 115 struct date today;
124 int iDWarn = DEF_WARN; 116 int def_warn = DEF_WARN;
125 117
126 const unsigned MLENDAT[] = {31,-1,31,30,31,30,31,31,30,31,30,31};
127 118
128 const struct _ftable FTABLE[] = { 119 const struct _ftable FTABLE[] = {
129 {"bd", F_TBIRTHDAY}, 120 {"#ann",F_TANNIVERSARY},
130 {"ann",F_TANNIVERSARY}, 121 {"#ev", F_TEVENT},
131 {"ev", F_TEVENT}, 122 {"#w", F_WTIME_P},
132 {"w", F_WTIME_P}, 123 {"#to", F_TODATE},
133 {"to", F_TODATE}, 124 {"#for", F_FORDAYS},
134 {"for", F_FORDAYS},
135 {NULL, 0} 125 {NULL, 0}
136 }; 126 };
137 127
138 128
139 129
172 /* ========== */ 162 /* ========== */
173 163
174 164
175 /* 165 /*
176 like strcat(), but lets the buffer automagically grow :-) 166 like strcat(), but lets the buffer automagically grow :-)
177 (needs local variable "size" with the buffer size) 167 */
178 */ 168 int
179 #define append(where, what) do { \ 169 append(char *where, int size, char *what)
180 if (strlen(what) > (size - strlen(where))) { \ 170 {
181 xrealloc(where, size + 128 + strlen(what)); \ 171 if (strlen(what) > ((size) - strlen(where))) {
182 size += 128 + strlen(what); \ 172 xrealloc(where, (size) + 128 + strlen(what));
183 } \ 173 size += 128 + strlen(what);
184 strcat(where, what); \ 174 }
185 } while(0) 175 strcat(where, what);
176 return size;
177 }
186 178
187 /* ========== */ 179 /* ========== */
180
181
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 }
193
194 int
195 ydelta(struct date a, struct date b)
196 {
197 return b.year - a.year + before(a, b);
198 }
199
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};
207
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 }
218
219
188 220
189 /* 221 /*
190 returns delta(d) in days, weeks, months, etc 222 returns delta(d) in days, weeks, months, etc
191 the returned buffer is malloc()ed, do not forget to free() it 223 the returned buffer is malloc()ed, do not forget to free() it
192 */ 224 */
199 int size = 128; 231 int size = 128;
200 232
201 *buf = 0; 233 *buf = 0;
202 switch (delta(d)) { 234 switch (delta(d)) {
203 case 0: 235 case 0:
204 append(buf, "today"); 236 size = append(buf, size, "TODAY");
205 return buf; 237 return buf;
206 case 1: 238 case 1:
207 append(buf, "tomorrow"); 239 size = append(buf, size, "Tomorrow");
208 return buf; 240 return buf;
209 default: 241 default:
210 /* like delta(), we ignore the year */ 242 /* like delta(), we ignore the year */
211 yr = -before(*d, today); 243 yr = -before(*d, today);
212 mn = d->month - today.month; 244 mn = d->month - today.month;
222 } 254 }
223 255
224 wk = (dy / 7); 256 wk = (dy / 7);
225 dy %= 7; 257 dy %= 7;
226 258
227 append(buf, "in "); 259 size = append(buf, size, "In ");
228 tmp = ttime(yr, mn, wk, dy); 260 tmp = ttime(yr, mn, wk, dy);
229 append(buf, tmp); 261 size = append(buf, size, tmp);
230 free(tmp); 262 free(tmp);
231 263
232 return buf; 264 return buf;
233 } 265 }
234 } 266 }
235 267
236 268
237 269
238 270
239 271
240 /*
241 void 272 void
242 donum(n,txt) 273 donum(char *buf, int size, int n, char *txt, int *terms)
243 { 274 {
244 if (n > 0) { 275 char tmp[128];
245 snprintf(tmp, sizeof(tmp), "%d", n); 276
246 append(buf, tmp); 277 if (n <= 0) {
247 append(buf, " " txt); 278 return;
248 if (n != 1) 279 }
249 append(buf, "s"); 280 snprintf(tmp, sizeof(tmp), "%d", n);
250 terms--; 281 size = append(buf, size, tmp);
251 if (orgterms > 1) { 282 size = append(buf, size, " ");
252 if (terms == 1) 283 size = append(buf, size, txt);
253 append(buf, " and "); 284 if (n != 1) {
254 else if (terms > 1) 285 size = append(buf, size, "s");
255 append(buf, ", "); 286 }
256 } 287 if (--*terms == 1) {
257 } 288 size = append(buf, size, " and ");
258 } 289 } else if (*terms > 1) {
259 */ 290 size = append(buf, size, ", ");
260 291 }
261 292 }
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)
278 293
279 294
280 /* returns allocated buffer, don't forget to free() */ 295 /* returns allocated buffer, don't forget to free() */
281 char * 296 char *
282 ttime(int yr, int mn, int wk, int dy) 297 ttime(int yr, int mn, int wk, int dy)
283 { 298 {
284 char *buf = xmalloc(128);
285 int size = 128; 299 int size = 128;
286 int terms, orgterms; 300 char *buf = xmalloc(size);
287 char tmp[128]; 301 int terms = (yr!=0) + (mn!=0) + (wk!=0) + (dy!=0);
288 302
289 *buf = 0; /* Initialize buffer */ 303 *buf = '\0'; /* Initialize buffer */
290 terms = orgterms = (yr!=0) + (mn!=0) + (wk!=0) + (dy!=0); 304
291 305 donum(buf, size, yr, "year", &terms);
292 donum(yr, "year"); 306 donum(buf, size, mn, "month", &terms);
293 donum(mn, "month"); 307 donum(buf, size, wk, "week", &terms);
294 donum(wk, "week"); 308 donum(buf, size, dy, "day", &terms);
295 donum(dy, "day");
296 309
297 return buf; 310 return buf;
298 } 311 }
299 #undef donum
300 312
301 313
302 314
303 315
304 316
306 /* 318 /*
307 lists the birthdays in their string format, one by one, and passes 319 lists the birthdays in their string format, one by one, and passes
308 the string to a function. 320 the string to a function.
309 */ 321 */
310 void 322 void
311 liststrings(struct event *evl, prnfunc outf) 323 liststrings(struct event *evl)
312 { 324 {
313 int i,j; 325 int i,j;
314 char *buf, *tmp; 326 char *buf, *tmp;
315 int size; 327 int size;
316 328
317 for (i = 0; evl[i].text != NULL; i++) { 329 for (i=0; evl[i].text; i++) {
318 buf = xmalloc(128); 330 size = 128;
331 buf = xmalloc(size);
319 *buf = '\0'; 332 *buf = '\0';
320 size = 128;
321 333
322 if (evl[i].warn == -1 && delta(&(evl[i].date))==0) { 334 if (evl[i].warn == -1 && delta(&(evl[i].date))==0) {
323 append(buf, evl[i].text); 335 size = append(buf, size, evl[i].text);
324 } else if (evl[i].enddate.day == 0) { 336 } else if (evl[i].enddate.day == 0) {
337
325 if (delta(&(evl[i].date)) <= evl[i].warn) { 338 if (delta(&(evl[i].date)) <= evl[i].warn) {
326 append(buf, evl[i].text);
327 append(buf, " ");
328 tmp = tdelta(&(evl[i].date)); 339 tmp = tdelta(&(evl[i].date));
329 append(buf, tmp); 340 size = append(buf, size, tmp);
341 size = append(buf, size, ": ");
342 size = append(buf, size, evl[i].text);
330 free(tmp); 343 free(tmp);
331 } 344 }
332 } else { 345 } else {
333 if (delta(&(evl[i].date)) <= evl[i].warn) { 346 if (delta(&(evl[i].date)) <= evl[i].warn) {
334 append(buf, evl[i].text); 347 size = append(buf, size, evl[i].text);
335 append(buf, " for "); 348 size = append(buf, size, " for ");
336 /* +1 because, if the difference between two dates is one day, 349 /* +1 because, if the difference between
337 then the length of an event on those days is two days */ 350 two dates is one day, then the length of
351 an event on those days is two days */
338 j = ddiff(&(evl[i].date),&(evl[i].enddate)) + 1; 352 j = ddiff(&(evl[i].date),&(evl[i].enddate)) + 1;
339 tmp = ttime(0, 0, j/7, j%7); 353 tmp = ttime(0, 0, j/7, j%7);
340 append(buf, tmp); 354 size = append(buf, size, tmp);
341 free(tmp); 355 free(tmp);
342 append(buf, " "); 356 size = append(buf, size, " ");
343 tmp = tdelta(&(evl[i].date)); 357 tmp = tdelta(&(evl[i].date));
344 append(buf, tmp); 358 size = append(buf, size, tmp);
345 } else if (delta(&(evl[i].enddate)) <= evl[i].warn) { 359 } else if (delta(&(evl[i].enddate)) <= evl[i].warn) {
346 append(buf, evl[i].text); 360 size = append(buf, size, evl[i].text);
347 append(buf, " "); 361 size = append(buf, size, " ");
348 j = delta(&(evl[i].enddate)); 362 j = delta(&(evl[i].enddate));
349 if (j) { 363 if (j) {
350 append(buf, "for "); 364 size = append(buf, size, "for ");
351 tmp = ttime(0, 0, j/7, j%7); 365 tmp = ttime(0, 0, j/7, j%7);
352 append(buf, tmp); 366 size = append(buf, size, tmp);
353 free(tmp); 367 free(tmp);
354 append(buf, " longer"); 368 size = append(buf, size, " longer");
355 } else { 369 } else {
356 append(buf, "finishes today"); 370 size = append(buf, size, "finishes today");
357 } 371 }
358 } 372 }
359 } 373 }
360 if (*buf) { 374 if (*buf) {
361 append(buf, "."); 375 size = append(buf, size, ".");
362 outf(buf); 376 puts(buf);
363 } 377 }
364 free(buf); 378 free(buf);
365 } 379 }
366 } 380 }
367 381
532 readlist() 546 readlist()
533 { 547 {
534 int i, j, k, l, d; 548 int i, j, k, l, d;
535 struct event *evl; 549 struct event *evl;
536 char buf[1024], buf2[1024]; 550 char buf[1024], buf2[1024];
537 char *ptr; 551 char *ptr, *cp;
538 unsigned flags; 552 unsigned flags;
539 553
540 /* initialise */ 554 /* initialise */
541 gettoday(); 555 gettoday();
542 556
548 i--; 562 i--;
549 continue; 563 continue;
550 } 564 }
551 565
552 /* parse string in buf */ 566 /* parse string in buf */
553 ptr = strrchr(buf, '='); /* allow '=' in text */ 567
568 ptr = strchr(buf, ' '); /* start of text */
554 569
555 /* not a valid line, so ignore it! Cool, huh? */ 570 /* not a valid line, so ignore it! Cool, huh? */
556 /* Attention: only recognizes lines without '=' */ 571 /* Attention: only recognizes lines without '=' */
557 if (ptr == NULL) { 572 if (!ptr) {
558 fprintf(stderr, "WARNING: Invalid line in input:\n%s", buf); 573 fprintf(stderr, "WARNING: Invalid input line:\n\t%s", buf);
559 i--; 574 i--;
560 continue; 575 continue;
561 } 576 }
562 577
563 *(ptr++) = 0; 578 *(ptr++) = '\0';
564 579 ptr[strlen(ptr)-1] = '\0';
565 j = sscanf(ptr, "%u-%u-%u", &(evl[i].date.year), 580
581 j = sscanf(buf, "%u-%u-%u", &(evl[i].date.year),
566 &(evl[i].date.month), &(evl[i].date.day)); 582 &(evl[i].date.month), &(evl[i].date.day));
567 /* ... unless it wasn't read, in which case set it to zero */ 583 if (j != 3) {
568 if (j==2) { 584 fprintf(stderr, "Error: Invalid date:\t%s\n", buf);
569 evl[i].date.year = 0; 585 i--;
570 } 586 continue;
571 587 }
572 588
573 /* parse flags */ 589 /* parse flags */
574 590
575 evl[i].warn = iDWarn; 591 evl[i].warn = def_warn;
576 evl[i].enddate.day = 0; 592 evl[i].enddate.day = 0;
577 evl[i].enddate.month = 0; 593 evl[i].enddate.month = 0;
578 evl[i].enddate.year = 0; 594 evl[i].enddate.year = 0;
579 595
580 flags = 0; 596 flags = 0;
581 j = 0; 597 j = 0;
582 598 cp = skptok(ptr);
583 while(j = skptok(j, ptr), ptr[j] != 0) { 599 for (cp=ptr; *cp && *cp=='#'; cp=skptok(cp)) {
584 for (k = 0; FTABLE[k].txt != NULL && strncmp(FTABLE[k].txt, ptr + j, strlen(FTABLE[k].txt)); k++) { 600 for (k = 0; FTABLE[k].txt && strncmp(FTABLE[k].txt, cp, strlen(FTABLE[k].txt)); k++) {
585 } 601 }
586 602
587 switch (FTABLE[k].flag) { 603 switch (FTABLE[k].flag) {
588 case F_WTIME_P: /* w <n> -- sets warning time */ 604 case F_WTIME_P: /* #w<n> -- sets warning time */
589 sscanf(ptr + j, "w %u", &(evl[i].warn)); 605 sscanf(cp, "#w%u", &(evl[i].warn));
590 break; 606 break;
591 case F_FORDAYS: /* for <days> -- sets the duration of the event */ 607 case F_FORDAYS: /* #for<days> -- sets the duration of the event */
592 sscanf(ptr + j, "for %u", &d); 608 sscanf(cp, "#for%u", &d);
593 evl[i].enddate=evl[i].date; 609 evl[i].enddate=evl[i].date;
594 for (l = 1; l < d; l++) { 610 for (l = 1; l < d; l++) {
595 evl[i].enddate.day++; 611 evl[i].enddate.day++;
596 if (evl[i].enddate.day > mlen(evl[i].enddate.month, evl[i].enddate.year)) { 612 if (evl[i].enddate.day > mlen(evl[i].enddate.month, evl[i].enddate.year)) {
597 evl[i].enddate.month++; 613 evl[i].enddate.month++;
601 evl[i].enddate.year++; 617 evl[i].enddate.year++;
602 evl[i].enddate.month = 1; 618 evl[i].enddate.month = 1;
603 } 619 }
604 } 620 }
605 break; 621 break;
606 case F_TODATE: /* to <date> -- sets the end date of the event */ 622 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)); 623 l = sscanf(cp, "#to%u-%u-%u", &(evl[i].enddate.year), &(evl[i].enddate.month), &(evl[i].enddate.day));
608 if (l == 2) { 624 if (l == 2) {
609 evl[i].enddate.year = 0; 625 evl[i].enddate.year = 0;
610 } 626 }
611 break; 627 break;
612 case 0: 628 case 0:
619 635
620 636
621 /* construct event text */ 637 /* construct event text */
622 638
623 switch(flags & F_MTYPE) { 639 switch(flags & F_MTYPE) {
624 case F_TBIRTHDAY:
625 default: /* assume it's a birthday */ 640 default: /* assume it's a birthday */
626 if (evl[i].date.year != 0) { 641 if (!evl[i].date.year) {
627 int tmp_age = ydelta(evl[i].date, today); 642 sprintf(buf2, "%s has a birthday", cp);
628 if (tmp_age != 1) { 643 break;
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 } 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":"");
636 break; 648 break;
637 case F_TANNIVERSARY: 649 case F_TANNIVERSARY:
638 if (evl[i].date.year != 0) { 650 if (evl[i].date.year) {
639 sprintf(buf2, "%s %d years ago", buf, ydelta(evl[i].date, today)); 651 sprintf(buf2, "%s %d years ago",
652 cp, ydelta(evl[i].date, today));
640 } else { 653 } else {
641 strcpy(buf2, buf); 654 strcpy(buf2, cp);
642 } 655 }
643 break; 656 break;
644 case F_TEVENT: 657 case F_TEVENT:
645 /* if a year was specified, and this warning isn't for it, ignore! */ 658 /* if a year was specified, and this
646 if ((evl[i].date.year != 0 && ydelta(evl[i].date, today) != 0) 659 warning isn't for it, ignore! */
647 && (evl[i].enddate.year == 0 || ydelta(evl[i].enddate, today) != 0)) { 660 if ((evl[i].date.year && ydelta(evl[i].date, today))
661 && (!evl[i].enddate.year || ydelta(evl[i].enddate, today))) {
648 i--; 662 i--;
649 continue; 663 continue;
650 } 664 }
651 strcpy(buf2, buf); 665 strcpy(buf2, cp);
652 break; 666 break;
653 } 667 }
654 evl[i].text = strdup(buf2); 668 evl[i].text = strdup(buf2);
655 } 669 }
656 670
669 683
670 684
671 685
672 686
673 687
674 int 688 char *
675 skptok(int j, char *ptr) 689 skptok(char *ptr)
676 { 690 {
677 for (; ptr[j] != 0 && ptr[j] != ' ' && ptr[j] != '\t' ; j++); 691 while (*ptr && (*ptr!=' ' && *ptr!='\t')) {
678 for (; ptr[j] != 0 && (ptr[j] == ' ' || ptr[j] == '\t'); j++); 692 ptr++;
679 693 }
680 return j; 694 while (*ptr && (*ptr==' ' || *ptr=='\t')) {
695 ptr++;
696 }
697 return ptr;
681 } 698 }
682 699
683 700
684 701
685 702
686 703
687 704
688 int 705 int
689 main(int argc, char *argv[]) 706 main(int argc, char *argv[])
690 { 707 {
691 while (--argc > 0 && (*++argv)[0] == '-') { 708 while (--argc > 0 && **++argv == '-') {
692 if (strcmp(argv[0], "-W") == 0) { 709 if (strcmp(argv[0], "-W") == 0) {
693 /* TODO: catch if no value given */ 710 /* TODO: catch if no value given */
694 iDWarn = atoi((++argv)[0]); 711 def_warn = atoi((++argv)[0]);
695 argc--; 712 argc--;
696 } else { 713 } else {
697 fprintf(stderr, "unknown option %s\n", argv[0]); 714 fprintf(stderr, "unknown option %s\n", argv[0]);
698 exit(1); 715 exit(1);
699 } 716 }
700 } 717 }
701 718 liststrings(readlist());
702 liststrings(readlist(), puts);
703
704 return 0; 719 return 0;
705 } 720 }