xref: /dragonfly/usr.sbin/pw/psdate.c (revision c89a6c1b)
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  * $FreeBSD: src/usr.sbin/pw/psdate.c,v 1.6.2.1 2000/06/28 19:19:04 ache Exp $
27  * $DragonFly: src/usr.sbin/pw/psdate.c,v 1.2 2003/06/17 04:30:01 dillon Exp $
28  */
29 
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <ctype.h>
34 
35 #include "psdate.h"
36 
37 
38 static int
39 a2i(char const ** str)
40 {
41 	int             i = 0;
42 	char const     *s = *str;
43 
44 	if (isdigit((unsigned char)*s)) {
45 		i = atoi(s);
46 		while (isdigit((unsigned char)*s))
47 			++s;
48 		*str = s;
49 	}
50 	return i;
51 }
52 
53 static int
54 numerics(char const * str)
55 {
56 	int             rc = isdigit((unsigned char)*str);
57 
58 	if (rc)
59 		while (isdigit((unsigned char)*str) || *str == 'x')
60 			++str;
61 	return rc && !*str;
62 }
63 
64 static int
65 aindex(char const * arr[], char const ** str, int len)
66 {
67 	int             l, i;
68 	char            mystr[32];
69 
70 	mystr[len] = '\0';
71 	l = strlen(strncpy(mystr, *str, len));
72 	for (i = 0; i < l; i++)
73 		mystr[i] = (char) tolower((unsigned char)mystr[i]);
74 	for (i = 0; arr[i] && strcmp(mystr, arr[i]) != 0; i++);
75 	if (arr[i] == NULL)
76 		i = -1;
77 	else {			/* Skip past it */
78 		while (**str && isalpha((unsigned char)**str))
79 			++(*str);
80 		/* And any following whitespace */
81 		while (**str && (**str == ',' || isspace((unsigned char)**str)))
82 			++(*str);
83 	}			/* Return index */
84 	return i;
85 }
86 
87 static int
88 weekday(char const ** str)
89 {
90 	static char const *days[] =
91 	{"sun", "mon", "tue", "wed", "thu", "fri", "sat", NULL};
92 
93 	return aindex(days, str, 3);
94 }
95 
96 static int
97 month(char const ** str)
98 {
99 	static char const *months[] =
100 	{"jan", "feb", "mar", "apr", "may", "jun", "jul",
101 	"aug", "sep", "oct", "nov", "dec", NULL};
102 
103 	return aindex(months, str, 3);
104 }
105 
106 static void
107 parse_time(char const * str, int *hour, int *min, int *sec)
108 {
109 	*hour = a2i(&str);
110 	if ((str = strchr(str, ':')) == NULL)
111 		*min = *sec = 0;
112 	else {
113 		++str;
114 		*min = a2i(&str);
115 		*sec = ((str = strchr(str, ':')) == NULL) ? 0 : atoi(++str);
116 	}
117 }
118 
119 
120 static void
121 parse_datesub(char const * str, int *day, int *mon, int *year)
122 {
123 	int             i;
124 
125 	static char const nchrs[] = "0123456789 \t,/-.";
126 
127 	if ((i = month(&str)) != -1) {
128 		*mon = i;
129 		if ((i = a2i(&str)) != 0)
130 			*day = i;
131 	} else if ((i = a2i(&str)) != 0) {
132 		*day = i;
133 		while (*str && strchr(nchrs + 10, *str) != NULL)
134 			++str;
135 		if ((i = month(&str)) != -1)
136 			*mon = i;
137 		else if ((i = a2i(&str)) != 0)
138 			*mon = i - 1;
139 	} else
140 		return;
141 
142 	while (*str && strchr(nchrs + 10, *str) != NULL)
143 		++str;
144 	if (isdigit((unsigned char)*str)) {
145 		*year = atoi(str);
146 		if (*year > 1900)
147 			*year -= 1900;
148 		else if (*year < 32)
149 			*year += 100;
150 	}
151 }
152 
153 
154 /*-
155  * Parse time must be flexible, it handles the following formats:
156  * nnnnnnnnnnn		UNIX timestamp (all numeric), 0 = now
157  * 0xnnnnnnnn		UNIX timestamp in hexadecimal
158  * 0nnnnnnnnn		UNIX timestamp in octal
159  * 0			Given time
160  * +nnnn[smhdwoy]	Given time + nnnn hours, mins, days, weeks, months or years
161  * -nnnn[smhdwoy]	Given time - nnnn hours, mins, days, weeks, months or years
162  * dd[ ./-]mmm[ ./-]yy	Date }
163  * hh:mm:ss		Time } May be combined
164  */
165 
166 time_t
167 parse_date(time_t dt, char const * str)
168 {
169 	char           *p;
170 	int             i;
171 	long            val;
172 	struct tm      *T;
173 
174 	if (dt == 0)
175 		dt = time(NULL);
176 
177 	while (*str && isspace((unsigned char)*str))
178 		++str;
179 
180 	if (numerics(str)) {
181 		dt = strtol(str, &p, 0);
182 	} else if (*str == '+' || *str == '-') {
183 		val = strtol(str, &p, 0);
184 		switch (*p) {
185 		case 'h':
186 		case 'H':	/* hours */
187 			dt += (val * 3600L);
188 			break;
189 		case '\0':
190 		case 'm':
191 		case 'M':	/* minutes */
192 			dt += (val * 60L);
193 			break;
194 		case 's':
195 		case 'S':	/* seconds */
196 			dt += val;
197 			break;
198 		case 'd':
199 		case 'D':	/* days */
200 			dt += (val * 86400L);
201 			break;
202 		case 'w':
203 		case 'W':	/* weeks */
204 			dt += (val * 604800L);
205 			break;
206 		case 'o':
207 		case 'O':	/* months */
208 			T = localtime(&dt);
209 			T->tm_mon += (int) val;
210 			i = T->tm_mday;
211 			goto fixday;
212 		case 'y':
213 		case 'Y':	/* years */
214 			T = localtime(&dt);
215 			T->tm_year += (int) val;
216 			i = T->tm_mday;
217 	fixday:
218 			dt = mktime(T);
219 			T = localtime(&dt);
220 			if (T->tm_mday != i) {
221 				T->tm_mday = 1;
222 				dt = mktime(T);
223 				dt -= (time_t) 86400L;
224 			}
225 		default:	/* unknown */
226 			break;	/* leave untouched */
227 		}
228 	} else {
229 		char           *q, tmp[64];
230 
231 		/*
232 		 * Skip past any weekday prefix
233 		 */
234 		weekday(&str);
235 		str = strncpy(tmp, str, sizeof tmp - 1);
236 		tmp[sizeof tmp - 1] = '\0';
237 		T = localtime(&dt);
238 
239 		/*
240 		 * See if we can break off any timezone
241 		 */
242 		while ((q = strrchr(tmp, ' ')) != NULL) {
243 			if (strchr("(+-", q[1]) != NULL)
244 				*q = '\0';
245 			else {
246 				int             j = 1;
247 
248 				while (q[j] && isupper((unsigned char)q[j]))
249 					++j;
250 				if (q[j] == '\0')
251 					*q = '\0';
252 				else
253 					break;
254 			}
255 		}
256 
257 		/*
258 		 * See if there is a time hh:mm[:ss]
259 		 */
260 		if ((p = strchr(tmp, ':')) == NULL) {
261 
262 			/*
263 			 * No time string involved
264 			 */
265 			T->tm_hour = T->tm_min = T->tm_sec = 0;
266 			parse_datesub(tmp, &T->tm_mday, &T->tm_mon, &T->tm_year);
267 		} else {
268 			char            datestr[64], timestr[64];
269 
270 			/*
271 			 * Let's chip off the time string
272 			 */
273 			if ((q = strpbrk(p, " \t")) != NULL) {	/* Time first? */
274 				int             l = q - str;
275 
276 				strncpy(timestr, str, l);
277 				timestr[l] = '\0';
278 				strncpy(datestr, q + 1, sizeof datestr);
279 				datestr[sizeof datestr - 1] = '\0';
280 				parse_time(timestr, &T->tm_hour, &T->tm_min, &T->tm_sec);
281 				parse_datesub(datestr, &T->tm_mday, &T->tm_mon, &T->tm_year);
282 			} else if ((q = strrchr(tmp, ' ')) != NULL) {	/* Time last */
283 				int             l = q - tmp;
284 
285 				strncpy(timestr, q + 1, sizeof timestr);
286 				timestr[sizeof timestr - 1] = '\0';
287 				strncpy(datestr, tmp, l);
288 				datestr[l] = '\0';
289 			} else	/* Bail out */
290 				return dt;
291 			parse_time(timestr, &T->tm_hour, &T->tm_min, &T->tm_sec);
292 			parse_datesub(datestr, &T->tm_mday, &T->tm_mon, &T->tm_year);
293 		}
294 		dt = mktime(T);
295 	}
296 	return dt;
297 }
298