home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / misc / volume17 / calentool / part16 / utils.c < prev   
C/C++ Source or Header  |  1991-04-06  |  30KB  |  1,149 lines

  1. /*
  2.  * $Header: utils.c,v 2.8 91/03/27 16:46:39 billr Exp $
  3.  */
  4. /*
  5.  * utils.c
  6.  *
  7.  * calentool - a year/month/week/day-at-a-glance calendar for Sun workstations.
  8.  *
  9.  * Author: Philip Heller, Sun Microsystems. Inc. <terrapin!heller@sun.com>
  10.  *
  11.  * Original source Copyright (C) 1987, Sun Microsystems, Inc.
  12.  *    All Rights Reserved
  13.  * Permission is hereby granted to use and modify this program in source
  14.  * or binary form as long as it is not sold for profit and this copyright
  15.  * notice remains intact.
  16.  *
  17.  *
  18.  * Changes/additions by: Bill Randle, Tektronix, Inc. <billr@saab.CNA.TEK.COM>
  19.  *
  20.  * Changes and additions Copyright (C) 1988, 1989, 1991 Tektronix, Inc.
  21.  *    All Rights Reserved
  22.  * Permission is hereby granted to use and modify the modifications in source
  23.  * or binary form as long as they are not sold for profit and this copyright
  24.  * notice remains intact.
  25.  * Modified parse_date to allow +nnn and -nnn syntax for dates relative to the
  26.  * current date.  Peter Marshall <peter.marshall@uwo.ca>. 1989-09-19.
  27.  */
  28. /********************************************
  29.  *                        *
  30.  *              Utility routines.        *
  31.  *                        *
  32.  ********************************************/
  33.  
  34.  
  35.  
  36. #include "ct.h"
  37. #include <stdio.h>
  38. #ifndef NOTOOL
  39. #include <suntool/sunview.h>
  40. #include <suntool/canvas.h>
  41. #endif  /* NOTOOL */
  42. #include <ctype.h>
  43. #include <sys/types.h>
  44. #include <sys/time.h>
  45. #include <sys/file.h>
  46. #include <sys/stat.h>
  47. #include <sys/errno.h>
  48.  
  49.  
  50. extern struct tm today, current;
  51. extern struct tm First;
  52. extern int day_is_open, read_only;
  53. extern int n_slots;
  54. extern struct dayslot *slots;
  55. extern char apts_pathname[], tmpapts_pathname[];
  56. extern char *progname;
  57. extern int one_based, version2, new_entry;
  58. extern char apts_dir[], lib_dir[];
  59. extern int include_old, save_old, expire_days;
  60. #ifndef NOTOOL
  61. extern Frame frame;
  62. extern Canvas canvas;
  63. extern Pixwin *main_pixwin;
  64. extern int mainsw_state;
  65. extern Pixfont *font, *sfont;
  66. extern Frame prompt_frame;
  67. extern int update_interval;
  68. extern int monday_first, hour24, day_first;
  69. #endif  /* NOTOOL */
  70. extern int week_ofs;
  71. extern int errno;
  72.  
  73. char inbuf[512], strbuf[256], errbuf[128];
  74. char clockstr[32];
  75. static int include_level = 0;
  76. static int log_to_console;
  77. char *daynames[] = {"Sunday","Monday","Tuesday","Wednesday",
  78.                "Thursday","Friday","Saturday"};
  79. char *monthnames[] = {"January","February","March","April",
  80.                  "May","June","July","August",
  81.                  "September","October","November","December"};
  82. char *smonthnames[] = {"Jan","Feb","Mar","Apr",
  83.                  "May","Jun","Jul","Aug",
  84.                  "Sep","Oct","Nov","Dec"};
  85. char *dayname[8] = {"SU", "MO", "TU", "WE", "TH", "FR", "SA", "MF"};
  86.  
  87. extern char *strcpy(), *strcat();
  88.  
  89. /*
  90.  * sets "today" and current time
  91.  */
  92. void
  93. get_today()
  94. {
  95.     struct tm *tm;
  96.     struct timeval tv;
  97.     char timstr[16];
  98.  
  99.     gettimeofday(&tv, 0);
  100.     tm = localtime(&tv.tv_sec);
  101.  
  102.     today = *tm;
  103.  
  104. #ifndef CALENCHECK
  105.     if (day_first)
  106.         sprintf(clockstr, "%3.3s %d %s %d, ", daynames[today.tm_wday],
  107.             today.tm_mday, smonthnames[today.tm_mon], today.tm_year+1900);
  108.     else
  109.         sprintf(clockstr, "%3.3s %s %d %d, ", daynames[today.tm_wday],
  110.             smonthnames[today.tm_mon], today.tm_mday, today.tm_year+1900);
  111.     if (update_interval >= 60)
  112.         sprintf(timstr, "%02d:%02d", today.tm_hour, today.tm_min);
  113.     else
  114.         sprintf(timstr, "%02d:%02d:%02d", today.tm_hour, today.tm_min, today.tm_sec);
  115.     if (!hour24) {
  116.         /* display am/pm for 12-hour time */
  117.         if (today.tm_hour > 12) {
  118.             strcat(timstr, "pm");
  119.             timstr[0] = ((today.tm_hour - 12) / 10) + '0';
  120.             timstr[1] = ((today.tm_hour - 12) % 10) + '0';
  121.         } else if (today.tm_hour == 12) {
  122.             strcat(timstr, "pm");
  123.         } else {
  124.             strcat(timstr, "am");
  125.         }
  126.         if (timstr[0] == '0')
  127.             timstr[0] = ' ';
  128.     }
  129.     strcat(clockstr, timstr);
  130. #endif  /* CALENCHECK */
  131. }
  132.  
  133. /*
  134.  *    Reset some values in current tm structure. Year, month and
  135.  *    day-of-month are valid but day and/or month may be < 0 or
  136.  *    greater than the maximum value, in which case they are adjusted
  137.  *    accordingly. Day-of-year and day-of-week are then recalculated.
  138.  */
  139. void
  140. fix_current_day()
  141. {
  142.     int month, totdays = 0;
  143.     struct tm from, to;
  144.  
  145.     if (current.tm_mon < JAN) {
  146.         current.tm_mon = DEC;
  147.         current.tm_year--;
  148.     } else if (current.tm_mon > DEC) {
  149.         current.tm_mon = JAN;
  150.         current.tm_year++;
  151.     }
  152.     if (current.tm_mday < 1) {
  153.         current.tm_mon--;
  154.         if (current.tm_mon < JAN) {
  155.             current.tm_mon = DEC;
  156.             current.tm_year--;
  157.         }
  158.         current.tm_mday += monthlength(current.tm_mon);
  159.     } else if (current.tm_mday > monthlength(current.tm_mon)) {
  160.         current.tm_mday -= monthlength(current.tm_mon);
  161.         current.tm_mon++;
  162.         if (current.tm_mon > DEC) {
  163.             current.tm_mon = JAN;
  164.             current.tm_year++;
  165.         }
  166.     }
  167.     current.tm_yday = day_of_year((double)current.tm_mday, current.tm_mon+1, current.tm_year+1900) - 1;
  168.     current.tm_wday = get_day_of_week((double)current.tm_mday, current.tm_mon+1, current.tm_year+1900);
  169. }
  170.  
  171. /*
  172.  * Compares two sets of year/month/day.  Returns -1 if the first is earlier than
  173.  * the second, +1 if later, 0 if they are the same.
  174.  */
  175. ymd_compare(day0, day1)
  176. struct tm day0, day1;
  177. {
  178.         if (day0.tm_year > day1.tm_year) return(1);
  179.         if (day0.tm_year < day1.tm_year) return(-1);
  180.         if (day0.tm_mon > day1.tm_mon) return(1);
  181.         if (day0.tm_mon < day1.tm_mon) return(-1);
  182.         if (day0.tm_mday > day1.tm_mday) return(1);
  183.         if (day0.tm_mday < day1.tm_mday) return(-1);
  184.         return(0);
  185. }
  186.  
  187. /*
  188.  * Compares two sets of year/month/day.  Returns -1 if the first is earlier than
  189.  * the second, +1 if later, 0 if they are the same.  Similar to
  190.  * ymd_compare() only compares given date to date of an appt entry.
  191.  */
  192. ymd2_compare(day0, aday)
  193. struct tm *day0;
  194. struct appt_entry *aday;
  195. {
  196.         if (day0->tm_year > aday->year) return(1);
  197.         if (day0->tm_year < aday->year) return(-1);
  198.         if (day0->tm_mon > aday->month) return(1);
  199.         if (day0->tm_mon < aday->month) return(-1);
  200.         if (day0->tm_mday > aday->day) return(1);
  201.         if (day0->tm_mday < aday->day) return(-1);
  202.         return(0);
  203. }
  204.  
  205. int
  206. monthlength(month)
  207. int    month;
  208. {
  209.     static int    monthlengths[] = {31,28,31,30,31,30,31,31,30,31,30,31};
  210.  
  211.     if (month == FEB && (length_of_year(current.tm_year + 1900) == 366))
  212.         return(29);
  213.     else
  214.         return(monthlengths[month]);
  215. }
  216.  
  217. /*
  218.  *
  219.  * Append data from active timeslots to end of "tmp.appointments"
  220.  * file, then copy "tmp.appointments" to "appointments".  Note that
  221.  * when we opened the current day we filtered "appointments":
  222.  * all items that applied to the current day were displayed and
  223.  * stored in slots; all others were copied to "tmp.appointments".
  224.  * So by now "tmp.appointments" contains no entries for the
  225.  * current day.
  226.  * As an optimization, if nothing changed in the day then the
  227.  * original appointments file is left unchanged.
  228.  *
  229.  */
  230.  
  231. close_day()
  232. {
  233.         int i, j;
  234.         FILE *f;
  235.     struct stat sbuf;
  236.     struct appt_entry *aptr, *optr;
  237.  
  238.     if (read_only || !new_entry) {
  239.         new_entry = 0;
  240.         day_is_open = FALSE;
  241.         return(0);
  242.     }
  243.  
  244. #ifndef CALENCHECK
  245.     f = fopen(tmpapts_pathname, "a+");
  246.     if (f == NULL) {
  247.         err_rpt("can't open temp file for appending", NON_FATAL);
  248.         day_is_open = FALSE;
  249.         return(1);
  250.     }
  251.     
  252.     for (i=0; i<n_slots; i++) {
  253.                 if (slots[i].first != NULL) {
  254.             aptr = slots[i].first;
  255.             if (put_aentry(f, aptr))
  256.                 /* write error */
  257.                 break;
  258.             optr = aptr;
  259.             while (aptr = aptr->next) {
  260.                 free(optr);
  261.                 if (put_aentry(f, aptr))
  262.                     /* write error */
  263.                     break;
  264.                 optr = aptr;
  265.             }
  266.             free(optr);
  267.         }
  268.         }
  269.     if (ferror(f))
  270.         err_rpt("write on temp file failed", FATAL);
  271.         fclose(f);
  272.     new_entry = 0;
  273.     day_is_open = FALSE;
  274.     /* don't rename zero length files */
  275.     stat(tmpapts_pathname, &sbuf);
  276.     if (sbuf.st_size == (off_t) 0)
  277.         return(1);
  278.     xrename(tmpapts_pathname, apts_pathname);
  279. #endif  /* CALENCHECK */
  280.     return(0);
  281. }
  282.  
  283. /*
  284.  * get entry from appointments file
  285.  */
  286. get_aentry(apts_file, appt, noInclude, noUmkNotes, target)
  287. FILE *apts_file;
  288. struct appt_entry *appt;
  289. int noInclude;
  290. int noUmkNotes;
  291. int target;
  292. {
  293.     char *ptr, *str;
  294.     char *fgets(), *index();
  295.     char *incl_ptr, incl_buf[128], wday[3];
  296.     int i, lib, parse_options, nodata = 1;
  297.     int n, nflag, lrange, hrange;
  298.     struct stat sbuf;
  299.     static FILE *include[MAX_INCLUDE_NESTING];
  300.  
  301.     appt->flags = appt->repeat = appt->lookahead = 0;
  302.     appt->sindex = 0;
  303.     appt->runlength = 0;
  304.     appt->warn = 10;
  305.     appt->next = NULL;
  306.     /* If noInclude is set then don't follow include files, i.e.
  307.      * treat #include directives as comments. This is useful for
  308.      * copying the appts file.
  309.      */
  310.     while (nodata) {
  311.         if (include_level) {
  312.             if (fgets(inbuf, 512, include[include_level-1]) == NULL) {
  313.                 /* end of include file - get next entry
  314.                  * from previous level of nesting
  315.                  */
  316.                 unwind:
  317.                 fclose(include[include_level-1]);
  318.                 include_level--;
  319.             } else {
  320.                 /* don't modify stuff from include files */
  321.                 appt->flags |= READONLY;
  322.                 nodata = 0;    /* still data in file */
  323.             }
  324.         } else {
  325.             if (fgets(inbuf, 512, apts_file) == NULL)
  326.                 return(EOF);
  327.             else
  328.                 nodata = 0;    /* still data in file */
  329.         }
  330.     }
  331.     ptr = inbuf;
  332.     if (noInclude && *ptr == '#') {
  333.         appt->flags |= A_COMMENT;
  334.         return(0);
  335.     }
  336.     if (*ptr == '#') {
  337.         if (!include_level && (!strcmp(inbuf, OHEADER) ||
  338.             !strncmp(inbuf, HEADER, 18))) {
  339.             /* first line in base file read */
  340.             if (include_old && (First.tm_year <= today.tm_year)) {
  341.                 /* read in old include file (if it exists) */
  342.                 /* prepend directory info */
  343.                 sprintf(incl_buf, "%s.%02d",
  344.                     apts_pathname, First.tm_year);
  345.                 if (!stat(incl_buf, &sbuf)) {
  346.                     if ((include[include_level] = fopen(incl_buf, "r")) == NULL) {
  347.                         strcpy(errbuf, "can't open include file <");
  348.                         strcat(errbuf, incl_buf);
  349.                         strcat(errbuf, "> (ignored)");
  350.                         err_rpt(errbuf, NON_FATAL);
  351.                     } else
  352.                         include_level++;
  353.                 }
  354.             }
  355.             if (!strcmp(inbuf, OHEADER))
  356.                 /* substitute new header format */
  357.                 strcpy(inbuf, HEADER);
  358.         } else if (!strncmp(inbuf, HEADER, 18)) {
  359.             /* found a ver 2.2 header - get statistics */
  360.             n = sscanf(inbuf, "%*[^-]- nflag=%d range=%d,%d",
  361.                 &nflag, &lrange, &hrange);
  362.             if (n == 3 && include_level) {
  363.                 /* proper match found */
  364.                 if (noUmkNotes && nflag)
  365.                     goto unwind;
  366.                 if (target && (target < lrange || target > hrange))
  367.                     goto unwind;
  368.             }
  369.         } else if (!strncmp(inbuf, "#include", 8)) {
  370.             /* include file */
  371.             if (include_level > MAX_INCLUDE_NESTING) {
  372.                 err_rpt("include files nested too deep (ignored)", NON_FATAL);
  373.                 appt->flags |= A_COMMENT;
  374.                 return(0);
  375.             }
  376.             incl_ptr = strbuf;
  377.             if ((ptr = index(inbuf, '"')) == NULL)
  378.  
  379.                 if ((ptr = index(inbuf, '<')) == NULL) {
  380.                     err_rpt("missing '\"' or '<' in include file spec", NON_FATAL);
  381.                     appt->flags |= A_COMMENT;
  382.                     return(0);
  383.                 } else {
  384.                     lib = 1;
  385.                 }
  386.             else
  387.                 lib = 0;
  388.             ptr++;
  389.             while (*ptr && *ptr != '"' && *ptr != '>')
  390.                 *incl_ptr++ = *ptr++;
  391.             if (! *ptr) {
  392.                 err_rpt("missing '\"' or '>' in include file spec", NON_FATAL);
  393.                 appt->flags |= A_COMMENT;
  394.                 return(0);
  395.             }
  396.             *incl_ptr = '\0';
  397.             if (strbuf[0] == '/')
  398.                 /* full pathname provided */
  399.                 strcpy(incl_buf, strbuf);
  400.             else
  401.                 /* prepend directory info */
  402.                 if (lib)
  403.                     sprintf(incl_buf, "%s/%s", lib_dir, strbuf);
  404.                 else
  405.                     sprintf(incl_buf, "%s/%s", apts_dir, strbuf);
  406.             if ((include[include_level] = fopen(incl_buf, "r")) == NULL) {
  407.                 strcpy(errbuf, "can't open include file <");
  408.                 strcat(errbuf, incl_buf);
  409.                 strcat(errbuf, "> (ignored)");
  410.                 err_rpt(errbuf, NON_FATAL);
  411.             } else 
  412.                 include_level++;
  413.         }
  414.         appt->flags |= A_COMMENT;
  415.         return(0);
  416.     }
  417.     while (isspace(*ptr))
  418.         ++ptr;
  419.     if (!*ptr) {
  420.         /* empty line */
  421.         appt->flags |= A_COMMENT;
  422.         return(0);
  423.     }
  424.     if (*ptr == '*') {
  425.         appt->flags |= ALL_YEARS;
  426.         appt->year = START_YEAR;
  427.         ++ptr;    /* point to second '*' */
  428.         ++ptr;    /* point to space */
  429.     } else {
  430.         appt->year = 0;
  431.         while (isdigit(*ptr)) {
  432.             appt->year *= 10;
  433.             appt->year += *ptr++ - '0';
  434.         }
  435.         /* sanity check */
  436.         if (appt->year < 0) {
  437.             sprintf(errbuf, "illegal year value [%d] (ignored)", appt->year);
  438.             err_rpt(errbuf, NON_FATAL);
  439.             return(1);
  440.         }
  441.     }
  442.     while (isspace(*ptr))
  443.         ++ptr;
  444.     if (*ptr == '*') {
  445.         appt->flags |= ALL_MONTHS;
  446.         appt->month = 0;
  447.         ++ptr;
  448.     } else {
  449.         appt->month = (*ptr - '0') * 10;
  450.         appt->month += *++ptr - '0';
  451.         if (one_based) (appt->month)--;
  452.         /* sanity check */
  453.         if (appt->month < JAN || appt->month > DEC) {
  454.             sprintf(errbuf, "illegal month value [%d] (ignored)", appt->month);
  455.             err_rpt(errbuf, NON_FATAL);
  456.             return(1);
  457.         }
  458.     }
  459.     ++ptr;
  460.     while (isspace(*ptr))
  461.         ++ptr;
  462.     if (*ptr == '*') {
  463.         appt->flags |= ALL_DAYS;
  464.         appt->day = 1;
  465.         appt->repeat = 1;
  466.         ++ptr;
  467.     } else if (isdigit(*ptr)) {
  468.         appt->day = (*ptr - '0') * 10;
  469.         appt->day += *++ptr - '0';
  470.         if (!one_based) (appt->day)++;
  471.         /* sanity check */
  472.         if (appt->day < 1 || appt->day > 31) {
  473.             sprintf(errbuf, "illegal day value [%d] (ignored)", appt->day);
  474.             err_rpt(errbuf, NON_FATAL);
  475.             return(1);
  476.         }
  477.     } else {
  478.         /* check for day names */
  479.         wday[0] = islower(*ptr) ? toupper(*ptr) : *ptr;
  480.         ++ptr;
  481.         wday[1] = islower(*ptr) ? toupper(*ptr) : *ptr;
  482.         wday[2] = '\0';
  483.         i = 0;
  484.         if (!strcmp(wday, dayname[i++]))
  485.             appt->flags |= EVERY_SUN;
  486.         else if (!strcmp(wday, dayname[i++]))
  487.             appt->flags |= EVERY_MON;
  488.         else if (!strcmp(wday, dayname[i++]))
  489.             appt->flags |= EVERY_TUE;
  490.         else if (!strcmp(wday, dayname[i++]))
  491.             appt->flags |= EVERY_WED;
  492.         else if (!strcmp(wday, dayname[i++]))
  493.             appt->flags |= EVERY_THU;
  494.         else if (!strcmp(wday, dayname[i++]))
  495.             appt->flags |= EVERY_FRI;
  496.         else if (!strcmp(wday, dayname[i++]))
  497.             appt->flags |= EVERY_SAT;
  498.         else if (!strcmp(wday, dayname[i]))
  499.             appt->flags |= EVERY_MON_FRI;
  500.         else {
  501.             /* sanity check */
  502.             sprintf(errbuf, "illegal day name [%s] (ignored)", wday);
  503.             err_rpt(errbuf, NON_FATAL);
  504.             return(1);
  505.         }
  506.         appt->day = 1;
  507.         if (appt->flags & EVERY_MON_FRI) {
  508.             appt->repeat = 1;
  509.         } else {
  510.             appt->flags |= REPEAT;
  511.             appt->repeat = ALL_WEEKS;    /* default to every week */
  512.         }
  513.     }
  514.     ++ptr;
  515.     while (isspace(*ptr))
  516.         ++ptr;
  517.     appt->hour = (*ptr - '0') * 10;
  518.     appt->hour += *++ptr - '0';
  519.     /* sanity check */
  520.     if (appt->hour < 0 || (appt->hour > 23 && appt->hour != 99)) {
  521.         sprintf(errbuf, "illegal hour value [%d] (ignored)", appt->hour);
  522.         err_rpt(errbuf, NON_FATAL);
  523.         return(1);
  524.     }
  525.     if ((version2 && appt->hour == 99) || (!version2 && appt->hour == 0))
  526.         appt->flags |= A_NOTE;
  527.     ++ptr;
  528.     while (isspace(*ptr))
  529.         ++ptr;
  530.     appt->minute = (*ptr - '0') * 10;
  531.     appt->minute += *++ptr - '0';
  532.     /* sanity check */
  533.     if (appt->minute < 0 || (appt->minute > 59 && appt->minute != 99)) {
  534.         /* minutes currently can only be 00 or 30
  535.          * unless it's a note.
  536.          */
  537.         sprintf(errbuf, "illegal minute value [%d] (ignored)", appt->minute);
  538.         err_rpt(errbuf, NON_FATAL);
  539.         return(1);
  540.     }
  541.     if ((appt->flags & A_NOTE) && version2 && appt->minute == 99)
  542.         appt->flags |= MARKED;  /* don't show in mon/yr display */
  543.     ++ptr;
  544.     while (isspace(*ptr))
  545.         ++ptr;
  546.     appt->arrows = (*ptr - '0') * 10;
  547.     appt->arrows += *++ptr - '0';
  548.     /* sanity check */
  549.     if (appt->arrows < 0 || appt->arrows > 48) {
  550.         sprintf(errbuf, "illegal arrow value [%d] (ignored)", appt->arrows);
  551.         err_rpt(errbuf, NON_FATAL);
  552.         return(1);
  553.     }
  554.     ++ptr;
  555.     while (isspace(*ptr))
  556.         ++ptr;
  557.     /* lookahead and repeat entries are free format, i.e. they */
  558.     /* can occur in any order */
  559.     parse_options = TRUE;
  560.     while (parse_options) {
  561.         switch (*ptr) {
  562.             case '\\':
  563.                 /* start of string text */
  564.                 parse_options = FALSE;
  565.                 ++ptr;
  566.                 break;
  567.  
  568.             case '%':
  569.                 /* advance warning time (minutes) */
  570.                 appt->warn = 0;
  571.                 while (isdigit(*++ptr))
  572.                     appt->warn = appt->warn * 10 + (int)(*ptr - '0');
  573.                 if (appt->warn < 0) {
  574.                     err_rpt("illegal advance warning (ignored)", NON_FATAL);
  575.                     appt->warn = 10;
  576.                 }
  577.                 break;
  578.  
  579.             case '[':
  580.                 /* repeating appointment */
  581.                 appt->flags |= REPEAT;
  582.                 if (appt->flags & EVERY_SOMEDAY) {
  583.                     if ((appt->repeat = do_wk_repeat(&ptr)) < 0)
  584.                         return(1);
  585.                 } else {
  586.                     if ((appt->repeat = do_repeat(&ptr)) < 0)
  587.                         return(1);
  588.                 }
  589.                 break;
  590.             
  591.             case '<':
  592.                 /* remind us ahead of time */
  593.                 appt->flags |= LOOKAHEAD;
  594.                 if ((appt->lookahead = do_lookahead(&ptr)) < 0)
  595.                     return(1);
  596.                 break;
  597.             
  598.             case '+':
  599.                 /* this appointment lasts for n days */
  600.                 appt->flags |= RUN;
  601.                 while (isdigit(*++ptr))
  602.                     appt->runlength = appt->runlength * 10 + (int)(*ptr - '0');
  603.                 if (appt->runlength < 0)
  604.                     return(1);
  605.                 if (!(appt->flags & REPEAT)) {
  606.                     /* default to run of days */
  607.                     appt->flags |= REPEAT;
  608.                     appt->repeat = 1;
  609.                 }
  610.                 break;
  611.  
  612.             case '#':
  613.                 /* deleted appointment */
  614.                 appt->flags |= DELETED;
  615.                 ++ptr;
  616.                 break;
  617.             
  618.             default:
  619.                 parse_options = FALSE;
  620.                 break;
  621.         }
  622.         while (isspace(*ptr))
  623.             ++ptr;
  624.     }
  625.     str = strbuf;
  626.     while (*ptr && *ptr != '\n')
  627.         *str++ = *ptr++;
  628.     *str = '\0';
  629.     strcpy(appt->str, strbuf);
  630.     if (appt->flags & DELETED)
  631.         /* ignore some flags */
  632.         appt->flags &= ~(RUN | REPEAT);
  633.  
  634.     return(0);
  635. }
  636.  
  637. /* parse normal repeated entry field */
  638. do_repeat(ptr)
  639. char **ptr;
  640. {
  641.     int repeat = 0;
  642.  
  643.     while (isdigit(*++*ptr))
  644.         repeat = repeat * 10 + (int)(**ptr - '0');
  645.     if (**ptr != ']') {
  646.         err_rpt("bad entry (ignored)", NON_FATAL);
  647.         return(-1);
  648.     }
  649.     /* sanity check */
  650.     if (repeat < 0) {
  651.         err_rpt("illegal repeat interval (ignored)", NON_FATAL);
  652.         return(-1);
  653.     }
  654.     ++*ptr;
  655.     return(repeat);
  656. }
  657.  
  658. /* parse weekly repeated entry field */
  659. do_wk_repeat(ptr)
  660. char **ptr;
  661. {
  662.     int repeat = 0;
  663.  
  664.     while (*++*ptr != ']') {
  665.         if (**ptr == ',')
  666.             continue;    /* get next week */
  667.         if (isdigit(**ptr)) {
  668.             repeat |= 0x1<<(**ptr - '1');
  669.         } else if (**ptr == 'L' || **ptr == 'l') {
  670.             /* last week in month */
  671.             repeat |= LAST_WEEK;
  672.         } else {
  673.             /* format error */
  674.             err_rpt("illegal repeat specification (ignored)", NON_FATAL);
  675.             return(-1);
  676.         }
  677.     }
  678.     /* sanity check */
  679.     if ((unsigned int)repeat > WEEK_LIMIT) {
  680.         err_rpt("illegal weekly repeat (ignored)", NON_FATAL);
  681.         return(-1);
  682.     }
  683.     ++*ptr;
  684.     return(repeat);
  685. }
  686.  
  687. /* parse lookahead entry field */
  688. do_lookahead(ptr)
  689. char **ptr;
  690. {
  691.     int lookahead = 0;
  692.  
  693.     while (isdigit(*++*ptr))
  694.         lookahead = lookahead * 10 + (int)(**ptr - '0');
  695.     if (**ptr != '>') {
  696.         err_rpt("bad entry (ignored)", NON_FATAL);
  697.         return(-1);
  698.     }
  699.     /* sanity check */
  700.     if (lookahead < 0) {
  701.         err_rpt("illegal lookahead interval (ignored)", NON_FATAL);
  702.         return(-1);
  703.     }
  704.     ++*ptr;
  705.     return(lookahead);
  706. }
  707.  
  708. #ifndef CALENCHECK
  709. /*
  710.  * put entry into appointments file
  711.  */
  712. put_aentry(apts_file, appt)
  713. FILE *apts_file;
  714. struct appt_entry *appt;
  715. {
  716.     char *to_str();
  717.  
  718.     if (read_only)
  719.         return(0);
  720.  
  721.     if (appt->flags & READONLY)
  722.         /* don't copy include file entries */
  723.         /* (the include directive is copied as a comment) */
  724.         return(0);
  725.     if (appt->flags & A_COMMENT) {
  726.         fputs(inbuf, apts_file);
  727.         return(ferror(apts_file));
  728.     }
  729.     if (appt->flags & ALL_YEARS)
  730.         fputs("** ", apts_file);
  731.     else if (appt->year > 99)
  732.         fprintf(apts_file, "%03d ", appt->year);
  733.     else
  734.         fprintf(apts_file, "%02d ", appt->year);
  735.     if (appt->flags & ALL_MONTHS)
  736.         fputs("** ", apts_file);
  737.     else
  738.         fprintf(apts_file, "%02d ", one_based ? appt->month+1 : appt->month);
  739.     if (appt->flags & ALL_DAYS)
  740.         fputs("** ", apts_file);
  741.     else if (appt->flags & EVERY_SOMEDAY) {
  742.         switch (appt->flags & EVERY_SOMEDAY) {
  743.             case EVERY_SUN:
  744.                 fputs("Su ", apts_file);
  745.                 break;
  746.             case EVERY_MON:
  747.                 fputs("Mo ", apts_file);
  748.                 break;
  749.             case EVERY_TUE:
  750.                 fputs("Tu ", apts_file);
  751.                 break;
  752.             case EVERY_WED:
  753.                 fputs("We ", apts_file);
  754.                 break;
  755.             case EVERY_THU:
  756.                 fputs("Th ", apts_file);
  757.                 break;
  758.             case EVERY_FRI:
  759.                 fputs("Fr ", apts_file);
  760.                 break;
  761.             case EVERY_SAT:
  762.                 fputs("Sa ", apts_file);
  763.                 break;
  764.         }
  765.     } else if (appt->flags & EVERY_MON_FRI) {
  766.         fputs("MF ", apts_file);
  767.     } else
  768.         fprintf(apts_file, "%02d ", one_based ? appt->day : appt->day-1);
  769.     if (appt->flags & A_NOTE) {
  770.         appt->hour = 99;
  771.         appt->minute = 0;    /* assume unmarked note */
  772.     }
  773.     if ((appt->flags & MARKED_NOTE) == MARKED_NOTE)
  774.         appt->minute = 99;
  775.     if (!(appt->flags & (ALL_DAYS|DELETED|EVERY_MON_FRI)) && appt->flags & REPEAT) {
  776.         if (appt->flags & EVERY_SOMEDAY)
  777.             fprintf(apts_file, "%02d %02d %02d %s ", appt->hour, appt->minute, appt->arrows, to_str(appt->repeat));
  778.         else
  779.             fprintf(apts_file, "%02d %02d %02d [%d] ", appt->hour, appt->minute, appt->arrows, appt->repeat);
  780.     } else
  781.         fprintf(apts_file, "%02d %02d %02d ", appt->hour, appt->minute, appt->arrows);
  782.  
  783.     if (appt->flags & LOOKAHEAD)
  784.         fprintf(apts_file, "<%d> ", appt->lookahead);
  785.     if (appt->flags & RUN)
  786.         fprintf(apts_file, "+%d ", appt->runlength);
  787.     if (appt->warn != 10)
  788.         fprintf(apts_file, "%%%d ", appt->warn);
  789.     if (appt->flags & DELETED)
  790.         fputs("# ", apts_file);
  791.     if (isalnum(*(appt->str)))
  792.         fprintf(apts_file, "%s\n", appt->str);
  793.     else
  794.         fprintf(apts_file, "\\%s\n", appt->str);
  795.     
  796.     /* check for failure (e.g. file system full) */
  797.     return(ferror(apts_file));
  798. }
  799.  
  800. char rptstr[10];
  801.  
  802. /* convert repeat bit map to printable string */
  803. char *
  804. to_str(repeat)
  805. int repeat;
  806. {
  807.     int i, j = 0;
  808.  
  809.     if (repeat == ALL_WEEKS)
  810.         /* if it's every week, then don't write [] spec */
  811.         rptstr[0] = '\0';
  812.     else {
  813.         rptstr[j++] = '[';
  814.         for (i=0; i<5; i++) {
  815.             if (repeat & (0x1<<i)) {
  816.                 rptstr[j++] = i+1 + '0';
  817.                 rptstr[j++] = ',';
  818.             }
  819.         }
  820.         if (repeat & LAST_WEEK) {
  821.             rptstr[j++] = 'L';
  822.             rptstr[j++] = ',';
  823.         }
  824.         rptstr[j] = '\0';
  825.         rptstr[--j] = ']';
  826.     }
  827.     return (rptstr);
  828. }
  829.  
  830. /*
  831.  * Convert from version 1 appts files to version 2 file format.
  832.  */
  833. ver1to2()
  834. {
  835.     FILE *oappts, *nappts, *fp;
  836.     struct appt_entry appt;
  837.     int err_flag, save_base;
  838.     char save_file[128];
  839.     struct stat stbuf;
  840.  
  841.     /*
  842.      * The main difference is that the ver 2 files are one-based, i.e
  843.      * days and months start with 1, rather than 0. Another difference
  844.      * is that a hour entry of 99 is used to flag a memo entry, rather
  845.      * than 00. This allows for 24-hour appointments.
  846.      * Version 2 appts files are marked with a special header line
  847.      * defined by the HEADER string (in ct.h).  Version 2 files
  848.      * also support a "lookahead" reminder service to remind one
  849.      * in advance of a future appointment.
  850.      * If "save_old" is set, then any appointments for years prior
  851.      * to this one are saved in a special file of the form
  852.      * ".appointments.YY", where YY is the year.
  853.      */
  854.  
  855.     if (read_only != 0) {
  856.         err_rpt("appts file is read-only, no conversion done", NON_FATAL);
  857.         return;
  858.     }
  859.  
  860.      /* open files, etc */
  861.     if ((oappts = fopen(apts_pathname, "r")) == NULL) {
  862.         err_rpt("can't open appts file for reading", FATAL);
  863.         /* NOT REACHED */
  864.      }
  865.     if ((nappts = fopen(tmpapts_pathname, "w")) == NULL) {
  866.         err_rpt("can't open temp file for writing", NON_FATAL);
  867.         return;
  868.      }
  869.      /* write new header line */
  870.      fputs(HEADER, nappts);
  871.  
  872.      /* copy existing entries to the new file */
  873.      save_base = one_based;
  874.      while ((err_flag = get_aentry(oappts, &appt, TRUE, 0, 0)) != EOF) {
  875.         if (err_flag)
  876.             continue;    /* ignore badly formatted input */
  877.         if (appt.hour == 0)
  878.             appt.flags |= A_NOTE;
  879.         one_based = 1;        /* force new format output */
  880.         if (save_old && !(appt.flags & ALL_YEARS) && (appt.year < today.tm_year)
  881.            && !((appt.flags & REPEAT) && !(appt.flags & EVERY_SOMEDAY))
  882.            && !(appt.flags & A_COMMENT)) {
  883.             /* prepend directory info */
  884.             sprintf(save_file, "%s.%02d",
  885.                 apts_pathname, appt.year);
  886.             if (stat(save_file, &stbuf) && errno == ENOENT) {
  887.                 /* new file*/
  888.                 if ((fp = fopen(save_file, "w")) == NULL)
  889.                     err_rpt("can't open save file, bailing out", FATAL);
  890.                 fputs(HEADER, fp);
  891.                 fclose(fp);
  892.             }
  893.             if ((fp = fopen(save_file, "a+")) == NULL)
  894.                 err_rpt("can't open save file, bailing out", FATAL);
  895.             else {
  896.                 if (put_aentry(fp, &appt))
  897.                     err_rpt("write to save appt file failed, bailing out", FATAL);
  898.                 fclose(fp);
  899.             }
  900.         } else {
  901.             if (appt.flags & A_COMMENT)
  902.                 fputs(inbuf, nappts);
  903.             else
  904.                 if (put_aentry(nappts, &appt))
  905.                     err_rpt("write to new appt file failed, bailing out", FATAL);
  906.         }
  907.         one_based = save_base;    /* (maybe) force old format input */
  908.     }
  909.     fclose(oappts);
  910.     fclose(nappts);
  911.     xrename(tmpapts_pathname, apts_pathname);
  912.     one_based = 1;
  913.     version2 = 1;
  914. }
  915.  
  916. /*
  917.  * parse the date on the given string and reset the "current"
  918.  * date to reflect that date. The date may take the form of a
  919.  * day name (e.g. Tu, Tue, Tuesday) or a date in m/d/y format
  920.  * where the month and/or year may be missing (e.g. 27 = 27th
  921.  * of this month, 8/27 = August 27 of this year, 8/27/89 =
  922.  * August 27 of 1989. If 'cmdline' is true, then the string
  923.  * came from the command line '-d' option.
  924.  * If the first character of the date is + or - scan the number and
  925.  * use it as an offset in days from the current date.  Thus -1 becomes
  926.  * yesterday and +1 becomes tomorrow. pbm.
  927.  */
  928. int
  929. parse_date(str, cmdline)
  930. char *str;
  931. int cmdline;
  932. {
  933.     char c[4];
  934.     int i, dow = -1, m = -1, d = -1, y = -1;
  935.  
  936.     if (isdigit(*str)) {
  937.         /* must be a m/d/y date */
  938.         /* assume it's a month first */
  939.         m = *str++ - '0';
  940.         if (isdigit(*str))
  941.             m = m*10 + *str++ - '0';
  942.         if (!*str) {
  943.             /* no more chars => day only */
  944.             d = m;
  945.             m = -1;
  946.         } else if (*str++ != '/') {
  947.             if (cmdline)
  948.                 err_rpt("badly formed date for -d option (ignored)", NON_FATAL);
  949.             else
  950.                 err_rpt("badly formed date - please reenter", NON_FATAL);
  951.             return(1);
  952.         } else {
  953.             d = *str++ - '0';
  954.             if (isdigit(*str))
  955.                 d = d*10 + *str++ - '0';
  956.             if (*str++ == '/') {
  957.                 /* year also specified */
  958.                 y = *str++ - '0';
  959.                 if (isdigit(*str)) {
  960.                     y = y*10 + *str++ - '0';
  961.                     if (*str && isdigit(*str))
  962.                         y = y*10 + *str++ - '0';
  963.                     if (*str && isdigit(*str))
  964.                         y = y*10 + *str++ - '0';
  965.                 }
  966.             }
  967.         }
  968.         if (y > 0) {
  969.             if (y > 1900)
  970.                 y -= 1900;
  971.             current.tm_year = y;
  972.         }
  973.         if (day_first) {
  974.             if (m > 0) {
  975.                 current.tm_mon = d - 1;
  976.                 current.tm_mday = m;
  977.             } else if (d > 0)
  978.                 current.tm_mday = d;
  979.         } else {
  980.             if (m > 0) {
  981.                 current.tm_mon = m - 1;
  982.                 current.tm_mday = d;
  983.             } else if (d > 0)
  984.                 current.tm_mday = d;
  985.         }
  986.         fix_current_day();
  987.     } else if (*str == '-' || *str == '+') {
  988.         /*
  989.          * If the argument begins with a + or - assume that it is an
  990.          * offset in days from the current date. Use current date if the
  991.          * number doesn't scan after the - or +. pbm
  992.          */
  993.         if (sscanf(str, "%d", &i) == 1) {
  994.             current.tm_mday += i;
  995.             fix_current_day();
  996.         }
  997.     } else {
  998.         /* day of week */
  999.         /* check for day names */
  1000.         c[0] = islower(*str) ? toupper(*str) : *str;
  1001.         ++str;
  1002.         c[1] = islower(*str) ? toupper(*str) : *str;
  1003.         c[2] = '\0';
  1004.         for (i=0; i<7; i++) {
  1005.             if (!strcmp(c, dayname[i])) {
  1006.                 dow = i;
  1007.                 break;
  1008.             }
  1009.         }
  1010.         if (dow >= 0) {
  1011.             /* match found */
  1012.             current.tm_mday += dow - current.tm_wday;
  1013.             fix_current_day();
  1014.         } else if (!strncmp(c, "TOM", 3)) {
  1015.             /* tommorrow */
  1016.             current.tm_mday++;
  1017.             fix_current_day();
  1018.         } else if (!strncmp(c, "YES", 3)) {
  1019.             /* yesterday */
  1020.             current.tm_mday--;
  1021.             fix_current_day();
  1022.         } else {
  1023.             if (cmdline)
  1024.                 err_rpt("badly formed date for -d option (ignored)", NON_FATAL);
  1025.             else
  1026.                 err_rpt("badly formed date - please reenter", NON_FATAL);
  1027.             return(1);
  1028.         }
  1029.     }
  1030.     return(0);
  1031. }
  1032. #endif  /* CALENCHECK */
  1033.  
  1034. /* set error logging flag */
  1035. err2console(state)
  1036. int state;
  1037. {
  1038.     /*
  1039.      * if TRUE, forces error messages to the console, even
  1040.      * if the base frame is running
  1041.      */
  1042.     log_to_console = state;
  1043. }
  1044.  
  1045. /*
  1046.  * Error reporting. Try first to put message in a popup frame, then
  1047.  * the console, then stderr as a last resort.
  1048.  */
  1049. err_rpt(errstr, fatal_flag)
  1050. char *errstr;
  1051. int fatal_flag;
  1052. {
  1053.     FILE    *f;
  1054.     int    closed;
  1055.     char *getenv();
  1056.  
  1057. #ifndef NOTOOL
  1058.     closed = (int) window_get(frame, FRAME_CLOSED);
  1059.     if (frame && !log_to_console && !closed) {
  1060.         /* base frame exists */
  1061.         create_prompt_frame(errstr, FALSE);
  1062.         (void) window_loop(prompt_frame);
  1063.         window_set(prompt_frame, WIN_SHOW, FALSE, 0);
  1064.     } else if ((f=fopen("/dev/console", "w")) != NULL) {
  1065. #else
  1066.     if (getenv("WINDOW_PARENT") != NULL && (f=fopen("/dev/console", "w")) != NULL) {
  1067. #endif
  1068.         fprintf(f, "%s: %s\n", progname, errstr);
  1069.         fclose(f);
  1070.     } else
  1071.         fprintf(stderr, "%s: %s\n", progname, errstr);
  1072.     if (fatal_flag)
  1073.         exit(1);
  1074. }
  1075.  
  1076. #ifndef CALENCHECK
  1077. /* Clean-up */
  1078. cleanup()
  1079. {
  1080.     if (day_is_open)
  1081.         close_day();
  1082.  
  1083.     /* create outdated include files (if necessary) */
  1084.     if (save_old || expire_days)
  1085.         expire(expire_days);
  1086.             
  1087.     /* delete tmp file */
  1088.     if (access(tmpapts_pathname, R_OK) == 0 && unlink(tmpapts_pathname) < 0)
  1089.         perror(tmpapts_pathname);
  1090. }
  1091.  
  1092. char sysbuf[512];
  1093.  
  1094. /* Rename files, copying if necessary */
  1095. xrename(from, to)
  1096. char *from, *to;
  1097. {
  1098.     if (rename(from, to) == -1) {
  1099.         /* rename sys call fialed, try doing a copy */
  1100.         sprintf(sysbuf, "cp %s %s", from, to);
  1101.         if (system(sysbuf) != 0)
  1102.             err_rpt("couldn't rename/copy tmp file", NON_FATAL);
  1103.     }
  1104. }
  1105.  
  1106. /*
  1107.  *  Conventional and ISO standard week numbers (week starts with monday)
  1108.  *  Assume if moday_first is set TRUE then ISO format is desired.
  1109.  */
  1110.  
  1111. week_number()
  1112. {
  1113.     int week_n, week_d, W, r, day, year, leap;
  1114.  
  1115.     /*  First day of this week */
  1116.     if (monday_first)
  1117.         week_d = (current.tm_wday?(current.tm_wday-1):6);
  1118.     else
  1119.         week_d = current.tm_wday;
  1120.     current.tm_mday -= week_d;
  1121.     fix_current_day();
  1122.     day = current.tm_yday + 1;
  1123.     week_n = day / 7 + 1;       /*  "Raw" week number  */ 
  1124.     r = day % 7 - 1;           /*  No of days in the first week this year  */
  1125.  
  1126.     if (!monday_first) {
  1127.         if (week_n == 53)
  1128.             week_n = 1;
  1129.         else if (r > 0 && day > r)
  1130.             if (++week_n == 53 && (day + 6) > length_of_year(current.tm_year+1900))
  1131.                 week_n = 1;
  1132.     } else {
  1133.         if (r >= 4) week_n++;       /*  First week has more than three days  */
  1134.         if (week_n == 53) {         /*  Week number of last week of year  */
  1135.             year = current.tm_year + 1900;
  1136.             leap = (year % 4 == 0 && year % 100 != 0 || year % 400 == 0);   
  1137.             day += 7 - ( leap ? 366 : 365 );
  1138.             W = day / 7 + 1;
  1139.             r = day % 7 - 1;
  1140.             if (r >= 4) W++;
  1141.             if (W == 2) week_n = 1;
  1142.             }
  1143.     }
  1144.     current.tm_mday += week_d;
  1145.     fix_current_day();
  1146.     return week_n;
  1147. }
  1148. #endif  /* CALENCHECK */
  1149.