/****************************************************************************

            F A S T    C A L E N D A R

        This C program is a faster more general version of the
    standard UNIX utility CALENDAR(1).  It recognizes the following
    formats for dates.

        numeric/numeric                     As is 1/12, 01/12, 12/1, ...

        3-charmonth[any chars] numeric      As in January 12, Jan. 12,
                                            jan. 12, JAN. 12, ...

        3-charweekday[any chars]            As in Friday, Monday, MON.,
                                            mon., tue, wed, ...

        numeric month                       A month from 1 to 12 as in
                                            1, 2, 04, 12, ...

    Any lines, following a line that is matched, that have "whitespace"
    in the first column will be printed along with the matched line.

****************************************************************************/

#include    <stdio.h>
#include    <ctype.h>
#include    <sys/types.h>
#include    <time.h>
#include    <pwd.h>

/*  Number of seconds in a day.  */

#define     DAY     (24*3600)

/*  Remove this for SYS5 machines, or others with strchr instead of index.  */

/*  #define     strchr  index   */

/*  Global input line.  */

char datebuf[300], *getdate();

main (argc, argv)
    int argc;
    char **argv;
{
    register char *s;
    char success;
    struct passwd *passwd, *getpwuid();
    register char file[200];
    register FILE *fp;
    register int i;

    /*
            Use file passed as an argument if it is there,
        otherwise use file in home directory.
    */

    if (argc <= 1) {
        if ((passwd = getpwuid (getuid())) == (struct passwd *)0) {
            perror ("Hi Dennis, the compiler is still broke\n");
            exit(1);
        }
        if (passwd -> pw_dir != (char *)0)
            strcpy (file, passwd -> pw_dir);
        strcat (file, "/.calendar");
    } else {
        strcpy (file, argv[1]);
    }

    /*  Open the input file.  */

    if ((fp = fopen (file, "r")) == NULL) {
        perror (file);
        exit (1);
    }
    
    /*  Initially, no success.  */

    success = 0;

    /*  Loop till EOF is encountered.  */

    while (s = getdate(fp, &success)) {

        /*  Check date for "NOW"  */

        if (isnow (datebuf)) {

            /*  Print it, it is valid.  */

            printf(s);
        } else {

            /* Reset the "print lines with leading blanks" flag. */

            success = 0;
        }
    }

    /*  Close up and exit.  */

    fclose (fp);
    exit (0);
}

/*
        Check the input line, and get only the lines without leading
    "whitespace".  Conditionally print those lines with leading blanks,
    based on the "success" flag.
*/

char *getdate(fp, success)
    char *success;
    register FILE *fp;
{
    register int c;
    static char inbuf[300];
    register char *t, *s;

    t = inbuf;
    s = datebuf;
    
    /*  Skip leading space.  */

    while ((c = getc(fp)) <= ' ' && c != EOF) {
        ungetc(c, fp);
        fgets (inbuf, 300, fp);

        /*  Print the line if we previously printed a match.  */

        if (*success)
            printf (inbuf);
    }

    /*  Assume success for now.  */

    *success = 1;

    /*  Return EOF if we are there.  */

    if (c == EOF)
        return ((char *)0);

    /*  Unget the character.  */

    ungetc (c, fp);

    /*  Get the input line.  */

    fgets (t, 300, fp);

    /*  Make a copy of it.  */

    strcpy (s, t);

    return (inbuf);
}

/*  Month definitions.  */

char *months[] = {
    "JAN", "FEB", "MAR",
    "APR", "MAY", "JUN",
    "JUL", "AUG", "SEP",
    "OCT", "NOV", "DEC"
};

/*  Weekday definitions.  */

char *weekdays[] = {
    "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"
};

/*  See if the passed string has a "NOW" date in it.  */

