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