xref: /freebsd/usr.sbin/pw/psdate.c (revision f6eae381a5a60996f7abb1084805e6a62afe11bb)
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_datesub(char const * str, struct tm *t)
102 {
103 	struct tm	 tm;
104 	locale_t	 l;
105 	int		 i;
106 	char		*ret;
107 	const char	*valid_formats[] = {
108 		"%d-%b-%y",
109 		"%d-%b-%Y",
110 		"%d-%m-%y",
111 		"%d-%m-%Y",
112 		"%H:%M %d-%b-%y",
113 		"%H:%M %d-%b-%Y",
114 		"%H:%M %d-%m-%y",
115 		"%H:%M %d-%m-%Y",
116 		"%H:%M:%S %d-%b-%y",
117 		"%H:%M:%S %d-%b-%Y",
118 		"%H:%M:%S %d-%m-%y",
119 		"%H:%M:%S %d-%m-%Y",
120 		"%d-%b-%y %H:%M",
121 		"%d-%b-%Y %H:%M",
122 		"%d-%m-%y %H:%M",
123 		"%d-%m-%Y %H:%M",
124 		"%d-%b-%y %H:%M:%S",
125 		"%d-%b-%Y %H:%M:%S",
126 		"%d-%m-%y %H:%M:%S",
127 		"%d-%m-%Y %H:%M:%S",
128 		"%H:%M\t%d-%b-%y",
129 		"%H:%M\t%d-%b-%Y",
130 		"%H:%M\t%d-%m-%y",
131 		"%H:%M\t%d-%m-%Y",
132 		"%H:%M\t%S %d-%b-%y",
133 		"%H:%M\t%S %d-%b-%Y",
134 		"%H:%M\t%S %d-%m-%y",
135 		"%H:%M\t%S %d-%m-%Y",
136 		"%d-%b-%y\t%H:%M",
137 		"%d-%b-%Y\t%H:%M",
138 		"%d-%m-%y\t%H:%M",
139 		"%d-%m-%Y\t%H:%M",
140 		"%d-%b-%y\t%H:%M:%S",
141 		"%d-%b-%Y\t%H:%M:%S",
142 		"%d-%m-%y\t%H:%M:%S",
143 		"%d-%m-%Y\t%H:%M:%S",
144 		NULL,
145 	};
146 
147 	l = newlocale(LC_ALL_MASK, "C", NULL);
148 
149 	memset(&tm, 0, sizeof(tm));
150 	for (i=0; valid_formats[i] != NULL; i++) {
151 		ret = strptime_l(str, valid_formats[i], &tm, l);
152 		if (ret && *ret == '\0') {
153 			t->tm_mday = tm.tm_mday;
154 			t->tm_mon = tm.tm_mon;
155 			t->tm_year = tm.tm_year;
156 			t->tm_hour = tm.tm_hour;
157 			t->tm_min = tm.tm_min;
158 			t->tm_sec = tm.tm_sec;
159 			freelocale(l);
160 			return;
161 		}
162 	}
163 
164 	freelocale(l);
165 
166 	errx(EXIT_FAILURE, "Invalid date");
167 }
168 
169 
170 /*-
171  * Parse time must be flexible, it handles the following formats:
172  * nnnnnnnnnnn		UNIX timestamp (all numeric), 0 = now
173  * 0xnnnnnnnn		UNIX timestamp in hexadecimal
174  * 0nnnnnnnnn		UNIX timestamp in octal
175  * 0			Given time
176  * +nnnn[smhdwoy]	Given time + nnnn hours, mins, days, weeks, months or years
177  * -nnnn[smhdwoy]	Given time - nnnn hours, mins, days, weeks, months or years
178  * dd[ ./-]mmm[ ./-]yy	Date }
179  * hh:mm:ss		Time } May be combined
180  */
181 
182 time_t
183 parse_date(time_t dt, char const * str)
184 {
185 	char           *p;
186 	int             i;
187 	long            val;
188 	struct tm      *T;
189 
190 	if (dt == 0)
191 		dt = time(NULL);
192 
193 	while (*str && isspace((unsigned char)*str))
194 		++str;
195 
196 	if (numerics(str)) {
197 		dt = strtol(str, &p, 0);
198 	} else if (*str == '+' || *str == '-') {
199 		val = strtol(str, &p, 0);
200 		switch (*p) {
201 		case 'h':
202 		case 'H':	/* hours */
203 			dt += (val * 3600L);
204 			break;
205 		case '\0':
206 		case 'm':
207 		case 'M':	/* minutes */
208 			dt += (val * 60L);
209 			break;
210 		case 's':
211 		case 'S':	/* seconds */
212 			dt += val;
213 			break;
214 		case 'd':
215 		case 'D':	/* days */
216 			dt += (val * 86400L);
217 			break;
218 		case 'w':
219 		case 'W':	/* weeks */
220 			dt += (val * 604800L);
221 			break;
222 		case 'o':
223 		case 'O':	/* months */
224 			T = localtime(&dt);
225 			T->tm_mon += (int) val;
226 			i = T->tm_mday;
227 			goto fixday;
228 		case 'y':
229 		case 'Y':	/* years */
230 			T = localtime(&dt);
231 			T->tm_year += (int) val;
232 			i = T->tm_mday;
233 	fixday:
234 			dt = mktime(T);
235 			T = localtime(&dt);
236 			if (T->tm_mday != i) {
237 				T->tm_mday = 1;
238 				dt = mktime(T);
239 				dt -= (time_t) 86400L;
240 			}
241 		default:	/* unknown */
242 			break;	/* leave untouched */
243 		}
244 	} else {
245 		char           *q, tmp[64];
246 
247 		/*
248 		 * Skip past any weekday prefix
249 		 */
250 		weekday(&str);
251 		strlcpy(tmp, str, sizeof(tmp));
252 		str = tmp;
253 		T = localtime(&dt);
254 
255 		/*
256 		 * See if we can break off any timezone
257 		 */
258 		while ((q = strrchr(tmp, ' ')) != NULL) {
259 			if (strchr("(+-", q[1]) != NULL)
260 				*q = '\0';
261 			else {
262 				int             j = 1;
263 
264 				while (q[j] && isupper((unsigned char)q[j]))
265 					++j;
266 				if (q[j] == '\0')
267 					*q = '\0';
268 				else
269 					break;
270 			}
271 		}
272 
273 		parse_datesub(tmp, T);
274 		dt = mktime(T);
275 	}
276 	return dt;
277 }
278