[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: hbday leap year prob



> 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);
}







Why do you want this page removed?