xref: /openbsd-src/usr.bin/calendar/day.c (revision 883b05ef9d2486ec13cc192e29c3b2c9aacd19f1)
1*883b05efSmillert /*	$OpenBSD: day.c,v 1.37 2019/08/12 20:03:28 millert Exp $	*/
2b1fc8d4cSmillert 
3b1fc8d4cSmillert /*
4b1fc8d4cSmillert  * Copyright (c) 1989, 1993, 1994
5b1fc8d4cSmillert  *	The Regents of the University of California.  All rights reserved.
6b1fc8d4cSmillert  *
7b1fc8d4cSmillert  * Redistribution and use in source and binary forms, with or without
8b1fc8d4cSmillert  * modification, are permitted provided that the following conditions
9b1fc8d4cSmillert  * are met:
10b1fc8d4cSmillert  * 1. Redistributions of source code must retain the above copyright
11b1fc8d4cSmillert  *    notice, this list of conditions and the following disclaimer.
12b1fc8d4cSmillert  * 2. Redistributions in binary form must reproduce the above copyright
13b1fc8d4cSmillert  *    notice, this list of conditions and the following disclaimer in the
14b1fc8d4cSmillert  *    documentation and/or other materials provided with the distribution.
15f75387cbSmillert  * 3. Neither the name of the University nor the names of its contributors
16b1fc8d4cSmillert  *    may be used to endorse or promote products derived from this software
17b1fc8d4cSmillert  *    without specific prior written permission.
18b1fc8d4cSmillert  *
19b1fc8d4cSmillert  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20b1fc8d4cSmillert  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21b1fc8d4cSmillert  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22b1fc8d4cSmillert  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23b1fc8d4cSmillert  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24b1fc8d4cSmillert  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25b1fc8d4cSmillert  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26b1fc8d4cSmillert  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27b1fc8d4cSmillert  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28b1fc8d4cSmillert  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29b1fc8d4cSmillert  * SUCH DAMAGE.
30b1fc8d4cSmillert  */
31b1fc8d4cSmillert 
32b1fc8d4cSmillert #include <sys/types.h>
33b1fc8d4cSmillert #include <sys/uio.h>
34b1fc8d4cSmillert 
35b1fc8d4cSmillert #include <ctype.h>
36b1fc8d4cSmillert #include <err.h>
37b1fc8d4cSmillert #include <locale.h>
38b1fc8d4cSmillert #include <stdio.h>
39b1fc8d4cSmillert #include <stdlib.h>
40b1fc8d4cSmillert #include <string.h>
41b1fc8d4cSmillert #include <time.h>
42b1fc8d4cSmillert 
43b1fc8d4cSmillert #include "pathnames.h"
44b1fc8d4cSmillert #include "calendar.h"
45b1fc8d4cSmillert 
46b0589fbaSjsg extern struct iovec header[];
47b0589fbaSjsg 
488a44d90fSpjanzen #define WEEKLY 1
498a44d90fSpjanzen #define MONTHLY 2
508a44d90fSpjanzen #define YEARLY 3
518a44d90fSpjanzen 
52b1fc8d4cSmillert struct tm *tp;
538a44d90fSpjanzen int *cumdays, offset;
54b1fc8d4cSmillert char dayname[10];
55907f0a9fSmickey enum calendars calendar;
56907f0a9fSmickey u_long julian;
57b1fc8d4cSmillert 
58b1fc8d4cSmillert 
59b1fc8d4cSmillert /* 1-based month, 0-based days, cumulative */
60b1fc8d4cSmillert int daytab[][14] = {
61b1fc8d4cSmillert 	{ 0, -1, 30, 58, 89, 119, 150, 180, 211, 242, 272, 303, 333, 364 },
62b1fc8d4cSmillert 	{ 0, -1, 30, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
63b1fc8d4cSmillert };
64b1fc8d4cSmillert 
65b1fc8d4cSmillert static char *days[] = {
66b1fc8d4cSmillert 	"sun", "mon", "tue", "wed", "thu", "fri", "sat", NULL,
67b1fc8d4cSmillert };
68b1fc8d4cSmillert 
69b1fc8d4cSmillert static char *months[] = {
70b1fc8d4cSmillert 	"jan", "feb", "mar", "apr", "may", "jun",
71b1fc8d4cSmillert 	"jul", "aug", "sep", "oct", "nov", "dec", NULL,
72b1fc8d4cSmillert };
73b1fc8d4cSmillert 
74b1fc8d4cSmillert static struct fixs fndays[8];         /* full national days names */
75b1fc8d4cSmillert static struct fixs ndays[8];          /* short national days names */
76b1fc8d4cSmillert 
77b1fc8d4cSmillert static struct fixs fnmonths[13];      /* full national months names */
78b1fc8d4cSmillert static struct fixs nmonths[13];       /* short national month names */
79b1fc8d4cSmillert 
80638175c6Sderaadt void
fill_print_date(struct match * m,struct tm * tm)81ca2f2f78Sespie fill_print_date(struct match *m, struct tm *tm)
82ca2f2f78Sespie {
83ca2f2f78Sespie 	if (strftime(m->print_date, sizeof(m->print_date),
84ca2f2f78Sespie 	    daynames ? "%a %b %d" : "%b %d", tm) == 0)
85ca2f2f78Sespie 		m->print_date[sizeof(m->print_date) - 1] = '\0';
86ca2f2f78Sespie }
87ca2f2f78Sespie 
88ca2f2f78Sespie void
setnnames(void)89638175c6Sderaadt setnnames(void)
90b1fc8d4cSmillert {
91b1fc8d4cSmillert 	char buf[80];
92b1fc8d4cSmillert 	int i, l;
93b1fc8d4cSmillert 	struct tm tm;
94b1fc8d4cSmillert 
95b1fc8d4cSmillert 	for (i = 0; i < 7; i++) {
96b1fc8d4cSmillert 		tm.tm_wday = i;
9744c6eb09Smillert 		l = strftime(buf, sizeof(buf), "%a", &tm);
986cd4fad2Sderaadt 		for (; l > 0 && isspace((unsigned char)buf[l - 1]); l--)
99b1fc8d4cSmillert 			;
100b1fc8d4cSmillert 		buf[l] = '\0';
101b1fc8d4cSmillert 		free(ndays[i].name);
102b1fc8d4cSmillert 		if ((ndays[i].name = strdup(buf)) == NULL)
103e454ee63Spjanzen 			err(1, NULL);
104b1fc8d4cSmillert 		ndays[i].len = strlen(buf);
105b1fc8d4cSmillert 
10644c6eb09Smillert 		l = strftime(buf, sizeof(buf), "%A", &tm);
1076cd4fad2Sderaadt 		for (; l > 0 && isspace((unsigned char)buf[l - 1]); l--)
108b1fc8d4cSmillert 			;
109b1fc8d4cSmillert 		buf[l] = '\0';
110b1fc8d4cSmillert 		free(fndays[i].name);
111b1fc8d4cSmillert 		if ((fndays[i].name = strdup(buf)) == NULL)
112e454ee63Spjanzen 			err(1, NULL);
113b1fc8d4cSmillert 		fndays[i].len = strlen(buf);
114b1fc8d4cSmillert 	}
115b1fc8d4cSmillert 
116b1fc8d4cSmillert 	for (i = 0; i < 12; i++) {
117b1fc8d4cSmillert 		tm.tm_mon = i;
11844c6eb09Smillert 		l = strftime(buf, sizeof(buf), "%b", &tm);
1196cd4fad2Sderaadt 		for (; l > 0 && isspace((unsigned char)buf[l - 1]); l--)
120b1fc8d4cSmillert 			;
121b1fc8d4cSmillert 		buf[l] = '\0';
122b1fc8d4cSmillert 		free(nmonths[i].name);
123b1fc8d4cSmillert 		if ((nmonths[i].name = strdup(buf)) == NULL)
124e454ee63Spjanzen 			err(1, NULL);
125b1fc8d4cSmillert 		nmonths[i].len = strlen(buf);
126b1fc8d4cSmillert 
12744c6eb09Smillert 		l = strftime(buf, sizeof(buf), "%B", &tm);
1286cd4fad2Sderaadt 		for (; l > 0 && isspace((unsigned char)buf[l - 1]); l--)
129b1fc8d4cSmillert 			;
130b1fc8d4cSmillert 		buf[l] = '\0';
131b1fc8d4cSmillert 		free(fnmonths[i].name);
132b1fc8d4cSmillert 		if ((fnmonths[i].name = strdup(buf)) == NULL)
133e454ee63Spjanzen 			err(1, NULL);
134b1fc8d4cSmillert 		fnmonths[i].len = strlen(buf);
135b1fc8d4cSmillert 	}
1368a44d90fSpjanzen 	/* Hardwired special events */
137eaf2975cSmickey 	spev[0].name = strdup(PESACH);
138eaf2975cSmickey 	spev[0].nlen = PESACHLEN;
139eaf2975cSmickey 	spev[0].getev = pesach;
140eaf2975cSmickey 	spev[1].name = strdup(EASTER);
141eaf2975cSmickey 	spev[1].nlen = EASTERNAMELEN;
142eaf2975cSmickey 	spev[1].getev = easter;
143eaf2975cSmickey 	spev[2].name = strdup(PASKHA);
144eaf2975cSmickey 	spev[2].nlen = PASKHALEN;
145eaf2975cSmickey 	spev[2].getev = paskha;
1468a44d90fSpjanzen 	for (i = 0; i < NUMEV; i++) {
1478a44d90fSpjanzen 		if (spev[i].name == NULL)
148e454ee63Spjanzen 			err(1, NULL);
1498a44d90fSpjanzen 		spev[i].uname = NULL;
1508a44d90fSpjanzen 	}
151b1fc8d4cSmillert }
152b1fc8d4cSmillert 
153b1fc8d4cSmillert void
settime(time_t * now)154638175c6Sderaadt settime(time_t *now)
155b1fc8d4cSmillert {
1568a44d90fSpjanzen 	tp = localtime(now);
1578a44d90fSpjanzen 	tp->tm_sec = 0;
1588a44d90fSpjanzen 	tp->tm_min = 0;
1598a44d90fSpjanzen 	/* Avoid getting caught by a timezone shift; set time to noon */
1608a44d90fSpjanzen 	tp->tm_isdst = 0;
1618a44d90fSpjanzen 	tp->tm_hour = 12;
1628a44d90fSpjanzen 	*now = mktime(tp);
16378badebcSmillert 	if (isleap(tp->tm_year + 1900))
164b1fc8d4cSmillert 		cumdays = daytab[1];
1658a44d90fSpjanzen 	else
166b1fc8d4cSmillert 		cumdays = daytab[0];
167b1fc8d4cSmillert 	/* Friday displays Monday's events */
168b1fc8d4cSmillert 	offset = tp->tm_wday == 5 ? 3 : 1;
169db809114Smillert 	if (f_Setday)
1708a44d90fSpjanzen 		offset = 0;	/* Except not when range is set explicitly */
171b1fc8d4cSmillert 	header[5].iov_base = dayname;
172b1fc8d4cSmillert 
173b1fc8d4cSmillert 	(void) setlocale(LC_TIME, "C");
174b1fc8d4cSmillert 	header[5].iov_len = strftime(dayname, sizeof(dayname), "%A", tp);
175b1fc8d4cSmillert 	(void) setlocale(LC_TIME, "");
176b1fc8d4cSmillert 
177b1fc8d4cSmillert 	setnnames();
178b1fc8d4cSmillert }
179b1fc8d4cSmillert 
180c60f2790Smillert /* convert [Year][Month]Day into unix time (since 1970)
181c60f2790Smillert  * Year: two or four digits, Month: two digits, Day: two digits
182b1fc8d4cSmillert  */
183638175c6Sderaadt time_t
Mktime(char * date)184638175c6Sderaadt Mktime(char *date)
185b1fc8d4cSmillert {
186b1fc8d4cSmillert 	time_t t;
187b1fc8d4cSmillert 	int len;
188b1fc8d4cSmillert 	struct tm tm;
189b1fc8d4cSmillert 
190b1fc8d4cSmillert 	(void)time(&t);
191b1fc8d4cSmillert 	tp = localtime(&t);
192b1fc8d4cSmillert 
193b1fc8d4cSmillert 	len = strlen(date);
194c60f2790Smillert 	if (len < 2)
195c60f2790Smillert 		return((time_t)-1);
1964486f476Sderaadt 	bzero(&tm, sizeof tm);
197b1fc8d4cSmillert 	tm.tm_sec = 0;
198b1fc8d4cSmillert 	tm.tm_min = 0;
1990b02000dSpjanzen 	/* Avoid getting caught by a timezone shift; set time to noon */
2000b02000dSpjanzen 	tm.tm_isdst = 0;
2010b02000dSpjanzen 	tm.tm_hour = 12;
202b1fc8d4cSmillert 	tm.tm_wday = 0;
203b1fc8d4cSmillert 	tm.tm_mday = tp->tm_mday;
204b1fc8d4cSmillert 	tm.tm_mon = tp->tm_mon;
205b1fc8d4cSmillert 	tm.tm_year = tp->tm_year;
206b1fc8d4cSmillert 
207c60f2790Smillert 	/* Day */
208c60f2790Smillert 	tm.tm_mday = atoi(date + len - 2);
209b1fc8d4cSmillert 
210c60f2790Smillert 	/* Month */
211b1fc8d4cSmillert 	if (len >= 4) {
212c60f2790Smillert 		*(date + len - 2) = '\0';
213c60f2790Smillert 		tm.tm_mon = atoi(date + len - 4) - 1;
214b1fc8d4cSmillert 	}
215b1fc8d4cSmillert 
216b1fc8d4cSmillert 	/* Year */
2170b02000dSpjanzen 	if (len >= 6) {
218c60f2790Smillert 		*(date + len - 4) = '\0';
219c60f2790Smillert 		tm.tm_year = atoi(date);
220b1fc8d4cSmillert 
2210b02000dSpjanzen 		if (tm.tm_year < 69)		/* Y2K */
22278badebcSmillert 			tm.tm_year += 100;
22378badebcSmillert 		else if (tm.tm_year > 1900)
22478badebcSmillert 			tm.tm_year -= 1900;
225b1fc8d4cSmillert 	}
226b1fc8d4cSmillert 
227b1fc8d4cSmillert #if DEBUG
228fab9a59cSderaadt 	printf("Mktime: %d %lld %d %s\n", (int)mktime(&tm), (long long)t, len,
229b1fc8d4cSmillert 	    asctime(&tm));
230b1fc8d4cSmillert #endif
231b1fc8d4cSmillert 	return(mktime(&tm));
232b1fc8d4cSmillert }
233b1fc8d4cSmillert 
234babb7faeSderaadt static void
adjust_calendar(int * day,int * month)235907f0a9fSmickey adjust_calendar(int *day, int *month)
236907f0a9fSmickey {
237907f0a9fSmickey 	switch (calendar) {
238907f0a9fSmickey 	case GREGORIAN:
239907f0a9fSmickey 		break;
240907f0a9fSmickey 
241907f0a9fSmickey 	case JULIAN:
242907f0a9fSmickey 		*day += julian;
243907f0a9fSmickey 		if (*day > (cumdays[*month + 1] - cumdays[*month])) {
244907f0a9fSmickey 			*day -= (cumdays[*month + 1] - cumdays[*month]);
245907f0a9fSmickey 			if (++*month > 12)
246907f0a9fSmickey 				*month = 1;
247907f0a9fSmickey 		}
248907f0a9fSmickey 		break;
249907f0a9fSmickey 	case LUNAR:
250907f0a9fSmickey 		break;
251907f0a9fSmickey 	}
252907f0a9fSmickey }
253907f0a9fSmickey 
254b1fc8d4cSmillert /*
255b1fc8d4cSmillert  * Possible date formats include any combination of:
256b1fc8d4cSmillert  *	3-charmonth			(January, Jan, Jan)
257b1fc8d4cSmillert  *	3-charweekday			(Friday, Monday, mon.)
258b1fc8d4cSmillert  *	numeric month or day		(1, 2, 04)
259b1fc8d4cSmillert  *
2601daff509Spjanzen  * Any character except \t or '*' may separate them, or they may not be
2611daff509Spjanzen  * separated.  Any line following a line that is matched, that starts
2621daff509Spjanzen  * with \t, is shown along with the matched line.
263b1fc8d4cSmillert  */
2644185b97eSpjanzen struct match *
isnow(char * endp,int bodun)265638175c6Sderaadt isnow(char *endp, int bodun)
266b1fc8d4cSmillert {
2678a44d90fSpjanzen 	int day = 0, flags = 0, month = 0, v1, v2, i;
2688a44d90fSpjanzen 	int monthp, dayp, varp = 0;
2698a44d90fSpjanzen 	struct match *matches = NULL, *tmp, *tmp2;
2708a44d90fSpjanzen 	int interval = YEARLY;	/* how frequently the event repeats. */
2718a44d90fSpjanzen 	int vwd = 0;	/* Variable weekday */
2728a44d90fSpjanzen 	time_t tdiff, ttmp;
2738a44d90fSpjanzen 	struct tm tmtmp;
274b1fc8d4cSmillert 
275b1fc8d4cSmillert 	/*
276b1fc8d4cSmillert 	 * CONVENTION
277b1fc8d4cSmillert 	 *
278b1fc8d4cSmillert 	 * Month:     1-12
279b1fc8d4cSmillert 	 * Monthname: Jan .. Dec
280b1fc8d4cSmillert 	 * Day:       1-31
281b1fc8d4cSmillert 	 * Weekday:   Mon-Sun
282b1fc8d4cSmillert 	 *
283b1fc8d4cSmillert 	 */
284b1fc8d4cSmillert 
285b1fc8d4cSmillert 	/* read first field */
286b1fc8d4cSmillert 	/* didn't recognize anything, skip it */
287b1fc8d4cSmillert 	if (!(v1 = getfield(endp, &endp, &flags)))
2884185b97eSpjanzen 		return (NULL);
289b1fc8d4cSmillert 
2900eb5bb76Smickey 	/* adjust bodun rate */
2910eb5bb76Smickey 	if (bodun && !bodun_always)
29266ad965fSdjm 		bodun = !arc4random_uniform(3);
2930eb5bb76Smickey 
294b1fc8d4cSmillert 	/* Easter or Easter depending days */
2958a44d90fSpjanzen 	if (flags & F_SPECIAL)
2968a44d90fSpjanzen 		vwd = v1;
297b1fc8d4cSmillert 
298b1fc8d4cSmillert 	 /*
299b1fc8d4cSmillert 	  * 1. {Weekday,Day} XYZ ...
300b1fc8d4cSmillert 	  *
301b1fc8d4cSmillert 	  *    where Day is > 12
302b1fc8d4cSmillert 	  */
303b1fc8d4cSmillert 	else if (flags & F_ISDAY || v1 > 12) {
304b1fc8d4cSmillert 
3058a44d90fSpjanzen 		/* found a day; day: 13-31 or weekday: 1-7 */
306b1fc8d4cSmillert 		day = v1;
307b1fc8d4cSmillert 
308b1fc8d4cSmillert 		/* {Day,Weekday} {Month,Monthname} ... */
3098a44d90fSpjanzen 		/* if no recognizable month, assume just a day alone -- this is
3108a44d90fSpjanzen 		 * very unlikely and can only happen after the first 12 days.
3118a44d90fSpjanzen 		 * --find month or use current month */
3128a44d90fSpjanzen 		if (!(month = getfield(endp, &endp, &flags))) {
313b1fc8d4cSmillert 			month = tp->tm_mon + 1;
3148a44d90fSpjanzen 			/* F_ISDAY is set only if a weekday was spelled out */
3158a44d90fSpjanzen 			/* F_ISDAY must be set if 0 < day < 8 */
3168a44d90fSpjanzen 			if ((day <= 7) && (day >= 1))
3178a44d90fSpjanzen 				interval = WEEKLY;
3188a44d90fSpjanzen 			else
3198a44d90fSpjanzen 				interval = MONTHLY;
3208a44d90fSpjanzen 		} else if ((day <= 7) && (day >= 1))
3218a44d90fSpjanzen 			day += 10;
3228a44d90fSpjanzen 			/* it's a weekday; make it the first one of the month */
3238a44d90fSpjanzen 		if (month == -1) {
3248a44d90fSpjanzen 			month = tp->tm_mon + 1;
3258a44d90fSpjanzen 			interval = MONTHLY;
326*883b05efSmillert 		} else {
3278a44d90fSpjanzen 			if ((month > 12) || (month < 1))
3288a44d90fSpjanzen 				return (NULL);
329*883b05efSmillert 			if (calendar)
330*883b05efSmillert 				adjust_calendar(&day, &month);
331*883b05efSmillert 		}
332b1fc8d4cSmillert 	}
333b1fc8d4cSmillert 
334b1fc8d4cSmillert 	/* 2. {Monthname} XYZ ... */
335b1fc8d4cSmillert 	else if (flags & F_ISMONTH) {
336b1fc8d4cSmillert 		month = v1;
3378a44d90fSpjanzen 		if (month == -1) {
3388a44d90fSpjanzen 			month = tp->tm_mon + 1;
3398a44d90fSpjanzen 			interval = MONTHLY;
3408a44d90fSpjanzen 		}
341b1fc8d4cSmillert 		/* Monthname {day,weekday} */
342b1fc8d4cSmillert 		/* if no recognizable day, assume the first day in month */
343b1fc8d4cSmillert 		if (!(day = getfield(endp, &endp, &flags)))
344b1fc8d4cSmillert 			day = 1;
3458a44d90fSpjanzen 		/* If a weekday was spelled out without an ordering,
3468a44d90fSpjanzen 		 * assume the first of that day in the month */
347907f0a9fSmickey 		if ((flags & F_ISDAY)) {
348907f0a9fSmickey 			if ((day >= 1) && (day <=7))
3498a44d90fSpjanzen 				day += 10;
350907f0a9fSmickey 		} else if (calendar)
351907f0a9fSmickey 			adjust_calendar(&day, &month);
352b1fc8d4cSmillert 	}
353b1fc8d4cSmillert 
354b1fc8d4cSmillert 	/* Hm ... */
355b1fc8d4cSmillert 	else {
356b1fc8d4cSmillert 		v2 = getfield(endp, &endp, &flags);
357b1fc8d4cSmillert 
358b1fc8d4cSmillert 		/*
359b1fc8d4cSmillert 		 * {Day} {Monthname} ...
360b1fc8d4cSmillert 		 * where Day <= 12
361b1fc8d4cSmillert 		 */
362b1fc8d4cSmillert 		if (flags & F_ISMONTH) {
363b1fc8d4cSmillert 			day = v1;
364b1fc8d4cSmillert 			month = v2;
3658a44d90fSpjanzen 			if (month == -1) {
3668a44d90fSpjanzen 				month = tp->tm_mon + 1;
3678a44d90fSpjanzen 				interval = MONTHLY;
368907f0a9fSmickey 			} else if (calendar)
369907f0a9fSmickey 				adjust_calendar(&day, &month);
370b1fc8d4cSmillert 		}
371b1fc8d4cSmillert 
372b1fc8d4cSmillert 		/* {Month} {Weekday,Day} ...  */
373b1fc8d4cSmillert 		else {
374b1fc8d4cSmillert 			/* F_ISDAY set, v2 > 12, or no way to tell */
375b1fc8d4cSmillert 			month = v1;
376*883b05efSmillert 			if ((month > 12) || (month < 1))
377*883b05efSmillert 				return (NULL);
378b1fc8d4cSmillert 			/* if no recognizable day, assume the first */
379b1fc8d4cSmillert 			day = v2 ? v2 : 1;
380907f0a9fSmickey 			if ((flags & F_ISDAY)) {
381907f0a9fSmickey 				if ((day >= 1) && (day <= 7))
3828a44d90fSpjanzen 					day += 10;
383907f0a9fSmickey 			} else
384907f0a9fSmickey 				adjust_calendar(&day, &month);
385b1fc8d4cSmillert 		}
386b1fc8d4cSmillert 	}
387b1fc8d4cSmillert 
388b1fc8d4cSmillert 	/* convert Weekday into *next*  Day,
389b1fc8d4cSmillert 	 * e.g.: 'Sunday' -> 22
3900b02000dSpjanzen 	 *       'SundayLast' -> ??
391b1fc8d4cSmillert 	 */
392b1fc8d4cSmillert 	if (flags & F_ISDAY) {
393b1fc8d4cSmillert #if DEBUG
394b1fc8d4cSmillert 		fprintf(stderr, "\nday: %d %s month %d\n", day, endp, month);
395b1fc8d4cSmillert #endif
396b1fc8d4cSmillert 
3974185b97eSpjanzen 		varp = 1;
398b1fc8d4cSmillert 		/* variable weekday, SundayLast, MondayFirst ... */
3998a44d90fSpjanzen 		if (day < 0 || day >= 10)
4008a44d90fSpjanzen 			vwd = day;
401b1fc8d4cSmillert 		else {
4024185b97eSpjanzen 			day = tp->tm_mday + (((day - 1) - tp->tm_wday + 7) % 7);
4038a44d90fSpjanzen 			interval = WEEKLY;
4048a44d90fSpjanzen 		}
4058a44d90fSpjanzen 	} else
4068a44d90fSpjanzen 	/* Check for silliness.  Note we still catch Feb 29 */
4078a44d90fSpjanzen 		if (!(flags & F_SPECIAL) &&
4088a44d90fSpjanzen 		    (day > (cumdays[month + 1] - cumdays[month]) || day < 1)) {
4098a44d90fSpjanzen 			if (!((month == 2 && day == 29) ||
4108a44d90fSpjanzen 			    (interval == MONTHLY && day <= 31)))
4118a44d90fSpjanzen 				return (NULL);
412b1fc8d4cSmillert 		}
413b1fc8d4cSmillert 
4148a44d90fSpjanzen 	if (!(flags & F_SPECIAL)) {
4154185b97eSpjanzen 		monthp = month;
4164185b97eSpjanzen 		dayp = day;
417b1fc8d4cSmillert 		day = cumdays[month] + day;
418b1fc8d4cSmillert #if DEBUG
4194185b97eSpjanzen 		fprintf(stderr, "day2: day %d(%d) yday %d\n", dayp, day, tp->tm_yday);
420b1fc8d4cSmillert #endif
4218a44d90fSpjanzen 	/* Speed up processing for the most common situation:  yearly events
4228a44d90fSpjanzen 	 * when the interval being checked is less than a month or so (this
4238a44d90fSpjanzen 	 * could be less than a year, but then we have to start worrying about
4248a44d90fSpjanzen 	 * leap years).  Only one event can match, and it's easy to find.
4258a44d90fSpjanzen 	 * Note we can't check special events, because they can wander widely.
4268a44d90fSpjanzen 	 */
4278a44d90fSpjanzen 		if (((v1 = offset + f_dayAfter) < 50) && (interval == YEARLY)) {
4288a44d90fSpjanzen 			memcpy(&tmtmp, tp, sizeof(struct tm));
4298a44d90fSpjanzen 			tmtmp.tm_mday = dayp;
4308a44d90fSpjanzen 			tmtmp.tm_mon = monthp - 1;
4318a9bb311Spjanzen 			if (vwd) {
4328a9bb311Spjanzen 			/* We want the event next year if it's late now
4338a9bb311Spjanzen 			 * this year.  The 50-day limit means we don't have to
4348a9bb311Spjanzen 			 * worry if next year is or isn't a leap year.
4358a9bb311Spjanzen 			 */
4368a9bb311Spjanzen 				if (tp->tm_yday > 300 && tmtmp.tm_mon <= 1)
4378a9bb311Spjanzen 					variable_weekday(&vwd, tmtmp.tm_mon + 1,
43878badebcSmillert 					    tmtmp.tm_year + 1900 + 1);
4398a9bb311Spjanzen 				else
4408a9bb311Spjanzen 					variable_weekday(&vwd, tmtmp.tm_mon + 1,
44178badebcSmillert 					    tmtmp.tm_year + 1900);
4428a9bb311Spjanzen 				day = cumdays[tmtmp.tm_mon + 1] + vwd;
4438a9bb311Spjanzen 				tmtmp.tm_mday = vwd;
4448a9bb311Spjanzen 			}
4458a44d90fSpjanzen 			v2 = day - tp->tm_yday;
4468a44d90fSpjanzen 			if ((v2 > v1) || (v2 < 0)) {
44778badebcSmillert 				if ((v2 += isleap(tp->tm_year + 1900) ? 366 : 365)
4488a44d90fSpjanzen 				    <= v1)
4498a44d90fSpjanzen 					tmtmp.tm_year++;
4500eb5bb76Smickey 				else if(!bodun || (day - tp->tm_yday) != -1)
4514185b97eSpjanzen 					return(NULL);
452b1fc8d4cSmillert 			}
4538a44d90fSpjanzen 			if ((tmp = malloc(sizeof(struct match))) == NULL)
454e454ee63Spjanzen 				err(1, NULL);
455a807d8f6Smickey 
456a807d8f6Smickey 			if (bodun && (day - tp->tm_yday) == -1) {
457a807d8f6Smickey 				tmp->when = f_time - 1 * SECSPERDAY;
458a807d8f6Smickey 				tmtmp.tm_mday++;
459a807d8f6Smickey 				tmp->bodun = 1;
460a807d8f6Smickey 			} else {
4618a44d90fSpjanzen 				tmp->when = f_time + v2 * SECSPERDAY;
462a807d8f6Smickey 				tmp->bodun = 0;
463a807d8f6Smickey 			}
464a807d8f6Smickey 
4658a44d90fSpjanzen 			(void)mktime(&tmtmp);
466ca2f2f78Sespie 			fill_print_date(tmp, &tmtmp);
4678a44d90fSpjanzen 			tmp->var   = varp;
4688a44d90fSpjanzen 			tmp->next  = NULL;
4698a44d90fSpjanzen 			return(tmp);
4708a44d90fSpjanzen 		}
471907f0a9fSmickey 	} else {
4728a44d90fSpjanzen 		varp = 1;
4738a44d90fSpjanzen 		/* Set up v1 to the event number and ... */
4748a44d90fSpjanzen 		v1 = vwd % (NUMEV + 1) - 1;
4758a44d90fSpjanzen 		vwd /= (NUMEV + 1);
4768a44d90fSpjanzen 		if (v1 < 0) {
4778a44d90fSpjanzen 			v1 += NUMEV + 1;
4788a44d90fSpjanzen 			vwd--;
4798a44d90fSpjanzen 		}
4808a44d90fSpjanzen 		dayp = monthp = 1;	/* Why not */
4818a44d90fSpjanzen 	}
4828a44d90fSpjanzen 
4838a44d90fSpjanzen 	/* Compare to past and coming instances of the event.  The i == 0 part
4848a44d90fSpjanzen 	 * of the loop corresponds to this specific instance.  Note that we
4858a44d90fSpjanzen 	 * can leave things sort of higgledy-piggledy since a mktime() happens
4868a44d90fSpjanzen 	 * on this before anything gets printed.  Also note that even though
4878a44d90fSpjanzen 	 * we've effectively gotten rid of f_dayBefore, we still have to check
4888a44d90fSpjanzen 	 * the one prior event for situations like "the 31st of every month"
4898a44d90fSpjanzen 	 * and "yearly" events which could happen twice in one year but not in
4908a44d90fSpjanzen 	 * the next */
4918a44d90fSpjanzen 	tmp2 = matches;
4928a44d90fSpjanzen 	for (i = -1; i < 2; i++) {
4938a44d90fSpjanzen 		memcpy(&tmtmp, tp, sizeof(struct tm));
4948a44d90fSpjanzen 		tmtmp.tm_mday = dayp;
4958a44d90fSpjanzen 		tmtmp.tm_mon = month = monthp - 1;
4968a44d90fSpjanzen 		do {
4978a44d90fSpjanzen 			v2 = 0;
4988a44d90fSpjanzen 			switch (interval) {
4998a44d90fSpjanzen 			case WEEKLY:
5008a44d90fSpjanzen 				tmtmp.tm_mday += 7 * i;
5018a44d90fSpjanzen 				break;
5028a44d90fSpjanzen 			case MONTHLY:
5038a44d90fSpjanzen 				month += i;
5048a44d90fSpjanzen 				tmtmp.tm_mon = month;
5058a44d90fSpjanzen 				switch(tmtmp.tm_mon) {
5068a44d90fSpjanzen 				case -1:
5078a44d90fSpjanzen 					tmtmp.tm_mon = month = 11;
5088a44d90fSpjanzen 					tmtmp.tm_year--;
5098a44d90fSpjanzen 					break;
5108a44d90fSpjanzen 				case 12:
5118a44d90fSpjanzen 					tmtmp.tm_mon = month = 0;
5128a44d90fSpjanzen 					tmtmp.tm_year++;
5138a44d90fSpjanzen 					break;
5148a44d90fSpjanzen 				}
5158a44d90fSpjanzen 				if (vwd) {
5168a44d90fSpjanzen 					v1 = vwd;
5178a44d90fSpjanzen 					variable_weekday(&v1, tmtmp.tm_mon + 1,
51878badebcSmillert 					    tmtmp.tm_year + 1900);
5198a44d90fSpjanzen 					tmtmp.tm_mday = v1;
5208a44d90fSpjanzen 				} else
5218a44d90fSpjanzen 					tmtmp.tm_mday = dayp;
5228a44d90fSpjanzen 				break;
5238a44d90fSpjanzen 			case YEARLY:
5248a44d90fSpjanzen 			default:
5258a44d90fSpjanzen 				tmtmp.tm_year += i;
5268a44d90fSpjanzen 				if (flags & F_SPECIAL) {
5278a44d90fSpjanzen 					tmtmp.tm_mon = 0;	/* Gee, mktime() is nice */
5288a44d90fSpjanzen 					tmtmp.tm_mday = spev[v1].getev(tmtmp.tm_year +
52978badebcSmillert 					    1900) + vwd;
5308a44d90fSpjanzen 				} else if (vwd) {
5318a44d90fSpjanzen 					v1 = vwd;
5328a44d90fSpjanzen 					variable_weekday(&v1, tmtmp.tm_mon + 1,
53378badebcSmillert 					    tmtmp.tm_year + 1900);
5348a44d90fSpjanzen 					tmtmp.tm_mday = v1;
5358a44d90fSpjanzen 				} else {
5368a44d90fSpjanzen 				/* Need the following to keep Feb 29 from
5378a44d90fSpjanzen 				 * becoming Mar 1 */
5388a44d90fSpjanzen 				tmtmp.tm_mday = dayp;
5398a44d90fSpjanzen 				tmtmp.tm_mon = monthp - 1;
5408a44d90fSpjanzen 				}
5418a44d90fSpjanzen 				break;
5428a44d90fSpjanzen 			}
5438a44d90fSpjanzen 			/* How many days apart are we */
5448a44d90fSpjanzen 			if ((ttmp = mktime(&tmtmp)) == -1)
5458a44d90fSpjanzen 				warnx("time out of range: %s", endp);
5468a44d90fSpjanzen 			else {
5478a44d90fSpjanzen 				tdiff = difftime(ttmp, f_time)/ SECSPERDAY;
5480eb5bb76Smickey 				if (tdiff <= offset + f_dayAfter ||
5490eb5bb76Smickey 				    (bodun && tdiff == -1)) {
55052592e37Smillert 					if (((tmtmp.tm_mon == month) ||
55152592e37Smillert 					     (flags & F_SPECIAL) ||
55252592e37Smillert 					     (interval == WEEKLY)) &&
55345cf8591Smillert 					    (tdiff >=  0 ||
55445cf8591Smillert 					    (bodun && tdiff == -1))) {
5558a44d90fSpjanzen 					if ((tmp = malloc(sizeof(struct match))) == NULL)
556e454ee63Spjanzen 						err(1, NULL);
5578a44d90fSpjanzen 					tmp->when = ttmp;
558ca2f2f78Sespie 					fill_print_date(tmp, &tmtmp);
559a807d8f6Smickey 					tmp->bodun = bodun && tdiff == -1;
5608a44d90fSpjanzen 					tmp->var   = varp;
5618a44d90fSpjanzen 					tmp->next  = NULL;
5628a44d90fSpjanzen 					if (tmp2)
5638a44d90fSpjanzen 						tmp2->next = tmp;
5648a44d90fSpjanzen 					else
5658a44d90fSpjanzen 						matches = tmp;
5668a44d90fSpjanzen 					tmp2 = tmp;
5678a44d90fSpjanzen 					v2 = (i == 1) ? 1 : 0;
5688a44d90fSpjanzen 					}
5698a44d90fSpjanzen 				} else
5708a44d90fSpjanzen 					i = 2; /* No point checking in the future */
5718a44d90fSpjanzen 			}
5728a44d90fSpjanzen 		} while (v2 != 0);
5738a44d90fSpjanzen 	}
5748a44d90fSpjanzen 	return (matches);
5758a44d90fSpjanzen }
576b1fc8d4cSmillert 
577b1fc8d4cSmillert 
578b1fc8d4cSmillert int
getmonth(char * s)579638175c6Sderaadt getmonth(char *s)
580b1fc8d4cSmillert {
581c0932ef1Smpech 	char **p;
582b1fc8d4cSmillert 	struct fixs *n;
583b1fc8d4cSmillert 
584b1fc8d4cSmillert 	for (n = fnmonths; n->name; ++n)
585b1fc8d4cSmillert 		if (!strncasecmp(s, n->name, n->len))
586b1fc8d4cSmillert 			return ((n - fnmonths) + 1);
587b1fc8d4cSmillert 	for (n = nmonths; n->name; ++n)
588b1fc8d4cSmillert 		if (!strncasecmp(s, n->name, n->len))
589b1fc8d4cSmillert 			return ((n - nmonths) + 1);
590b1fc8d4cSmillert 	for (p = months; *p; ++p)
591b1fc8d4cSmillert 		if (!strncasecmp(s, *p, 3))
592b1fc8d4cSmillert 			return ((p - months) + 1);
593b1fc8d4cSmillert 	return (0);
594b1fc8d4cSmillert }
595b1fc8d4cSmillert 
596b1fc8d4cSmillert 
597b1fc8d4cSmillert int
getday(char * s)598638175c6Sderaadt getday(char *s)
599b1fc8d4cSmillert {
600c0932ef1Smpech 	char **p;
601b1fc8d4cSmillert 	struct fixs *n;
602b1fc8d4cSmillert 
603b1fc8d4cSmillert 	for (n = fndays; n->name; ++n)
604b1fc8d4cSmillert 		if (!strncasecmp(s, n->name, n->len))
605b1fc8d4cSmillert 			return ((n - fndays) + 1);
606b1fc8d4cSmillert 	for (n = ndays; n->name; ++n)
607b1fc8d4cSmillert 		if (!strncasecmp(s, n->name, n->len))
608b1fc8d4cSmillert 			return ((n - ndays) + 1);
609b1fc8d4cSmillert 	for (p = days; *p; ++p)
610b1fc8d4cSmillert 		if (!strncasecmp(s, *p, 3))
611b1fc8d4cSmillert 			return ((p - days) + 1);
612b1fc8d4cSmillert 	return (0);
613b1fc8d4cSmillert }
614b1fc8d4cSmillert 
615b1fc8d4cSmillert /* return offset for variable weekdays
616b1fc8d4cSmillert  * -1 -> last weekday in month
617b1fc8d4cSmillert  * +1 -> first weekday in month
618b1fc8d4cSmillert  * ... etc ...
619b1fc8d4cSmillert  */
620b1fc8d4cSmillert int
getdayvar(char * s)621638175c6Sderaadt getdayvar(char *s)
622b1fc8d4cSmillert {
623c0932ef1Smpech 	int offset;
624b1fc8d4cSmillert 
625b1fc8d4cSmillert 
626b1fc8d4cSmillert 	offset = strlen(s);
627b1fc8d4cSmillert 
628b1fc8d4cSmillert 	/* Sun+1 or Wednesday-2
629b1fc8d4cSmillert 	 *    ^              ^   */
630b1fc8d4cSmillert 
631b1fc8d4cSmillert 	/* printf ("x: %s %s %d\n", s, s + offset - 2, offset); */
632b1fc8d4cSmillert 	switch(*(s + offset - 2)) {
633b1fc8d4cSmillert 	case '-':
634b1fc8d4cSmillert 	case '+':
6358a44d90fSpjanzen 	    return(atoi(s + offset - 2));
636b1fc8d4cSmillert 	    break;
637b1fc8d4cSmillert 	}
638b1fc8d4cSmillert 
639b1fc8d4cSmillert 	/*
640b1fc8d4cSmillert 	 * some aliases: last, first, second, third, fourth
641b1fc8d4cSmillert 	 */
642b1fc8d4cSmillert 
643b1fc8d4cSmillert 	/* last */
644b1fc8d4cSmillert 	if      (offset > 4 && !strcasecmp(s + offset - 4, "last"))
645b1fc8d4cSmillert 	    return(-1);
646b1fc8d4cSmillert 	else if (offset > 5 && !strcasecmp(s + offset - 5, "first"))
647b1fc8d4cSmillert 	    return(+1);
648b1fc8d4cSmillert 	else if (offset > 6 && !strcasecmp(s + offset - 6, "second"))
649b1fc8d4cSmillert 	    return(+2);
650b1fc8d4cSmillert 	else if (offset > 5 && !strcasecmp(s + offset - 5, "third"))
651b1fc8d4cSmillert 	    return(+3);
652b1fc8d4cSmillert 	else if (offset > 6 && !strcasecmp(s + offset - 6, "fourth"))
653b1fc8d4cSmillert 	    return(+4);
654b1fc8d4cSmillert 
655b1fc8d4cSmillert 	/* no offset detected */
656b1fc8d4cSmillert 	return(0);
657b1fc8d4cSmillert }
6588a44d90fSpjanzen 
6598a44d90fSpjanzen 
6608a44d90fSpjanzen int
foy(int year)661638175c6Sderaadt foy(int year)
6628a44d90fSpjanzen {
6638a44d90fSpjanzen 	/* 0-6; what weekday Jan 1 is */
6648a44d90fSpjanzen 	year--;
6658a44d90fSpjanzen 	return ((1 - year/100 + year/400 + (int)(365.25 * year)) % 7);
6668a44d90fSpjanzen }
6678a44d90fSpjanzen 
6688a44d90fSpjanzen 
6698a44d90fSpjanzen 
6708a44d90fSpjanzen void
variable_weekday(int * day,int month,int year)671638175c6Sderaadt variable_weekday(int *day, int month, int year)
6728a44d90fSpjanzen {
6738a44d90fSpjanzen 	int v1, v2;
6748a44d90fSpjanzen 	int *cumdays;
6758a44d90fSpjanzen 	int day1;
6768a44d90fSpjanzen 
6778a44d90fSpjanzen 	if (isleap(year))
6788a44d90fSpjanzen 		cumdays = daytab[1];
6798a44d90fSpjanzen 	else
6808a44d90fSpjanzen 		cumdays = daytab[0];
6818a44d90fSpjanzen 	day1 = foy(year);
6828a44d90fSpjanzen 	/* negative offset; last, -4 .. -1 */
6838a44d90fSpjanzen 	if (*day < 0) {
6848a44d90fSpjanzen 		v1 = *day/10 - 1;          /* offset -4 ... -1 */
6858a44d90fSpjanzen 		*day = 10 + (*day % 10);    /* day 1 ... 7 */
6868a44d90fSpjanzen 
6878a44d90fSpjanzen 		/* which weekday the end of the month is (1-7) */
6888a44d90fSpjanzen 		v2 = (cumdays[month + 1] + day1) % 7 + 1;
6898a44d90fSpjanzen 
6908a44d90fSpjanzen 		/* and subtract enough days */
6918a44d90fSpjanzen 		*day = cumdays[month + 1] - cumdays[month] +
6928a44d90fSpjanzen 		    (v1 + 1) * 7 - (v2 - *day + 7) % 7;
6938a44d90fSpjanzen #if DEBUG
6948a44d90fSpjanzen 		fprintf(stderr, "\nMonth %d ends on weekday %d\n", month, v2);
6958a44d90fSpjanzen #endif
6968a44d90fSpjanzen 	}
6978a44d90fSpjanzen 
6988a44d90fSpjanzen 	/* first, second ... +1 ... +5 */
6998a44d90fSpjanzen 	else {
7008a44d90fSpjanzen 		v1 = *day/10;        /* offset */
7018a44d90fSpjanzen 		*day = *day % 10;
7028a44d90fSpjanzen 
7038a44d90fSpjanzen 		/* which weekday the first of the month is (1-7) */
7048a44d90fSpjanzen 		v2 = (cumdays[month] + 1 + day1) % 7 + 1;
7058a44d90fSpjanzen 
7068a44d90fSpjanzen 		/* and add enough days */
7078a44d90fSpjanzen 		*day = 1 + (v1 - 1) * 7 + (*day - v2 + 7) % 7;
7088a44d90fSpjanzen #if DEBUG
7098a44d90fSpjanzen 		fprintf(stderr, "\nMonth %d starts on weekday %d\n", month, v2);
7108a44d90fSpjanzen #endif
7118a44d90fSpjanzen 	}
7128a44d90fSpjanzen }
713