xref: /freebsd/usr.sbin/pw/psdate.c (revision fd45b686f9d92f583366c75b22c04c7ee49709c0)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (C) 1996
5  *	David L. Nugent.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 #include <ctype.h>
30 #include <err.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <xlocale.h>
34 
35 #include "psdate.h"
36 
37 
38 int
39 numerics(char const * str)
40 {
41 
42 	return (str[strspn(str, "0123456789x")] == '\0');
43 }
44 
45 static int
46 aindex(char const * arr[], char const ** str, int len)
47 {
48 	int             l, i;
49 	char            mystr[32];
50 
51 	mystr[len] = '\0';
52 	l = strlen(strncpy(mystr, *str, len));
53 	for (i = 0; i < l; i++)
54 		mystr[i] = (char) tolower((unsigned char)mystr[i]);
55 	for (i = 0; arr[i] && strcmp(mystr, arr[i]) != 0; i++);
56 	if (arr[i] == NULL)
57 		i = -1;
58 	else {			/* Skip past it */
59 		while (**str && isalpha((unsigned char)**str))
60 			++(*str);
61 		/* And any following whitespace */
62 		while (**str && (**str == ',' || isspace((unsigned char)**str)))
63 			++(*str);
64 	}			/* Return index */
65 	return i;
66 }
67 
68 static int
69 weekday(char const ** str)
70 {
71 	static char const *days[] =
72 	{"sun", "mon", "tue", "wed", "thu", "fri", "sat", NULL};
73 
74 	return aindex(days, str, 3);
75 }
76 
77 static void
78 parse_datesub(char const * str, struct tm *t)
79 {
80 	struct tm	 tm;
81 	locale_t	 l;
82 	int		 i;
83 	char		*ret;
84 	const char	*valid_formats[] = {
85 		"%d-%b-%y",
86 		"%d-%b-%Y",
87 		"%d-%m-%y",
88 		"%d-%m-%Y",
89 		"%H:%M %d-%b-%y",
90 		"%H:%M %d-%b-%Y",
91 		"%H:%M %d-%m-%y",
92 		"%H:%M %d-%m-%Y",
93 		"%H:%M:%S %d-%b-%y",
94 		"%H:%M:%S %d-%b-%Y",
95 		"%H:%M:%S %d-%m-%y",
96 		"%H:%M:%S %d-%m-%Y",
97 		"%d-%b-%y %H:%M",
98 		"%d-%b-%Y %H:%M",
99 		"%d-%m-%y %H:%M",
100 		"%d-%m-%Y %H:%M",
101 		"%d-%b-%y %H:%M:%S",
102 		"%d-%b-%Y %H:%M:%S",
103 		"%d-%m-%y %H:%M:%S",
104 		"%d-%m-%Y %H:%M:%S",
105 		"%H:%M\t%d-%b-%y",
106 		"%H:%M\t%d-%b-%Y",
107 		"%H:%M\t%d-%m-%y",
108 		"%H:%M\t%d-%m-%Y",
109 		"%H:%M\t%S %d-%b-%y",
110 		"%H:%M\t%S %d-%b-%Y",
111 		"%H:%M\t%S %d-%m-%y",
112 		"%H:%M\t%S %d-%m-%Y",
113 		"%d-%b-%y\t%H:%M",
114 		"%d-%b-%Y\t%H:%M",
115 		"%d-%m-%y\t%H:%M",
116 		"%d-%m-%Y\t%H:%M",
117 		"%d-%b-%y\t%H:%M:%S",
118 		"%d-%b-%Y\t%H:%M:%S",
119 		"%d-%m-%y\t%H:%M:%S",
120 		"%d-%m-%Y\t%H:%M:%S",
121 		NULL,
122 	};
123 
124 	l = newlocale(LC_ALL_MASK, "C", NULL);
125 
126 	for (i=0; valid_formats[i] != NULL; i++) {
127 		memset(&tm, 0, sizeof(tm));
128 		ret = strptime_l(str, valid_formats[i], &tm, l);
129 		if (ret && *ret == '\0') {
130 			t->tm_mday = tm.tm_mday;
131 			t->tm_mon = tm.tm_mon;
132 			t->tm_year = tm.tm_year;
133 			t->tm_hour = tm.tm_hour;
134 			t->tm_min = tm.tm_min;
135 			t->tm_sec = tm.tm_sec;
136 			freelocale(l);
137 			return;
138 		}
139 	}
140 
141 	freelocale(l);
142 
143 	errx(EXIT_FAILURE, "Invalid date");
144 }
145 
146 
147 /*-
148  * Parse time must be flexible, it handles the following formats:
149  * nnnnnnnnnnn		UNIX timestamp (all numeric), 0 = now
150  * 0xnnnnnnnn		UNIX timestamp in hexadecimal
151  * 0nnnnnnnnn		UNIX timestamp in octal
152  * 0			Given time
153  * +nnnn[smhdwoy]	Given time + nnnn hours, mins, days, weeks, months or years
154  * -nnnn[smhdwoy]	Given time - nnnn hours, mins, days, weeks, months or years
155  * dd[ ./-]mmm[ ./-]yy	Date }
156  * hh:mm:ss		Time } May be combined
157  */
158 
159 time_t
160 parse_date(time_t dt, char const * str)
161 {
162 	char           *p;
163 	int             i;
164 	long            val;
165 	struct tm      *T;
166 
167 	if (dt == 0)
168 		dt = time(NULL);
169 
170 	while (*str && isspace((unsigned char)*str))
171 		++str;
172 
173 	if (numerics(str)) {
174 		dt = strtol(str, &p, 0);
175 	} else if (*str == '+' || *str == '-') {
176 		val = strtol(str, &p, 0);
177 		switch (*p) {
178 		case 'h':
179 		case 'H':	/* hours */
180 			dt += (val * 3600L);
181 			break;
182 		case '\0':
183 		case 'm':
184 		case 'M':	/* minutes */
185 			dt += (val * 60L);
186 			break;
187 		case 's':
188 		case 'S':	/* seconds */
189 			dt += val;
190 			break;
191 		case 'd':
192 		case 'D':	/* days */
193 			dt += (val * 86400L);
194 			break;
195 		case 'w':
196 		case 'W':	/* weeks */
197 			dt += (val * 604800L);
198 			break;
199 		case 'o':
200 		case 'O':	/* months */
201 			T = localtime(&dt);
202 			T->tm_mon += (int) val;
203 			i = T->tm_mday;
204 			goto fixday;
205 		case 'y':
206 		case 'Y':	/* years */
207 			T = localtime(&dt);
208 			T->tm_year += (int) val;
209 			i = T->tm_mday;
210 	fixday:
211 			dt = mktime(T);
212 			T = localtime(&dt);
213 			if (T->tm_mday != i) {
214 				T->tm_mday = 1;
215 				dt = mktime(T);
216 				dt -= (time_t) 86400L;
217 			}
218 		default:	/* unknown */
219 			break;	/* leave untouched */
220 		}
221 	} else {
222 		char           *q, tmp[64];
223 
224 		/*
225 		 * Skip past any weekday prefix
226 		 */
227 		weekday(&str);
228 		strlcpy(tmp, str, sizeof(tmp));
229 		str = tmp;
230 		T = localtime(&dt);
231 
232 		/*
233 		 * See if we can break off any timezone
234 		 */
235 		while ((q = strrchr(tmp, ' ')) != NULL) {
236 			if (strchr("(+-", q[1]) != NULL)
237 				*q = '\0';
238 			else {
239 				int             j = 1;
240 
241 				while (q[j] && isupper((unsigned char)q[j]))
242 					++j;
243 				if (q[j] == '\0')
244 					*q = '\0';
245 				else
246 					break;
247 			}
248 		}
249 
250 		parse_datesub(tmp, T);
251 		dt = mktime(T);
252 	}
253 	return dt;
254 }
255