xref: /openbsd/usr.bin/calendar/day.c (revision 7b36286a)
1 /*	$OpenBSD: day.c,v 1.21 2008/04/13 00:22:17 djm Exp $	*/
2 
3 /*
4  * Copyright (c) 1989, 1993, 1994
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. Neither the name of the University nor the names of its contributors
16  *    may be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 
32 #ifndef lint
33 static const char copyright[] =
34 "@(#) Copyright (c) 1989, 1993\n\
35 	The Regents of the University of California.  All rights reserved.\n";
36 #endif /* not lint */
37 
38 #ifndef lint
39 #if 0
40 static const char sccsid[] = "@(#)calendar.c  8.3 (Berkeley) 3/25/94";
41 #else
42 static const char rcsid[] = "$OpenBSD: day.c,v 1.21 2008/04/13 00:22:17 djm Exp $";
43 #endif
44 #endif /* not lint */
45 
46 #include <sys/types.h>
47 #include <sys/uio.h>
48 
49 #include <ctype.h>
50 #include <err.h>
51 #include <locale.h>
52 #include <stdio.h>
53 #include <stdlib.h>
54 #include <string.h>
55 #include <time.h>
56 #include <tzfile.h>
57 
58 #include "pathnames.h"
59 #include "calendar.h"
60 
61 #define WEEKLY 1
62 #define MONTHLY 2
63 #define YEARLY 3
64 
65 struct tm *tp;
66 int *cumdays, offset;
67 char dayname[10];
68 enum calendars calendar;
69 u_long julian;
70 
71 
72 /* 1-based month, 0-based days, cumulative */
73 int daytab[][14] = {
74 	{ 0, -1, 30, 58, 89, 119, 150, 180, 211, 242, 272, 303, 333, 364 },
75 	{ 0, -1, 30, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
76 };
77 
78 static char *days[] = {
79 	"sun", "mon", "tue", "wed", "thu", "fri", "sat", NULL,
80 };
81 
82 static char *months[] = {
83 	"jan", "feb", "mar", "apr", "may", "jun",
84 	"jul", "aug", "sep", "oct", "nov", "dec", NULL,
85 };
86 
87 static struct fixs fndays[8];         /* full national days names */
88 static struct fixs ndays[8];          /* short national days names */
89 
90 static struct fixs fnmonths[13];      /* full national months names */
91 static struct fixs nmonths[13];       /* short national month names */
92 
93 
94 void
95 setnnames(void)
96 {
97 	char buf[80];
98 	int i, l;
99 	struct tm tm;
100 
101 	for (i = 0; i < 7; i++) {
102 		tm.tm_wday = i;
103 		l = strftime(buf, sizeof(buf), "%a", &tm);
104 		for (; l > 0 && isspace((int)buf[l - 1]); l--)
105 			;
106 		buf[l] = '\0';
107 		if (ndays[i].name != NULL)
108 			free(ndays[i].name);
109 		if ((ndays[i].name = strdup(buf)) == NULL)
110 			err(1, NULL);
111 		ndays[i].len = strlen(buf);
112 
113 		l = strftime(buf, sizeof(buf), "%A", &tm);
114 		for (; l > 0 && isspace((int)buf[l - 1]); l--)
115 			;
116 		buf[l] = '\0';
117 		if (fndays[i].name != NULL)
118 			free(fndays[i].name);
119 		if ((fndays[i].name = strdup(buf)) == NULL)
120 			err(1, NULL);
121 		fndays[i].len = strlen(buf);
122 	}
123 
124 	for (i = 0; i < 12; i++) {
125 		tm.tm_mon = i;
126 		l = strftime(buf, sizeof(buf), "%b", &tm);
127 		for (; l > 0 && isspace((int)buf[l - 1]); l--)
128 			;
129 		buf[l] = '\0';
130 		if (nmonths[i].name != NULL)
131 			free(nmonths[i].name);
132 		if ((nmonths[i].name = strdup(buf)) == NULL)
133 			err(1, NULL);
134 		nmonths[i].len = strlen(buf);
135 
136 		l = strftime(buf, sizeof(buf), "%B", &tm);
137 		for (; l > 0 && isspace((int)buf[l - 1]); l--)
138 			;
139 		buf[l] = '\0';
140 		if (fnmonths[i].name != NULL)
141 			free(fnmonths[i].name);
142 		if ((fnmonths[i].name = strdup(buf)) == NULL)
143 			err(1, NULL);
144 		fnmonths[i].len = strlen(buf);
145 	}
146 	/* Hardwired special events */
147 	spev[0].name = strdup(PESACH);
148 	spev[0].nlen = PESACHLEN;
149 	spev[0].getev = pesach;
150 	spev[1].name = strdup(EASTER);
151 	spev[1].nlen = EASTERNAMELEN;
152 	spev[1].getev = easter;
153 	spev[2].name = strdup(PASKHA);
154 	spev[2].nlen = PASKHALEN;
155 	spev[2].getev = paskha;
156 	for (i = 0; i < NUMEV; i++) {
157 		if (spev[i].name == NULL)
158 			err(1, NULL);
159 		spev[i].uname = NULL;
160 	}
161 }
162 
163 void
164 settime(time_t *now)
165 {
166 	tp = localtime(now);
167 	tp->tm_sec = 0;
168 	tp->tm_min = 0;
169 	/* Avoid getting caught by a timezone shift; set time to noon */
170 	tp->tm_isdst = 0;
171 	tp->tm_hour = 12;
172 	*now = mktime(tp);
173 	if (isleap(tp->tm_year + TM_YEAR_BASE))
174 		cumdays = daytab[1];
175 	else
176 		cumdays = daytab[0];
177 	/* Friday displays Monday's events */
178 	offset = tp->tm_wday == 5 ? 3 : 1;
179 	if (f_SetdayAfter)
180 		offset = 0;	/* Except not when range is set explicitly */
181 	header[5].iov_base = dayname;
182 
183 	(void) setlocale(LC_TIME, "C");
184 	header[5].iov_len = strftime(dayname, sizeof(dayname), "%A", tp);
185 	(void) setlocale(LC_TIME, "");
186 
187 	setnnames();
188 }
189 
190 /* convert [Year][Month]Day into unix time (since 1970)
191  * Year: two or four digits, Month: two digits, Day: two digits
192  */
193 time_t
194 Mktime(char *date)
195 {
196 	time_t t;
197 	int len;
198 	struct tm tm;
199 
200 	(void)time(&t);
201 	tp = localtime(&t);
202 
203 	len = strlen(date);
204 	if (len < 2)
205 		return((time_t)-1);
206 	tm.tm_sec = 0;
207 	tm.tm_min = 0;
208 	/* Avoid getting caught by a timezone shift; set time to noon */
209 	tm.tm_isdst = 0;
210 	tm.tm_hour = 12;
211 	tm.tm_wday = 0;
212 	tm.tm_mday = tp->tm_mday;
213 	tm.tm_mon = tp->tm_mon;
214 	tm.tm_year = tp->tm_year;
215 
216 	/* Day */
217 	tm.tm_mday = atoi(date + len - 2);
218 
219 	/* Month */
220 	if (len >= 4) {
221 		*(date + len - 2) = '\0';
222 		tm.tm_mon = atoi(date + len - 4) - 1;
223 	}
224 
225 	/* Year */
226 	if (len >= 6) {
227 		*(date + len - 4) = '\0';
228 		tm.tm_year = atoi(date);
229 
230 		/* tm_year up TM_YEAR_BASE ... */
231 		if (tm.tm_year < 69)		/* Y2K */
232 			tm.tm_year += 2000 - TM_YEAR_BASE;
233 		else if (tm.tm_year < 100)
234 			tm.tm_year += 1900 - TM_YEAR_BASE;
235 		else if (tm.tm_year > TM_YEAR_BASE)
236 			tm.tm_year -= TM_YEAR_BASE;
237 	}
238 
239 #if DEBUG
240 	printf("Mktime: %d %d %d %s\n", (int)mktime(&tm), (int)t, len,
241 	    asctime(&tm));
242 #endif
243 	return(mktime(&tm));
244 }
245 
246 void
247 adjust_calendar(int *day, int *month)
248 {
249 	switch (calendar) {
250 	case GREGORIAN:
251 		break;
252 
253 	case JULIAN:
254 		*day += julian;
255 		if (*day > (cumdays[*month + 1] - cumdays[*month])) {
256 			*day -= (cumdays[*month + 1] - cumdays[*month]);
257 			if (++*month > 12)
258 				*month = 1;
259 		}
260 		break;
261 	case LUNAR:
262 		break;
263 	}
264 }
265 
266 /*
267  * Possible date formats include any combination of:
268  *	3-charmonth			(January, Jan, Jan)
269  *	3-charweekday			(Friday, Monday, mon.)
270  *	numeric month or day		(1, 2, 04)
271  *
272  * Any character except \t or '*' may separate them, or they may not be
273  * separated.  Any line following a line that is matched, that starts
274  * with \t, is shown along with the matched line.
275  */
276 struct match *
277 isnow(char *endp, int bodun)
278 {
279 	int day = 0, flags = 0, month = 0, v1, v2, i;
280 	int monthp, dayp, varp = 0;
281 	struct match *matches = NULL, *tmp, *tmp2;
282 	int interval = YEARLY;	/* how frequently the event repeats. */
283 	int vwd = 0;	/* Variable weekday */
284 	time_t tdiff, ttmp;
285 	struct tm tmtmp;
286 
287 	/*
288 	 * CONVENTION
289 	 *
290 	 * Month:     1-12
291 	 * Monthname: Jan .. Dec
292 	 * Day:       1-31
293 	 * Weekday:   Mon-Sun
294 	 *
295 	 */
296 
297 	/* read first field */
298 	/* didn't recognize anything, skip it */
299 	if (!(v1 = getfield(endp, &endp, &flags)))
300 		return (NULL);
301 
302 	/* adjust bodun rate */
303 	if (bodun && !bodun_always)
304 		bodun = !arc4random_uniform(3);
305 
306 	/* Easter or Easter depending days */
307 	if (flags & F_SPECIAL)
308 		vwd = v1;
309 
310 	 /*
311 	  * 1. {Weekday,Day} XYZ ...
312 	  *
313 	  *    where Day is > 12
314 	  */
315 	else if (flags & F_ISDAY || v1 > 12) {
316 
317 		/* found a day; day: 13-31 or weekday: 1-7 */
318 		day = v1;
319 
320 		/* {Day,Weekday} {Month,Monthname} ... */
321 		/* if no recognizable month, assume just a day alone -- this is
322 		 * very unlikely and can only happen after the first 12 days.
323 		 * --find month or use current month */
324 		if (!(month = getfield(endp, &endp, &flags))) {
325 			month = tp->tm_mon + 1;
326 			/* F_ISDAY is set only if a weekday was spelled out */
327 			/* F_ISDAY must be set if 0 < day < 8 */
328 			if ((day <= 7) && (day >= 1))
329 				interval = WEEKLY;
330 			else
331 				interval = MONTHLY;
332 		} else if ((day <= 7) && (day >= 1))
333 			day += 10;
334 			/* it's a weekday; make it the first one of the month */
335 		if (month == -1) {
336 			month = tp->tm_mon + 1;
337 			interval = MONTHLY;
338 		} else if (calendar)
339 			adjust_calendar(&day, &month);
340 		if ((month > 12) || (month < 1))
341 			return (NULL);
342 	}
343 
344 	/* 2. {Monthname} XYZ ... */
345 	else if (flags & F_ISMONTH) {
346 		month = v1;
347 		if (month == -1) {
348 			month = tp->tm_mon + 1;
349 			interval = MONTHLY;
350 		}
351 		/* Monthname {day,weekday} */
352 		/* if no recognizable day, assume the first day in month */
353 		if (!(day = getfield(endp, &endp, &flags)))
354 			day = 1;
355 		/* If a weekday was spelled out without an ordering,
356 		 * assume the first of that day in the month */
357 		if ((flags & F_ISDAY)) {
358 			if ((day >= 1) && (day <=7))
359 				day += 10;
360 		} else if (calendar)
361 			adjust_calendar(&day, &month);
362 	}
363 
364 	/* Hm ... */
365 	else {
366 		v2 = getfield(endp, &endp, &flags);
367 
368 		/*
369 		 * {Day} {Monthname} ...
370 		 * where Day <= 12
371 		 */
372 		if (flags & F_ISMONTH) {
373 			day = v1;
374 			month = v2;
375 			if (month == -1) {
376 				month = tp->tm_mon + 1;
377 				interval = MONTHLY;
378 			} else if (calendar)
379 				adjust_calendar(&day, &month);
380 		}
381 
382 		/* {Month} {Weekday,Day} ...  */
383 		else {
384 			/* F_ISDAY set, v2 > 12, or no way to tell */
385 			month = v1;
386 			/* if no recognizable day, assume the first */
387 			day = v2 ? v2 : 1;
388 			if ((flags & F_ISDAY)) {
389 				if ((day >= 1) && (day <= 7))
390 					day += 10;
391 			} else
392 				adjust_calendar(&day, &month);
393 		}
394 	}
395 
396 	/* convert Weekday into *next*  Day,
397 	 * e.g.: 'Sunday' -> 22
398 	 *       'SundayLast' -> ??
399 	 */
400 	if (flags & F_ISDAY) {
401 #if DEBUG
402 		fprintf(stderr, "\nday: %d %s month %d\n", day, endp, month);
403 #endif
404 
405 		varp = 1;
406 		/* variable weekday, SundayLast, MondayFirst ... */
407 		if (day < 0 || day >= 10)
408 			vwd = day;
409 		else {
410 			day = tp->tm_mday + (((day - 1) - tp->tm_wday + 7) % 7);
411 			interval = WEEKLY;
412 		}
413 	} else
414 	/* Check for silliness.  Note we still catch Feb 29 */
415 		if (!(flags & F_SPECIAL) &&
416 		    (day > (cumdays[month + 1] - cumdays[month]) || day < 1)) {
417 			if (!((month == 2 && day == 29) ||
418 			    (interval == MONTHLY && day <= 31)))
419 				return (NULL);
420 		}
421 
422 	if (!(flags & F_SPECIAL)) {
423 		monthp = month;
424 		dayp = day;
425 		day = cumdays[month] + day;
426 #if DEBUG
427 		fprintf(stderr, "day2: day %d(%d) yday %d\n", dayp, day, tp->tm_yday);
428 #endif
429 	/* Speed up processing for the most common situation:  yearly events
430 	 * when the interval being checked is less than a month or so (this
431 	 * could be less than a year, but then we have to start worrying about
432 	 * leap years).  Only one event can match, and it's easy to find.
433 	 * Note we can't check special events, because they can wander widely.
434 	 */
435 		if (((v1 = offset + f_dayAfter) < 50) && (interval == YEARLY)) {
436 			memcpy(&tmtmp, tp, sizeof(struct tm));
437 			tmtmp.tm_mday = dayp;
438 			tmtmp.tm_mon = monthp - 1;
439 			if (vwd) {
440 			/* We want the event next year if it's late now
441 			 * this year.  The 50-day limit means we don't have to
442 			 * worry if next year is or isn't a leap year.
443 			 */
444 				if (tp->tm_yday > 300 && tmtmp.tm_mon <= 1)
445 					variable_weekday(&vwd, tmtmp.tm_mon + 1,
446 					    tmtmp.tm_year + TM_YEAR_BASE + 1);
447 				else
448 					variable_weekday(&vwd, tmtmp.tm_mon + 1,
449 					    tmtmp.tm_year + TM_YEAR_BASE);
450 				day = cumdays[tmtmp.tm_mon + 1] + vwd;
451 				tmtmp.tm_mday = vwd;
452 			}
453 			v2 = day - tp->tm_yday;
454 			if ((v2 > v1) || (v2 < 0)) {
455 				if ((v2 += isleap(tp->tm_year + TM_YEAR_BASE) ? 366 : 365)
456 				    <= v1)
457 					tmtmp.tm_year++;
458 				else if(!bodun || (day - tp->tm_yday) != -1)
459 					return(NULL);
460 			}
461 			if ((tmp = malloc(sizeof(struct match))) == NULL)
462 				err(1, NULL);
463 
464 			if (bodun && (day - tp->tm_yday) == -1) {
465 				tmp->when = f_time - 1 * SECSPERDAY;
466 				tmtmp.tm_mday++;
467 				tmp->bodun = 1;
468 			} else {
469 				tmp->when = f_time + v2 * SECSPERDAY;
470 				tmp->bodun = 0;
471 			}
472 
473 			(void)mktime(&tmtmp);
474 			if (strftime(tmp->print_date,
475 			    sizeof(tmp->print_date),
476 			/*    "%a %b %d", &tm);  Skip weekdays */
477 			    "%b %d", &tmtmp) == 0)
478 				tmp->print_date[sizeof(tmp->print_date) - 1] = '\0';
479 
480 			tmp->var   = varp;
481 			tmp->next  = NULL;
482 			return(tmp);
483 		}
484 	} else {
485 		varp = 1;
486 		/* Set up v1 to the event number and ... */
487 		v1 = vwd % (NUMEV + 1) - 1;
488 		vwd /= (NUMEV + 1);
489 		if (v1 < 0) {
490 			v1 += NUMEV + 1;
491 			vwd--;
492 		}
493 		dayp = monthp = 1;	/* Why not */
494 	}
495 
496 	/* Compare to past and coming instances of the event.  The i == 0 part
497 	 * of the loop corresponds to this specific instance.  Note that we
498 	 * can leave things sort of higgledy-piggledy since a mktime() happens
499 	 * on this before anything gets printed.  Also note that even though
500 	 * we've effectively gotten rid of f_dayBefore, we still have to check
501 	 * the one prior event for situations like "the 31st of every month"
502 	 * and "yearly" events which could happen twice in one year but not in
503 	 * the next */
504 	tmp2 = matches;
505 	for (i = -1; i < 2; i++) {
506 		memcpy(&tmtmp, tp, sizeof(struct tm));
507 		tmtmp.tm_mday = dayp;
508 		tmtmp.tm_mon = month = monthp - 1;
509 		do {
510 			v2 = 0;
511 			switch (interval) {
512 			case WEEKLY:
513 				tmtmp.tm_mday += 7 * i;
514 				break;
515 			case MONTHLY:
516 				month += i;
517 				tmtmp.tm_mon = month;
518 				switch(tmtmp.tm_mon) {
519 				case -1:
520 					tmtmp.tm_mon = month = 11;
521 					tmtmp.tm_year--;
522 					break;
523 				case 12:
524 					tmtmp.tm_mon = month = 0;
525 					tmtmp.tm_year++;
526 					break;
527 				}
528 				if (vwd) {
529 					v1 = vwd;
530 					variable_weekday(&v1, tmtmp.tm_mon + 1,
531 					    tmtmp.tm_year + TM_YEAR_BASE);
532 					tmtmp.tm_mday = v1;
533 				} else
534 					tmtmp.tm_mday = dayp;
535 				break;
536 			case YEARLY:
537 			default:
538 				tmtmp.tm_year += i;
539 				if (flags & F_SPECIAL) {
540 					tmtmp.tm_mon = 0;	/* Gee, mktime() is nice */
541 					tmtmp.tm_mday = spev[v1].getev(tmtmp.tm_year +
542 					    TM_YEAR_BASE) + vwd;
543 				} else if (vwd) {
544 					v1 = vwd;
545 					variable_weekday(&v1, tmtmp.tm_mon + 1,
546 					    tmtmp.tm_year + TM_YEAR_BASE);
547 					tmtmp.tm_mday = v1;
548 				} else {
549 				/* Need the following to keep Feb 29 from
550 				 * becoming Mar 1 */
551 				tmtmp.tm_mday = dayp;
552 				tmtmp.tm_mon = monthp - 1;
553 				}
554 				break;
555 			}
556 			/* How many days apart are we */
557 			if ((ttmp = mktime(&tmtmp)) == -1)
558 				warnx("time out of range: %s", endp);
559 			else {
560 				tdiff = difftime(ttmp, f_time)/ SECSPERDAY;
561 				if (tdiff <= offset + f_dayAfter ||
562 				    (bodun && tdiff == -1)) {
563 					if (tdiff >=  0 ||
564 					    (bodun && tdiff == -1)) {
565 					if ((tmp = malloc(sizeof(struct match))) == NULL)
566 						err(1, NULL);
567 					tmp->when = ttmp;
568 					if (strftime(tmp->print_date,
569 					    sizeof(tmp->print_date),
570 					/*    "%a %b %d", &tm);  Skip weekdays */
571 					    "%b %d", &tmtmp) == 0)
572 						tmp->print_date[sizeof(tmp->print_date) - 1] = '\0';
573 					tmp->bodun = bodun && tdiff == -1;
574 					tmp->var   = varp;
575 					tmp->next  = NULL;
576 					if (tmp2)
577 						tmp2->next = tmp;
578 					else
579 						matches = tmp;
580 					tmp2 = tmp;
581 					v2 = (i == 1) ? 1 : 0;
582 					}
583 				} else
584 					i = 2; /* No point checking in the future */
585 			}
586 		} while (v2 != 0);
587 	}
588 	return (matches);
589 }
590 
591 
592 int
593 getmonth(char *s)
594 {
595 	char **p;
596 	struct fixs *n;
597 
598 	for (n = fnmonths; n->name; ++n)
599 		if (!strncasecmp(s, n->name, n->len))
600 			return ((n - fnmonths) + 1);
601 	for (n = nmonths; n->name; ++n)
602 		if (!strncasecmp(s, n->name, n->len))
603 			return ((n - nmonths) + 1);
604 	for (p = months; *p; ++p)
605 		if (!strncasecmp(s, *p, 3))
606 			return ((p - months) + 1);
607 	return (0);
608 }
609 
610 
611 int
612 getday(char *s)
613 {
614 	char **p;
615 	struct fixs *n;
616 
617 	for (n = fndays; n->name; ++n)
618 		if (!strncasecmp(s, n->name, n->len))
619 			return ((n - fndays) + 1);
620 	for (n = ndays; n->name; ++n)
621 		if (!strncasecmp(s, n->name, n->len))
622 			return ((n - ndays) + 1);
623 	for (p = days; *p; ++p)
624 		if (!strncasecmp(s, *p, 3))
625 			return ((p - days) + 1);
626 	return (0);
627 }
628 
629 /* return offset for variable weekdays
630  * -1 -> last weekday in month
631  * +1 -> first weekday in month
632  * ... etc ...
633  */
634 int
635 getdayvar(char *s)
636 {
637 	int offset;
638 
639 
640 	offset = strlen(s);
641 
642 	/* Sun+1 or Wednesday-2
643 	 *    ^              ^   */
644 
645 	/* printf ("x: %s %s %d\n", s, s + offset - 2, offset); */
646 	switch(*(s + offset - 2)) {
647 	case '-':
648 	case '+':
649 	    return(atoi(s + offset - 2));
650 	    break;
651 	}
652 
653 	/*
654 	 * some aliases: last, first, second, third, fourth
655 	 */
656 
657 	/* last */
658 	if      (offset > 4 && !strcasecmp(s + offset - 4, "last"))
659 	    return(-1);
660 	else if (offset > 5 && !strcasecmp(s + offset - 5, "first"))
661 	    return(+1);
662 	else if (offset > 6 && !strcasecmp(s + offset - 6, "second"))
663 	    return(+2);
664 	else if (offset > 5 && !strcasecmp(s + offset - 5, "third"))
665 	    return(+3);
666 	else if (offset > 6 && !strcasecmp(s + offset - 6, "fourth"))
667 	    return(+4);
668 
669 	/* no offset detected */
670 	return(0);
671 }
672 
673 
674 int
675 foy(int year)
676 {
677 	/* 0-6; what weekday Jan 1 is */
678 	year--;
679 	return ((1 - year/100 + year/400 + (int)(365.25 * year)) % 7);
680 }
681 
682 
683 
684 void
685 variable_weekday(int *day, int month, int year)
686 {
687 	int v1, v2;
688 	int *cumdays;
689 	int day1;
690 
691 	if (isleap(year))
692 		cumdays = daytab[1];
693 	else
694 		cumdays = daytab[0];
695 	day1 = foy(year);
696 	/* negative offset; last, -4 .. -1 */
697 	if (*day < 0) {
698 		v1 = *day/10 - 1;          /* offset -4 ... -1 */
699 		*day = 10 + (*day % 10);    /* day 1 ... 7 */
700 
701 		/* which weekday the end of the month is (1-7) */
702 		v2 = (cumdays[month + 1] + day1) % 7 + 1;
703 
704 		/* and subtract enough days */
705 		*day = cumdays[month + 1] - cumdays[month] +
706 		    (v1 + 1) * 7 - (v2 - *day + 7) % 7;
707 #if DEBUG
708 		fprintf(stderr, "\nMonth %d ends on weekday %d\n", month, v2);
709 #endif
710 	}
711 
712 	/* first, second ... +1 ... +5 */
713 	else {
714 		v1 = *day/10;        /* offset */
715 		*day = *day % 10;
716 
717 		/* which weekday the first of the month is (1-7) */
718 		v2 = (cumdays[month] + 1 + day1) % 7 + 1;
719 
720 		/* and add enough days */
721 		*day = 1 + (v1 - 1) * 7 + (*day - v2 + 7) % 7;
722 #if DEBUG
723 		fprintf(stderr, "\nMonth %d starts on weekday %d\n", month, v2);
724 #endif
725 	}
726 }
727