isnow (datebuf)
    char *datebuf;
{
    char *s, *t;
    long l;
    register int before = 0, after = 0, i, offset, month, day;
    register struct tm *tmptr;
    long timenow;

    /*  Get the current time.  */

    time (&timenow);
    tmptr = localtime (&timenow);

    /*  Is the first character a digit in a Month?  */

    t = datebuf;
    if (isdigit (*t) && (month = getnum (&t) - 1) >= 0 && month < 12) {

        /*  Find the separating slash.  */

        if (s = (char *) strchr (t, '/'))
            t = s + 1;
        else if (tmptr -> tm_mon == month)
            return (1);
        else
            return (0);

    /*  Is the string a month name?  */

    } else if ((month = ischmonth (t)) >= 0) {

        /*  Skip till while space.  */

        while (!isspace (*t) && *t)
            ++t;

        /*  Skip to first NON-white space.  */

        while (isspace (*t) && *t)
            ++t;

    /*  Not a month, so try a DAY as in Friday, Saturday, etc...  */

    } else if ((day = ischday (datebuf)) >= 0) {

        /*  Is this today?  */

        if (day == tmptr -> tm_wday || ((day + 6)%7) == tmptr -> tm_wday)
            return (1);
        else
            return (0);
    } else {

        /*  Fail to recognize any other data as a valid date.  */

        return (0);
    }

    /*  Is there only what looks to be a month?  If so, then OK.  */

    if (!*t || !isdigit (*t)) {

        /*  Is it this month?  */

        if (tmptr -> tm_mon == month)
            return (1);

        return (0);
    }

    /*  Calculate the value of the day.  */

    day = getnum (&t);

    /*  Check for *w or *W extended week specifiers.  */

    if (*t == '*') {
        if (t[1] == 'w')
            ++before;
        if (t[1] == 'W')
            ++after;
        if (t[2] == 'W')
            ++after;
        if (t[2] == 'w')
            ++before;
    }
            
    /*  Set weekend offset.  */

    offset = 1;

    /*  Get offset based on the weekend.  */

    switch (tmptr -> tm_wday) {
        case 5:
            ++offset;

        case 6:
            ++offset;

        default:
            ++offset;
    }

    /*  Step through all dates by DAY.  */

    l = timenow - (after * 7 * DAY);

    while (l < timenow + (before * 7 * DAY) + (offset * DAY)) {
        if (istoday (l, month, day))
            return (1);

        l += DAY;
    }

    /*  Return failure.  */

    return (0);
}
    
/*
        Check if the string s is a DAY of the week as in monday, tues,
    wed, THU., or any derivative thereof.
*/

ischday (s)
    register char *s;
{
    register char *tt;
    register int i, found = 0, c;
    register char t[30];

    /*  Copy the input, and capitalize it.  */

    for (tt = t, i=0; isalpha(*s) && i < 28; ++i)
        *tt++ = islower (c = *s++) ? toupper (c) : c;

    *tt = '\0';

    /*  Search the list of valid day names for the string.  */

    for (i = 0; i < 7; ++i) {
        if (strncmp (t, weekdays[i], 3) == 0) {
            found++;
            break;
        }
    }

    /*  Return the numeric equivalent if we found it.  */

    if (found)
        return (i);

    /*  Return invalid day name.  */

    return (-1);
}

/*
        Check if the string s is a valid derivative of the name of a
    month as in JUNE, jun, August, etc...
*/

ischmonth (s)
    register char *s;
{
    register char *tt;
    register int i, found = 0, c;
    register char t[30];

    /*  Copy and capitalize.  */

    for (tt = t, i=0; isalpha(*s) && i < 28; ++i)
        *tt++ = islower (c = *s++) ? toupper (c) : c;

    *tt = '\0';

    /*  Look through the list for a match.  */

    for (i = 0; i < 12; ++i) {
        if (strncmp (t, months[i], 3) == 0) {
            found++;
            break;
        }
    }

    /*  Return the numeric equivalent if we found it.  */

    if (found)
        return (i);

    /*  Return faliure.  */

    return (-1);
}

/*
        Scan the string *t, and evaluate any integer contained there.
    It is assumed that at least 1 digit exists (**t).
*/

getnum (t)
    char **t;
{
    register int i, c;
    register char *s;

    i = 0;

    /*  Get the starting address.  */

    s = *t;

    /*  Get the starting value from the first digit.  */

    i = (c = *s++) - '0';

    /*  Evaluate the rest of the digits (if any).  */

    while (isdigit (c = *s++))
        i = i * 10 + (c - '0');

    /*  Set the new address.  */

    *t = --s;

    /*  Return the value.  */

    return (i);
}

/*
    Return whether or not the time t corresponds to the month and day.
*/

istoday (t, month, day)
    long t;
    register int month, day;
{
    register struct tm *tm;

    tm = localtime(&t);

    return (tm -> tm_mon == month && tm -> tm_mday == day);
}
