[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: hbday leap year prob
- To: http://dummy.us.eu.org/robert (robert)
- Subject: Re: hbday leap year prob
- From: http://www.trudge.engr.sgi.com/~mja (Mike Abbott)
- Date: Mon, 28 Feb 2000 14:40:57 -0800 (PST)
- In-Reply-To: <20000228143221.30262.http://www.continuum_si.com/~qmail> from "robert" at Feb 28, 2000 02:32:02 PM
> 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);
}