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