xref: /freebsd/usr.sbin/pw/psdate.c (revision 330e423084cb6e54d591a149120ed2ec36fec40d)
1 /*-
2  * Copyright (C) 1996
3  *	David L. Nugent.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26 
27 #ifndef lint
28 static const char rcsid[] =
29   "$FreeBSD$";
30 #endif /* not lint */
31 
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <ctype.h>
36 #include <xlocale.h>
37 #include <err.h>
38 
39 #include "psdate.h"
40 
41 
42 static int
43 a2i(char const ** str)
44 {
45 	int             i = 0;
46 	char const     *s = *str;
47 
48 	if (isdigit((unsigned char)*s)) {
49 		i = atoi(s);
50 		while (isdigit((unsigned char)*s))
51 			++s;
52 		*str = s;
53 	}
54 	return i;
55 }
56 
57 static int
58 numerics(char const * str)
59 {
60 	int             rc = isdigit((unsigned char)*str);
61 
62 	if (rc)
63 		while (isdigit((unsigned char)*str) || *str == 'x')
64 			++str;
65 	return rc && !*str;
66 }
67 
68 static int
69 aindex(char const * arr[], char const ** str, int len)
70 {
71 	int             l, i;
72 	char            mystr[32];
73 
74 	mystr[len] = '\0';
75 	l = strlen(strncpy(mystr, *str, len));
76 	for (i = 0; i < l; i++)
77 		mystr[i] = (char) tolower((unsigned char)mystr[i]);
78 	for (i = 0; arr[i] && strcmp(mystr, arr[i]) != 0; i++);
79 	if (arr[i] == NULL)
80 		i = -1;
81 	else {			/* Skip past it */
82 		while (**str && isalpha((unsigned char)**str))
83 			++(*str);
84 		/* And any following whitespace */
85 		while (**str && (**str == ',' || isspace((unsigned char)**str)))
86 			++(*str);
87 	}			/* Return index */
88 	return i;
89 }
90 
91 static int
92 weekday(char const ** str)
93 {
94 	static char const *days[] =
95 	{"sun", "mon", "tue", "wed", "thu", "fri", "sat", NULL};
96 
97 	return aindex(days, str, 3);
98 }
99 
100 static void
101 parse_time(char const * str, int *hour, int *min, int *sec)
102 {
103 	*hour = a2i(&str);
104 	if ((str = strchr(str, ':')) == NULL)
105 		*min = *sec = 0;
106 	else {
107 		++str;
108 		*min = a2i(&str);
109 		*sec = ((str = strchr(str, ':')) == NULL) ? 0 : atoi(++str);
110 	}
111 }
112 
113 
114 static void
115 parse_datesub(char const * str, int *day, int *mon, int *year)
116 {
117 	struct tm	 tm;
118 	locale_t	 l;
119 	int		 i;
120 	char		*ret;
121 	const char	*valid_formats[] = {
122 		"%d-%b-%y",
123 		"%d-%b-%Y",
124 		"%d-%m-%y",
125 		"%d-%m-%Y",
126 		NULL,
127 	};
128 
129 	l = newlocale(LC_ALL_MASK, "C", NULL);
130 
131 	memset(&tm, 0, sizeof(tm));
132 	for (i=0; valid_formats[i] != NULL; i++) {
133 		ret = strptime_l(str, valid_formats[i], &tm, l);
134 		if (ret && *ret == '\0') {
135 			*day = tm.tm_mday;
136 			*mon = tm.tm_mon;
137 			*year = tm.tm_year;
138 			freelocale(l);
139 			return;
140 		}
141 	}
142 
143 	freelocale(l);
144 
145 	errx(EXIT_FAILURE, "Invalid date");
146 }
147 
148 
149 /*-
150  * Parse time must be flexible, it handles the following formats:
151  * nnnnnnnnnnn		UNIX timestamp (all numeric), 0 = now
152  * 0xnnnnnnnn		UNIX timestamp in hexadecimal
153  * 0nnnnnnnnn		UNIX timestamp in octal
154  * 0			Given time
155  * +nnnn[smhdwoy]	Given time + nnnn hours, mins, days, weeks, months or years
156  * -nnnn[smhdwoy]	Given time - nnnn hours, mins, days, weeks, months or years
157  * dd[ ./-]mmm[ ./-]yy	Date }
158  * hh:mm:ss		Time } May be combined
159  */
160 
161 time_t
162 parse_date(time_t dt, char const * str)
163 {
164 	char           *p;
165 	int             i;
166 	long            val;
167 	struct tm      *T;
168 
169 	if (dt == 0)
170 		dt = time(NULL);
171 
172 	while (*str && isspace((unsigned char)*str))
173 		++str;
174 
175 	if (numerics(str)) {
176 		dt = strtol(str, &p, 0);
177 	} else if (*str == '+' || *str == '-') {
178 		val = strtol(str, &p, 0);
179 		switch (*p) {
180 		case 'h':
181 		case 'H':	/* hours */
182 			dt += (val * 3600L);
183 			break;
184 		case '\0':
185 		case 'm':
186 		case 'M':	/* minutes */
187 			dt += (val * 60L);
188 			break;
189 		case 's':
190 		case 'S':	/* seconds */
191 			dt += val;
192 			break;
193 		case 'd':
194 		case 'D':	/* days */
195 			dt += (val * 86400L);
196 			break;
197 		case 'w':
198 		case 'W':	/* weeks */
199 			dt += (val * 604800L);
200 			break;
201 		case 'o':
202 		case 'O':	/* months */
203 			T = localtime(&dt);
204 			T->tm_mon += (int) val;
205 			i = T->tm_mday;
206 			goto fixday;
207 		case 'y':
208 		case 'Y':	/* years */
209 			T = localtime(&dt);
210 			T->tm_year += (int) val;
211 			i = T->tm_mday;
212 	fixday:
213 			dt = mktime(T);
214 			T = localtime(&dt);
215 			if (T->tm_mday != i) {
216 				T->tm_mday = 1;
217 				dt = mktime(T);
218 				dt -= (time_t) 86400L;
219 			}
220 		default:	/* unknown */
221 			break;	/* leave untouched */
222 		}
223 	} else {
224 		char           *q, tmp[64];
225 
226 		/*
227 		 * Skip past any weekday prefix
228 		 */
229 		weekday(&str);
230 		strlcpy(tmp, str, sizeof(tmp));
231 		str = tmp;
232 		T = localtime(&dt);
233 
234 		/*
235 		 * See if we can break off any timezone
236 		 */
237 		while ((q = strrchr(tmp, ' ')) != NULL) {
238 			if (strchr("(+-", q[1]) != NULL)
239 				*q = '\0';
240 			else {
241 				int             j = 1;
242 
243 				while (q[j] && isupper((unsigned char)q[j]))
244 					++j;
245 				if (q[j] == '\0')
246 					*q = '\0';
247 				else
248 					break;
249 			}
250 		}
251 
252 		/*
253 		 * See if there is a time hh:mm[:ss]
254 		 */
255 		if ((p = strchr(tmp, ':')) == NULL) {
256 
257 			/*
258 			 * No time string involved
259 			 */
260 			T->tm_hour = T->tm_min = T->tm_sec = 0;
261 			parse_datesub(tmp, &T->tm_mday, &T->tm_mon, &T->tm_year);
262 		} else {
263 			char            datestr[64], timestr[64];
264 
265 			/*
266 			 * Let's chip off the time string
267 			 */
268 			if ((q = strpbrk(p, " \t")) != NULL) {	/* Time first? */
269 				int             l = q - str;
270 
271 				strlcpy(timestr, str, l + 1);
272 				strlcpy(datestr, q + 1, sizeof(datestr));
273 				parse_time(timestr, &T->tm_hour, &T->tm_min, &T->tm_sec);
274 				parse_datesub(datestr, &T->tm_mday, &T->tm_mon, &T->tm_year);
275 			} else if ((q = strrchr(tmp, ' ')) != NULL) {	/* Time last */
276 				int             l = q - tmp;
277 
278 				strlcpy(timestr, q + 1, sizeof(timestr));
279 				strlcpy(datestr, tmp, l + 1);
280 			} else	/* Bail out */
281 				return dt;
282 			parse_time(timestr, &T->tm_hour, &T->tm_min, &T->tm_sec);
283 			parse_datesub(datestr, &T->tm_mday, &T->tm_mon, &T->tm_year);
284 		}
285 		dt = mktime(T);
286 	}
287 	return dt;
288 }
289