xref: /freebsd/usr.sbin/pw/psdate.c (revision f4b37ed0f8b307b1f3f0f630ca725d68f1dff30d)
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 numerics(char const * str)
44 {
45 	int             rc = isdigit((unsigned char)*str);
46 
47 	if (rc)
48 		while (isdigit((unsigned char)*str) || *str == 'x')
49 			++str;
50 	return rc && !*str;
51 }
52 
53 static int
54 aindex(char const * arr[], char const ** str, int len)
55 {
56 	int             l, i;
57 	char            mystr[32];
58 
59 	mystr[len] = '\0';
60 	l = strlen(strncpy(mystr, *str, len));
61 	for (i = 0; i < l; i++)
62 		mystr[i] = (char) tolower((unsigned char)mystr[i]);
63 	for (i = 0; arr[i] && strcmp(mystr, arr[i]) != 0; i++);
64 	if (arr[i] == NULL)
65 		i = -1;
66 	else {			/* Skip past it */
67 		while (**str && isalpha((unsigned char)**str))
68 			++(*str);
69 		/* And any following whitespace */
70 		while (**str && (**str == ',' || isspace((unsigned char)**str)))
71 			++(*str);
72 	}			/* Return index */
73 	return i;
74 }
75 
76 static int
77 weekday(char const ** str)
78 {
79 	static char const *days[] =
80 	{"sun", "mon", "tue", "wed", "thu", "fri", "sat", NULL};
81 
82 	return aindex(days, str, 3);
83 }
84 
85 static void
86 parse_datesub(char const * str, struct tm *t)
87 {
88 	struct tm	 tm;
89 	locale_t	 l;
90 	int		 i;
91 	char		*ret;
92 	const char	*valid_formats[] = {
93 		"%d-%b-%y",
94 		"%d-%b-%Y",
95 		"%d-%m-%y",
96 		"%d-%m-%Y",
97 		"%H:%M %d-%b-%y",
98 		"%H:%M %d-%b-%Y",
99 		"%H:%M %d-%m-%y",
100 		"%H:%M %d-%m-%Y",
101 		"%H:%M:%S %d-%b-%y",
102 		"%H:%M:%S %d-%b-%Y",
103 		"%H:%M:%S %d-%m-%y",
104 		"%H:%M:%S %d-%m-%Y",
105 		"%d-%b-%y %H:%M",
106 		"%d-%b-%Y %H:%M",
107 		"%d-%m-%y %H:%M",
108 		"%d-%m-%Y %H:%M",
109 		"%d-%b-%y %H:%M:%S",
110 		"%d-%b-%Y %H:%M:%S",
111 		"%d-%m-%y %H:%M:%S",
112 		"%d-%m-%Y %H:%M:%S",
113 		"%H:%M\t%d-%b-%y",
114 		"%H:%M\t%d-%b-%Y",
115 		"%H:%M\t%d-%m-%y",
116 		"%H:%M\t%d-%m-%Y",
117 		"%H:%M\t%S %d-%b-%y",
118 		"%H:%M\t%S %d-%b-%Y",
119 		"%H:%M\t%S %d-%m-%y",
120 		"%H:%M\t%S %d-%m-%Y",
121 		"%d-%b-%y\t%H:%M",
122 		"%d-%b-%Y\t%H:%M",
123 		"%d-%m-%y\t%H:%M",
124 		"%d-%m-%Y\t%H:%M",
125 		"%d-%b-%y\t%H:%M:%S",
126 		"%d-%b-%Y\t%H:%M:%S",
127 		"%d-%m-%y\t%H:%M:%S",
128 		"%d-%m-%Y\t%H:%M:%S",
129 		NULL,
130 	};
131 
132 	l = newlocale(LC_ALL_MASK, "C", NULL);
133 
134 	memset(&tm, 0, sizeof(tm));
135 	for (i=0; valid_formats[i] != NULL; i++) {
136 		ret = strptime_l(str, valid_formats[i], &tm, l);
137 		if (ret && *ret == '\0') {
138 			t->tm_mday = tm.tm_mday;
139 			t->tm_mon = tm.tm_mon;
140 			t->tm_year = tm.tm_year;
141 			t->tm_hour = tm.tm_hour;
142 			t->tm_min = tm.tm_min;
143 			t->tm_sec = tm.tm_sec;
144 			freelocale(l);
145 			return;
146 		}
147 	}
148 
149 	freelocale(l);
150 
151 	errx(EXIT_FAILURE, "Invalid date");
152 }
153 
154 
155 /*-
156  * Parse time must be flexible, it handles the following formats:
157  * nnnnnnnnnnn		UNIX timestamp (all numeric), 0 = now
158  * 0xnnnnnnnn		UNIX timestamp in hexadecimal
159  * 0nnnnnnnnn		UNIX timestamp in octal
160  * 0			Given time
161  * +nnnn[smhdwoy]	Given time + nnnn hours, mins, days, weeks, months or years
162  * -nnnn[smhdwoy]	Given time - nnnn hours, mins, days, weeks, months or years
163  * dd[ ./-]mmm[ ./-]yy	Date }
164  * hh:mm:ss		Time } May be combined
165  */
166 
167 time_t
168 parse_date(time_t dt, char const * str)
169 {
170 	char           *p;
171 	int             i;
172 	long            val;
173 	struct tm      *T;
174 
175 	if (dt == 0)
176 		dt = time(NULL);
177 
178 	while (*str && isspace((unsigned char)*str))
179 		++str;
180 
181 	if (numerics(str)) {
182 		dt = strtol(str, &p, 0);
183 	} else if (*str == '+' || *str == '-') {
184 		val = strtol(str, &p, 0);
185 		switch (*p) {
186 		case 'h':
187 		case 'H':	/* hours */
188 			dt += (val * 3600L);
189 			break;
190 		case '\0':
191 		case 'm':
192 		case 'M':	/* minutes */
193 			dt += (val * 60L);
194 			break;
195 		case 's':
196 		case 'S':	/* seconds */
197 			dt += val;
198 			break;
199 		case 'd':
200 		case 'D':	/* days */
201 			dt += (val * 86400L);
202 			break;
203 		case 'w':
204 		case 'W':	/* weeks */
205 			dt += (val * 604800L);
206 			break;
207 		case 'o':
208 		case 'O':	/* months */
209 			T = localtime(&dt);
210 			T->tm_mon += (int) val;
211 			i = T->tm_mday;
212 			goto fixday;
213 		case 'y':
214 		case 'Y':	/* years */
215 			T = localtime(&dt);
216 			T->tm_year += (int) val;
217 			i = T->tm_mday;
218 	fixday:
219 			dt = mktime(T);
220 			T = localtime(&dt);
221 			if (T->tm_mday != i) {
222 				T->tm_mday = 1;
223 				dt = mktime(T);
224 				dt -= (time_t) 86400L;
225 			}
226 		default:	/* unknown */
227 			break;	/* leave untouched */
228 		}
229 	} else {
230 		char           *q, tmp[64];
231 
232 		/*
233 		 * Skip past any weekday prefix
234 		 */
235 		weekday(&str);
236 		strlcpy(tmp, str, sizeof(tmp));
237 		str = tmp;
238 		T = localtime(&dt);
239 
240 		/*
241 		 * See if we can break off any timezone
242 		 */
243 		while ((q = strrchr(tmp, ' ')) != NULL) {
244 			if (strchr("(+-", q[1]) != NULL)
245 				*q = '\0';
246 			else {
247 				int             j = 1;
248 
249 				while (q[j] && isupper((unsigned char)q[j]))
250 					++j;
251 				if (q[j] == '\0')
252 					*q = '\0';
253 				else
254 					break;
255 			}
256 		}
257 
258 		parse_datesub(tmp, T);
259 		dt = mktime(T);
260 	}
261 	return dt;
262 }
263