1 /*
2 		 Copyright (c) 1991, 1992, 1995 Simon Marshall
3 
4 		   If you still end up late, don't blame me!
5 
6   Permission to use, copy, modify, distribute, and sell this software and its
7        documentation for any purpose and without fee is hereby granted,
8     provided that the above copyright notice appear in all copies and that
9 	both that copyright notice and this permission notice appear in
10 			   supporting documentation.
11 
12   This software is provided AS IS with no warranties of any kind.  The author
13     shall have no liability with respect to the infringement of copyrights,
14      trade secrets or any patents by this file or any part thereof.  In no
15       event will the author be liable for any lost revenue or profits or
16 	      other special, indirect and consequential damages.
17 */
18 
19 /*
20  * Parse dates.
21  */
22 
23 
24 
25 #include "xalarm.h"
26 #include "dates.h"
27 
28 
29 Boolean		AppointmentWithin();
30 unsigned long	DateToMilliSeconds();
31 int		DaysTo();
32 static int	DaysBetween(), DaysBeforeDate();
33 static Boolean	ParseDateString();
34 extern Boolean	ParseTimeString(), IsInteger();
35 extern String	NextWord();
36 extern long	TimeToMilliSeconds();
37 extern time_t	time();
38 extern struct tm *localtime();
39 
40 
41 
42 extern AlarmData xalarm;
43 
44 
45 
46 /*
47  * Is the number of days before the date within the number of days given.
48  */
49 
AppointmentWithin(withindays,line,timestr,datestr,chpos)50 Boolean AppointmentWithin (withindays, line, timestr, datestr, chpos)
51   String   line, *timestr, *datestr;
52   int 	   withindays, *chpos;
53 {
54     String 	    date, word;
55     Boolean 	    dummy;
56     unsigned long   dateout;
57     long 	    timeout;
58     int 	    days, dayofweek, dayofmonth, month, year, hrs, mins;
59 
60     if ((*line == '\n') or (*line == '#') or (*line == '!') or (*line == '\0'))
61 	return (False);
62 
63     /*
64      * Get the date.
65      */
66     date = XtMalloc (TEXT);
67     date[0] = '\0';
68     while (strcmp (word = NextWord (line, chpos), "-"))
69 	if (*word == '\0')
70 	    break;
71 	else if (date[0] == '\0')
72 	    (void) strcpy (date, word);
73 	else
74 	    (void) sprintf (ENDOF (date), " %s", word);
75 
76     if (date[0] == '\0')
77 	*datestr = XtNewString ("today");
78     else
79 	*datestr = XtNewString (date);
80 
81     if (not ParseDateString (*datestr, &days, &dayofweek, &dayofmonth, &month, &year)) {
82 	(void) fprintf (stderr, "%s%s", xalarm.errormessage, line);
83 	return (False);
84     }
85 
86     /*
87      * Get the time.
88      * There might not be a time there, so we ignore any errors.
89      */
90     if (*word == '\0')
91 	*timestr = XtNewString ("+0");
92     else {
93 	*timestr = NextWord (line, chpos);
94 	if (not ParseTimeString (*timestr, &hrs, &mins, &dummy)) {
95 	    if (*timestr[0] != '\0')
96 		do
97 		    (*chpos)--;
98 		while (line[*chpos] != ' ');
99 	    *timestr = XtNewString ("+0");
100 	}
101     }
102 
103     dateout = DateToMilliSeconds (*datestr);
104     timeout = TimeToMilliSeconds (*timestr);
105 
106     if (ISVALID (dateout)) {
107 	/*
108 	 * We can't just see if the sum is positive 'cos dateout is unsigned...
109 	 */
110 	days = (int) (dateout / MSECSIN1DAY);
111 	return (((days > 0) and (days <= withindays)) or
112 		((days == 0) and (timeout >= 0)));
113     } else {
114 	/*
115 	 * We don't report errors if the alarm is just too far ahead or gone...
116 	 */
117 	(void) DateToMilliSeconds (*datestr);
118 	if (not (ERRORIS (TOOFARAHEAD) or ERRORIS (DATEPASSED) or ERRORIS (TIMEPASSED)))
119 	    (void) fprintf (stderr, "%s%s", xalarm.errormessage, line);
120 	return (False);
121     }
122 }
123 
124 
125 
126 /*
127  * Returns the number of milliseconds to the date given in datestr.
128  */
129 
DateToMilliSeconds(datestr)130 unsigned long DateToMilliSeconds (datestr)
131   String   datestr;
132 {
133     char   baddate[TEXT];
134     int    days, dayofweek, dayofmonth, month, year;
135 
136     if (*datestr == '\0')
137 	return ((unsigned long) 0);
138     else if (not ParseDateString (datestr, &days, &dayofweek, &dayofmonth, &month, &year))
139 	return ((unsigned long) INVALID);
140 
141     if (ISINVALID (days))
142 	days = DaysBeforeDate (dayofweek, dayofmonth, month, year);
143 
144     if (((days > 0) and (days <= MAXDAYS)) or
145 	((days == 0) and (ISINVALID (xalarm.timeout) or (xalarm.timeout >= 0))))
146 	return ((unsigned long) days * MSECSIN1DAY);
147     else {
148 	if ((days == 0) and (ISVALID (xalarm.timeout)) and (xalarm.timeout < 0)) {
149 	    ADDERROR (TIMEPASSED, (String) NULL);
150 	} else if (days < 0) {
151 	    (void) sprintf (baddate, "%d days", days);
152 	    ADDERROR (DATEPASSED, baddate);
153 	} else {
154 	    (void) sprintf (baddate, "+%d days", days);
155 	    ADDERROR (TOOFARAHEAD, baddate);
156 	}
157 	return ((unsigned long) INVALID);
158     }
159 }
160 
161 
162 
163 /*
164  * Parse that string.  I did this one myself!
165  * Works in whole days only.
166  */
167 
ParseDateString(str,days,dayofweek,dayofmonth,month,year)168 static Boolean ParseDateString (str, days, dayofweek, dayofmonth, month, year)
169   String   str;
170   int 	  *days, *dayofweek, *dayofmonth, *month, *year;
171 {
172     static int 	  daysin[] = {DAYSINMONTHS};
173     static char   strings[][4] = {WEEKDAYS, MONTHS, TODAY, DAILY, TOMORROW, WEEK, WEEKLY,
174 				  "!!!", "!!!"};
175     String 	  word, monthstr = (String) NULL;
176     time_t 	  now;
177     struct tm 	 *today;
178     int 	  thisyear, num, i, chpos = 0;
179 
180     RESETERROR ();
181 
182     *days = *dayofweek = *dayofmonth = *month = *year = -1;
183 
184     (void) time (&now);
185     today = localtime (&now);
186 
187     if ((*str == '+') and (IsInteger (str)))
188 	*days = atoi (str);
189     else
190 	while (strcmp (word = NextWord (str, &chpos), "")) {
191 	    i = 0;
192 	    while ((not (STREQUAL (word, strings[i]))) and (i < XtNumber (strings)))
193 		i++;
194 
195 	    if (i == 19)				/* TODAY. */
196 		*days = 0;
197 	    else if (i == 20)				/* DAILY. */
198 		*days = 1;
199 	    else if (i == 21)				/* TOMORROW. */
200 		if (*days < 0)
201 		    *days = 1;
202 		else
203 		    (*days)++;
204 	    else if ((i == 22) or (i == 23))		/* WEEK or WEEKLY. */
205 		if (*days < 0)
206 		    *days = 7;
207 		else
208 		    (*days) += 7;
209 	    else if (i < 7)				/* Weekday. */
210 		if (*dayofweek < 0)
211 		    *dayofweek = i;
212 		else {
213 		    ADDERROR (ANOTHERWEEKDAY, word);
214 		    return (False);
215 		}
216 	    else if (i < 19)				/* Month. */
217 		if (*month < 0) {
218 		    *month = i-7;
219 		    monthstr = word;
220 		} else {
221 		    ADDERROR (ANOTHERMONTH, word);
222 		    return (False);
223 		}
224 	    else if (((num = atoi (word)) <= 0) or (not IsInteger (word))) {
225 		    ADDERROR (UNRECOGNISED, word);
226 		    return (False);
227 		}
228 		/*
229 		 * Otherwise assume it's a year or a month day.
230 		 */
231 		else if (num > 90)
232 		    if (*year < 0)
233 			*year = num;
234 		    else {
235 			ADDERROR (ANOTHERYEAR, word);
236 			return (False);
237 		    }
238 		else
239 		    if (*dayofmonth < 0)
240 			*dayofmonth = num;
241 		    else {
242 			ADDERROR (ANOTHERMONTH, word);
243 			return (False);
244 		    }
245 	}
246 
247     /*
248      * Have we been given a day + day of week, like "week tues"?
249      */
250     if ((*days > 0) and (*dayofweek >= 0))
251 	(*days) += ((*dayofweek + 7) - today->tm_wday) % 7;
252 
253     /*
254      * Check that the day of month is OK for this month.
255      */
256     if ((*month >= 0) and (*dayofmonth > 0)) {
257 	if ((thisyear = *year) < 0)
258 	    thisyear = today->tm_year + 1900;
259 	daysin[1] = DAYSINFEB (thisyear);
260 	if (*dayofmonth > daysin[*month]) {
261 	    ADDERROR (WRONGDAYSMONTH, monthstr);
262 	    return (False);
263 	}
264     }
265 
266     return (True);
267 }
268 
269 
270 
271 /*
272  * Calculates the number of *whole* days before the date given by the args.
273  */
274 
DaysBeforeDate(dayofweek,dayofmonth,month,year)275 static int DaysBeforeDate (dayofweek, dayofmonth, month, year)
276   int 	   dayofweek, dayofmonth, month, year;
277 {
278     static int 	 daysin[] = {DAYSINMONTHS};
279     struct tm 	 today, *date;
280     time_t 	 now;
281 
282     (void) time (&now);
283     date = localtime (&now);
284     today.tm_yday = date->tm_yday;
285     today.tm_year = date->tm_year;
286 
287     if (year > 99)
288 	year = year - 1900;
289 
290     if (dayofweek < 0) {
291 	/*
292 	 * If no day of week is given, just use the day/month/year date.
293 	 * Cycle through the month/year.
294 	 */
295 	if (dayofmonth >= 0)		date->tm_mday = dayofmonth;
296 	if (month >= 0)			date->tm_mon = month;
297 	if (year >= 0)			date->tm_year = year;
298 	while ((year < 0) and (date->tm_year < (2500-1900)) and
299 	       (DaysBetween (&today, date) < 0))
300 	    if (month < 0) {
301 		date->tm_mon++;
302 		if (date->tm_mon > 11) {
303 		    date->tm_mon = 0;
304 		    date->tm_year++;
305 		}
306 	    }
307 	    else
308 		date->tm_year++;
309     } else
310 	/*
311 	 * We assume Buck Rodgers will have something better than xalarm...
312 	 * Cycle through dates until we find it.
313 	 */
314 	while ((date->tm_year < (2500-1900)) and
315 	       (((dayofweek >= 0) and	(dayofweek != date->tm_wday)) or
316 		((dayofmonth >= 0) and	(dayofmonth != date->tm_mday)) or
317 		((month >= 0) and	(month != date->tm_mon)) or
318 		((year >= 0) and	(year != date->tm_year)))) {
319 	    /*
320 	     * Just add one to the week day & month day...
321 	     */
322 	    date->tm_wday = (date->tm_wday + 1) % 7;
323 	    date->tm_mday++;
324 	    daysin[1] = DAYSINFEB (date->tm_year + 1900);
325 	    if (date->tm_mday > daysin[date->tm_mon]) {
326 		date->tm_mday = 1;
327 		date->tm_mon++;
328 		if (date->tm_mon > 11) {
329 		    date->tm_mon = 0;
330 		    date->tm_year++;
331 		}
332 	    }
333 	}
334 
335     return (DaysBetween (&today, date));
336 }
337 
338 
339 
340 /*
341  * Why oh why don't all systems have timelocal()?
342  */
343 
DaysBetween(today,date)344 static int DaysBetween (today, date)
345   struct tm  *today, *date;
346 {
347     static int 	 daysin[] = {DAYSINMONTHS};
348     int 	 i, dayofyear = date->tm_mday-1, daysbetween = 0;
349 
350     for (i=0; i<date->tm_mon; i++)
351 	if (i == 1)
352 	    dayofyear += DAYSINFEB (date->tm_year + 1900);
353 	else
354 	    dayofyear += daysin[i];
355 
356     if (date->tm_year == today->tm_year)
357 	return (dayofyear - today->tm_yday);
358     else {
359 	for (i = 1 + MIN (date->tm_year, today->tm_year);
360 	     i < MAX (date->tm_year, today->tm_year); i++)
361 	    daysbetween += DAYSINYEAR (i + 1900);
362 
363 	daysbetween += (date->tm_year > today->tm_year) ?
364 			dayofyear + (DAYSINYEAR (today->tm_year+1900) - today->tm_yday) :
365 			today->tm_yday + (DAYSINYEAR (date->tm_year+1900) - dayofyear);
366 
367 	return ((date->tm_year > today->tm_year) ? daysbetween : -daysbetween);
368     }
369 }
370 
371 
372 
373 /*
374  * Returns the number of days before the date given.  WEEK is special.
375  * WEEK means until the end of the current week.
376  */
377 
DaysTo(datestr,inst)378 int DaysTo (datestr, inst)
379   String     datestr;
380   Instance   inst;
381 {
382     time_t 	    abstime;
383     struct tm 	   *now;
384     unsigned long   millisecs;
385 
386     (void) time (&abstime);
387     now = localtime (&abstime);
388 
389     if (STREQUAL (datestr, WEEK))
390 	if (inst == Daemon)
391 	    return (DAEMONWEEKLY);
392 	else
393 	    return (7 - now->tm_wday);
394     else
395 	if (ISVALID (millisecs = DateToMilliSeconds (datestr)))
396 	    return ((int) (millisecs / MSECSIN1DAY));
397 	else
398 	    return (INVALID);
399 }
400