> Just FYI: it seems that hbday incorrectly thought that today was the last > day of February. You're right. I'm not surprised. Here's a new version. ---------------------------------------- /* * hbday Keep track of holidays and birthdays so we don't have to. * * Written by Michael J. Abbott '89 (LSRHS '85) at Cornell University. * August 1987. Revised February 1989. * * Usage: hbday [-behln] [-f database] [daysahead] * * All internal dates are in days since Monday, January 1, 1900. */ #include <sys/types.h> #include <stdio.h> #include <time.h> #include <strings.h> /* #define DEBUG */ #ifdef SYSV #define index strchr #endif #define issep(c) (index(seplist, c) != NULL) #define skipsep(p) while (*(p) && issep(*(p))) (p)++ #define tosep(p) while (*(p) && !issep(*(p))) (p)++ char seplist[] = " \t,/-"; /* Characters separating fields in database */ char defdb[] = ".hbdays"; /* Default database is $HOME/.hbdays */ #ifndef LIBDB # define LIBDB "/usr/lib/hbdays" #endif char libdb[] = LIBDB; /* System-wide database file */ int thisyear; /* Today's year */ int thismonth, thisday; /* For makedate */ int holidahead = 0; /* Nonzero prints upcoming holidays too */ int exact = 0; /* Nonzero does n days ahead, not <= n days */ int nobeeps = 0; /* Nonzero prevents beeping */ #ifdef DEBUG int debug = 1; #else int debug = 0; #endif /* * The entries which should be printed are placed in the hbtab table and * sorted by the hb_diff field (number of days until the event). Then the * contents of the table are printed top down. */ #define NHB 250 struct hbd { char *hb_text; /* String to print for active event */ int hb_diff; /* Difference of date of event and today */ enum hbtype { Holiday, Birthday, Anniversary } hb_type; /* Type of event */ int hb_m; /* Month of event */ int hb_d; /* Day of event */ int hb_y; /* Year of event */ } hbtab[NHB]; int hbindex = 0; /* Number of entries in hbtab[] */ /* Straight out of K&R */ #define leap(y) (y % 4 == 0 && y % 100 != 0 || y % 400 == 0) /* Month days and names, both with 0 origin (january = 0) */ short mdays[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; char *mnames[12] = { "january", "february", "march", "april", "may", "june", "july", "august", "september", "october", "november", "december" }; #define NDB 16 /* Max number of files can read from */ char *dblist[NDB + 1]; /* List of files to read from, NULL at end */ main(argc, argv) int argc; char *argv[]; { register FILE *fp; register int c; register struct tm *tp; register char **dbl; int daysahead, today, readlibdb = 1, readhomedb = 1; time_t now; char database[128]; extern char *optarg; extern int optind; extern char *getenv(); #ifdef DEBUG char todaybuf[32]; #endif dbl = &dblist[2]; /* Parse the arguments */ while ((c = getopt(argc, argv, "bdef:hln")) != EOF) { switch (c) { case 'b': nobeeps = 1; break; case 'd': debug = 1; break; case 'e': exact = 1; break; case 'f': if (dbl >= &dblist[NDB]) fprintf(stderr, "%s: Too many databases! (%d max)\n", argv[0], NDB); else *dbl++ = optarg; break; case 'h': holidahead = 1; break; case 'l': readlibdb = 0; break; case 'n': readhomedb = 0; break; default: fprintf(stderr, "Usage: %s [-behln] [-f database] [daysahead]\n", argv[0]); exit(1); } } /* Parse the days ahead argument, if there is one. */ if (optind > 0 && optind < argc && (daysahead = atoi(argv[optind])) < 0) daysahead = 0; dbl = &dblist[1]; /* Read the personal database ($HOME/.hbdays) unless -n given */ if (readhomedb) { char *p = getenv("HOME"); if (p == NULL) p = ""; sprintf(database, "%s/%s", p, defdb); *dbl-- = database; } /* Read the system database unless -l given */ if (readlibdb) *dbl = libdb; if (*dbl == NULL) dbl++; /* Find out what the current date is. */ time(&now); tp = localtime(&now); thisyear = tp->tm_year + 1900; thismonth = tp->tm_mon; thisday = tp->tm_mday; #ifdef DEBUG printf("Enter date to use (m/d/y) or CR for today: "); fflush(stdout); gets(todaybuf); if (todaybuf[0] != '\0') { sscanf(todaybuf, "%d/%d/%d", &thismonth, &thisday, &thisyear); thismonth--; } printf("Today is %s %d %d\n", mnames[thismonth], thisday, thisyear); #endif today = makedate(thismonth, thisday, thisyear); if (debug) printf("today = %d\n", today); while (*dbl != NULL) { if (debug) printf("reading %s\n", *dbl); if ((fp = fopen(*dbl, "r")) == NULL) { fprintf(stderr, "%s: Cannot open \"%s\"\n", argv[0], *dbl); dbl++; continue; } dbl++; /* * Scan once for both today's and upcoming holidays and * birthdays. */ hbscan(fp, today, daysahead); fclose(fp); } /* Print the selected entries, if there are any. */ if (hbindex > 0) prhbtab(); exit(0); } /* * Scan the file fp for events that occur either today or daysahead days from * today, and print them. */ hbscan(fp, today, daysahead) register FILE *fp; int today, daysahead; { register char *bp; char buf[BUFSIZ], *xp; register int c, diff, entdate, countdown, noadvance; enum hbtype type; int em, ed, ey; while (fgets(buf, sizeof buf, fp) != NULL) { bp = buf; skipsep(bp); c = *bp++; /* Check for the overriding countdown flag */ if (c == 'C' || c == 'c') { countdown = 1; skipsep(bp); c = *bp++; } else countdown = 0; /* Check for the overriding no advance warning flag */ if (c == 'N' || c == 'n') { if (countdown) fprintf(stderr, "hbday: Cannot specify both N and C, line is:\n%s", buf); noadvance = 1; skipsep(bp); c = *bp++; } else noadvance = 0; /* Get the type of the event */ if (c == 'H' || c == 'h') type = Holiday; else if (c == 'B' || c == 'b') type = Birthday; else if (c == 'A' || c == 'a') type = Anniversary; else /* Treat as a comment */ continue; skipsep(bp); /* * I want bp to be a register above, but it can't be for the * dateof call. Sigh. */ xp = bp; entdate = dateof(&xp, type, &em, &ed, &ey); diff = entdate - today; if (debug) printf("%s%d (%d/%d/%d), diff = %d: ", buf, entdate, em + 1, ed, ey, diff); if (entdate != -1 && diff >= 0 && (countdown || (diff <= daysahead && (!exact || diff == 0 || diff == daysahead) && (diff == 0 || (!noadvance && (holidahead || type != Holiday)))))) { if (debug) printf("YES\n"); addentry(xp, diff, type, em, ed, ey); } else if (debug) printf("no\n"); } } /* * Compute the date held in *bpp and return it. *mp, *dp, and *yp get the * computed month, day, and year, respectively, of the date, and *bpp gets the * pointer to the message to be printed. If type != Holiday, then ignore * any year in bp and use instead the current year. This is to allow birthday * messages to give ages, and holidays to be once-only if a year is specified * in the database. * This assumes bp points to the first non-blank character of the date. */ dateof(bpp, type, mp, dp, yp) char **bpp; enum hbtype type; int *mp, *dp, *yp; { register char *bp = *bpp; register int month, day, year, nth = -1, wday = -1, rdate, weekday = 0; int kday; static char wdays[] = "UMTWHFS"; /* Parse the month. */ month = atoi(bp); if (month == 0) /* Perhaps it's a string, like "November" */ month = monthof(bp); if (month < 1 || month > 12) return -1; /* At this point, 1 <= month <= 12. (not 0 <= month <= 11) */ tosep(bp); skipsep(bp); /* * Next field may be a day of the month (a number), or something of * the form "#NX", where N is a digit (1-5, really) and X is one of * the days of the week UMTWHFS. This second form is for floating * holidays, like Labor Day, which is the first Monday of September. * * Alternatively, the day field could be of the form "~NX", where N * is a day of the month and X is one of UMTWHFS. This form will * calculate the week on which N falls and then slide to the X day, * so "~5m" would find which week the 5th falls on in that month, * and make that monday the holiday. * * The latest additions are "@N", where N is a day of the month, which * translates to the weekday (MTWHF) closest to N but still within * the month; "<N" which translates to the weekday of or Friday * before N, and ">N" which translates to the weekday of or Monday * after N. */ if (*bp == '#') { if ((*++bp < '1' || *bp > '5') && *bp != '$') return -1; #define LASTNTH 99 nth = (*bp == '$') ? LASTNTH : *bp - '0'; if (*++bp >= 'a' && *bp <= 'z') *bp += 'A' - 'a'; wday = index(wdays, *bp) - wdays; if (wday < 0 || wday > 6) return -1; /* * To make sure that the date returned by makedate is in * the current year, use KLUDGEDAY as the day of the * month instead of, say, 1. Example: If today is * 9/7/1987, and the entry is "H Sep #1M Labor Day" then * the entry should be printed. But if we use 1 as the * DOM, then today is past the date, and we will not * match. Using a day that forces makedate to place us * in this year will solve this problem. */ #define KLUDGEDAY 98 day = KLUDGEDAY; /* Must remember to subtract this! */ } else if (*bp == '~') { day = atoi(++bp); if (day < 1 || day > 31) return -1; while (*++bp && *bp >= '0' && *bp <= '9') ; if (*bp >= 'a' && *bp <= 'z') *bp += 'A' - 'a'; wday = index(wdays, *bp) - wdays; if (wday < 0 || wday > 6) return -1; } else if (*bp == '@') { #define LASTDAY 97 if (*++bp == '$') day = LASTDAY; else { kday = atoi(bp); if (kday < 1 || kday > 31) return -1; day = KLUDGEDAY; } weekday = 1; } else if (*bp == '<') { if (*++bp == '$') day = LASTDAY; else { kday = atoi(bp); if (kday < 1 || kday > 31) return -1; day = KLUDGEDAY; } weekday = 2; } else if (*bp == '>') { if (*++bp == '$') day = LASTDAY; else { kday = atoi(bp); if (kday < 1 || kday > 31) return -1; day = KLUDGEDAY; } weekday = 3; } else { /* Parse the day of the month. */ day = atoi(bp); if (day < 1 || day > 31) return -1; } tosep(bp); skipsep(bp); /* Parse the year. */ year = atoi(bp); if (year == 0) /* No year specified, is ok */ year = -1; else { if (year < 0) return -1; tosep(bp); skipsep(bp); } if (*bp == '\n' || *bp == '\0') return -1; *mp = --month; *dp = day; *yp = year; *bpp = bp; rdate = makedate(month, day, (type == Holiday) ? year : -1); if (nth != -1) { rdate -= KLUDGEDAY - 1; /* Go back to day 1 of the month */ if (nth == LASTNTH) { int leapfact = (month == 1 && leap(thisyear)); nth = wday - (rdate % 7) + 5 * 7; while (nth >= mdays[month] + leapfact) nth -= 7; } else { if (rdate % 7 <= wday) nth--; nth = wday - (rdate % 7) + nth * 7; } *dp = nth + 1; rdate += nth; } else if (wday != -1) { day = wday - (rdate % 7); *dp += day; rdate += day; } else if (weekday) { int leapfact = (month == 1 && leap(thisyear)); if (day == LASTDAY) { day = mdays[month] + leapfact; rdate -= LASTDAY - day; *dp = day; } if (day == KLUDGEDAY) { rdate -= KLUDGEDAY - kday; *dp = day = kday; } if (rdate % 7 == 0) { if (weekday == 2) day = -2; else if (weekday == 3) day = 1; else if (day < mdays[month] + leapfact) day = 1; else day = -2; } else if (rdate % 7 == 6) { if (weekday == 2) day = -1; else if (weekday == 3) day = 2; else if (day > 1) day = -1; else day = 2; } else day = 0; *dp += day; rdate += day; } return rdate; } /* * Return the number (1 to 12) of the month that might be specified in the text * pointed to by bp. If bp doesn't point to a month name, return 0. */ monthof(bp) register char *bp; { register char *cp, *np; register int i; char nbuf[BUFSIZ]; /* * First convert the month name to lowercase, copying it so we don't * corrupt the bp buffer. */ for (cp = bp, np = nbuf; *cp && !issep(*cp); np++, cp++) { *np = *cp; if (*np >= 'A' && *np <= 'Z') *np += 'a' - 'A'; /* convert to lowercase */ } /* Now see if the month matches with anything. */ for (i = 0; i < 12; i++) if (!strncmp(nbuf, mnames[i], cp - bp)) break; if (i < 12) return i + 1; /* Might be an every-month event; return current month if so. */ if (!strncmp(nbuf, "any", cp - bp) || !strncmp(nbuf, "every", cp - bp)) return thismonth + 1; return 0; } /* * Return the date (days since 1/1/1900) of the arguments. If year is -1, * use the current year (thisyear) instead. */ makedate(month, day, year) register int month, day, year; { register int i, date = 0; if (year == -1) { year = thisyear; if (month < thismonth || (month == thismonth && day < thisday)) year++; } /* How the years just fly by... */ date += (year - 1900) * 365; for (i = 1900; i < year; i++) if (leap(i)) date++; for (i = 0; i < month; i++) date += mdays[i]; if (month > 1 && leap(year)) /* Past Feb */ date++; date += day; return date; } /* * Add an event that has already been ascertained as worthy of printing to the * list of active entries. bp is the message or birthday-boy-or-girl's name. * diff is the number of days from today that the event will take place (or 0 * for today), and em, ed, and ey are the event's month, day, and year. */ addentry(bp, diff, type, em, ed, ey) register char *bp; int diff; enum hbtype type; int em, ed, ey; { register char *cp; register struct hbd *hp; extern char *malloc(); /* Strip off the newline that fgets keeps. */ cp = bp; while (*cp && *cp != '\n') cp++; *cp = '\0'; if (*bp == '\0') /* Whoops! */ return; if (hbindex >= NHB) { fprintf(stderr, "hbday internal error: NHB too small\n"); return; } hp = &hbtab[hbindex++]; hp->hb_text = malloc(strlen(bp) + 1); if (hp->hb_text == NULL) { fprintf(stderr, "Out of memory.\n"); exit(1); } strcpy(hp->hb_text, bp); hp->hb_diff = diff; hp->hb_type = type; hp->hb_m = em; hp->hb_d = ed; hp->hb_y = ey; } /* * Sort the list by date and holiday (most recent first, holidays first). */ hbcomp(h1, h2) register struct hbd *h1, *h2; { register int diff = h1->hb_diff - h2->hb_diff; if (diff == 0) return h2->hb_type - h1->hb_type; else return diff; } /* * t="foo (bar)" => n="foo" e="bar " * t="foo" => n="foo" e="" */ anivtext(const char *t, char *n, char *e) { char *op, *cp; if ((op = index(t, '(')) == NULL || (cp = index(op, ')')) == NULL) { strcpy(n, t); *e = 0; return; } if (op[-1] == ' ') op--; strncpy(n, t, op - t); n[op - t] = 0; if (*op == ' ') op++; strncpy(e, op + 1, cp - op - 1); e[cp - op - 1] = ' '; e[cp - op] = 0; } /* * Sort and print the table of selected events. */ prhbtab() { register struct hbd *hp, *stophp; register int printed = 0, beeped = nobeeps; qsort((char *) hbtab, hbindex, sizeof (struct hbd), hbcomp); for (hp = hbtab, stophp = &hbtab[hbindex]; hp < stophp; hp++) { if (hp->hb_diff) { /* This event does not happen today */ if (printed == 0) { printf("Coming up:\n"); printed = 1; } /* Announce it, and cap the first char of the month */ printf("%c%.2s %2d", mnames[hp->hb_m][0] + 'A' - 'a', &mnames[hp->hb_m][1], hp->hb_d); if (hp->hb_diff > 364) printf(", %d", hp->hb_y); printf(": "); switch (hp->hb_type) { case Holiday: printf("%s.", hp->hb_text); break; case Birthday: printf("%s's ", hp->hb_text); if (hp->hb_y != -1 && hp->hb_y < thisyear) prage(thisyear - hp->hb_y); printf("birthday."); break; case Anniversary: { char t[128], p[128]; anivtext(hp->hb_text, t, p); printf("%s's ", t); if (hp->hb_y != -1 && hp->hb_y < thisyear) prage(thisyear - hp->hb_y); printf("%sanniversary.", p); break; } } if (hp->hb_diff == 1) printf(" (tomorrow)\n"); else printf(" (in %d days)\n", hp->hb_diff); } else { /* This event happens today */ switch (hp->hb_type) { case Holiday: printf("%s\n", hp->hb_text); break; case Birthday: if (!beeped) putchar('\007'); printf("HAPPY "); if (hp->hb_y != -1 && hp->hb_y < thisyear) prage(thisyear - hp->hb_y); if (!beeped) putchar('\007'); printf("BIRTHDAY %s!!!\n", hp->hb_text); if (!beeped) putchar('\007'); beeped = 1; break; case Anniversary: { char t[128], p[128]; anivtext(hp->hb_text, t, p); if (!beeped) putchar('\007'); printf("Happy "); if (hp->hb_y != -1 && hp->hb_y < thisyear) prage(thisyear - hp->hb_y); printf("%sanniversary %s!!!\n", p, t); beeped = 1; break; } } } } } /* * Print out someone's age, followed by the correct suffix (one of "st," * "nd," "rd," or "th." */ prage(age) register int age; { register char *suffix; register int digit = age % 10; if (digit == 1 && age != 11) suffix = "st"; else if (digit == 2 && age != 12) suffix = "nd"; else if (digit == 3 && age != 13) suffix = "rd"; else suffix = "th"; printf("%d%s ", age, suffix); }