xref: /freebsd/usr.sbin/pw/psdate.c (revision 7453645f2a9411a3f9d982b768bcc323f41cf906)
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 <ctype.h>
33 #include <err.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <xlocale.h>
37 
38 #include "psdate.h"
39 
40 
41 static int
42 numerics(char const * str)
43 {
44 	int             rc = isdigit((unsigned char)*str);
45 
46 	if (rc)
47 		while (isdigit((unsigned char)*str) || *str == 'x')
48 			++str;
49 	return rc && !*str;
50 }
51 
52 static int
53 aindex(char const * arr[], char const ** str, int len)
54 {
55 	int             l, i;
56 	char            mystr[32];
57 
58 	mystr[len] = '\0';
59 	l = strlen(strncpy(mystr, *str, len));
60 	for (i = 0; i < l; i++)
61 		mystr[i] = (char) tolower((unsigned char)mystr[i]);
62 	for (i = 0; arr[i] && strcmp(mystr, arr[i]) != 0; i++);
63 	if (arr[i] == NULL)
64 		i = -1;
65 	else {			/* Skip past it */
66 		while (**str && isalpha((unsigned char)**str))
67 			++(*str);
68 		/* And any following whitespace */
69 		while (**str && (**str == ',' || isspace((unsigned char)**str)))
70 			++(*str);
71 	}			/* Return index */
72 	return i;
73 }
74 
75 static int
76 weekday(char const ** str)
77 {
78 	static char const *days[] =
79 	{"sun", "mon", "tue", "wed", "thu", "fri", "sat", NULL};
80 
81 	return aindex(days, str, 3);
82 }
83 
84 static void
85 parse_datesub(char const * str, struct tm *t)
86 {
87 	struct tm	 tm;
88 	locale_t	 l;
89 	int		 i;
90 	char		*ret;
91 	const char	*valid_formats[] = {
92 		"%d-%b-%y",
93 		"%d-%b-%Y",
94 		"%d-%m-%y",
95 		"%d-%m-%Y",
96 		"%H:%M %d-%b-%y",
97 		"%H:%M %d-%b-%Y",
98 		"%H:%M %d-%m-%y",
99 		"%H:%M %d-%m-%Y",
100 		"%H:%M:%S %d-%b-%y",
101 		"%H:%M:%S %d-%b-%Y",
102 		"%H:%M:%S %d-%m-%y",
103 		"%H:%M:%S %d-%m-%Y",
104 		"%d-%b-%y %H:%M",
105 		"%d-%b-%Y %H:%M",
106 		"%d-%m-%y %H:%M",
107 		"%d-%m-%Y %H:%M",
108 		"%d-%b-%y %H:%M:%S",
109 		"%d-%b-%Y %H:%M:%S",
110 		"%d-%m-%y %H:%M:%S",
111 		"%d-%m-%Y %H:%M:%S",
112 		"%H:%M\t%d-%b-%y",
113 		"%H:%M\t%d-%b-%Y",
114 		"%H:%M\t%d-%m-%y",
115 		"%H:%M\t%d-%m-%Y",
116 		"%H:%M\t%S %d-%b-%y",
117 		"%H:%M\t%S %d-%b-%Y",
118 		"%H:%M\t%S %d-%m-%y",
119 		"%H:%M\t%S %d-%m-%Y",
120 		"%d-%b-%y\t%H:%M",
121 		"%d-%b-%Y\t%H:%M",
122 		"%d-%m-%y\t%H:%M",
123 		"%d-%m-%Y\t%H:%M",
124 		"%d-%b-%y\t%H:%M:%S",
125 		"%d-%b-%Y\t%H:%M:%S",
126 		"%d-%m-%y\t%H:%M:%S",
127 		"%d-%m-%Y\t%H:%M:%S",
128 		NULL,
129 	};
130 
131 	l = newlocale(LC_ALL_MASK, "C", NULL);
132 
133 	memset(&tm, 0, sizeof(tm));
134 	for (i=0; valid_formats[i] != NULL; i++) {
135 		ret = strptime_l(str, valid_formats[i], &tm, l);
136 		if (ret && *ret == '\0') {
137 			t->tm_mday = tm.tm_mday;
138 			t->tm_mon = tm.tm_mon;
139 			t->tm_year = tm.tm_year;
140 			t->tm_hour = tm.tm_hour;
141 			t->tm_min = tm.tm_min;
142 			t->tm_sec = tm.tm_sec;
143 			freelocale(l);
144 			return;
145 		}
146 	}
147 
148 	freelocale(l);
149 
150 	errx(EXIT_FAILURE, "Invalid date");
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 		strlcpy(tmp, str, sizeof(tmp));
236 		str = tmp;
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 		parse_datesub(tmp, T);
258 		dt = mktime(T);
259 	}
260 	return dt;
261 }
262