psdate.c revision 20253
1/*-
2 * Copyright (c) 1996 by David L. Nugent <davidn@blaze.net.au>.
3 * 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 as
10 *    the first lines of this file unmodified.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 * 3. All advertising materials mentioning features or use of this software
15 *    must display the following acknowledgement:
16 *	This product includes software developed by David L. Nugent.
17 * 4. The name of the author may not be used to endorse or promote products
18 *    derived from this software without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE DAVID L. NUGENT ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED.  IN NO EVENT SHALL DAVID L. NUGENT BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 *
32 *	$Id$
33 */
34
35#include <stdio.h>
36#include <stdlib.h>
37#include <string.h>
38#include <ctype.h>
39
40#include "psdate.h"
41
42
43static int
44a2i(char const ** str)
45{
46	int             i = 0;
47	char const     *s = *str;
48
49	if (isdigit(*s)) {
50		i = atoi(s);
51		while (isdigit(*s))
52			++s;
53		*str = s;
54	}
55	return i;
56}
57
58static int
59numerics(char const * str)
60{
61	int             rc = isdigit(*str);
62
63	if (rc)
64		while (isdigit(*str) || *str == 'x')
65			++str;
66	return rc && !*str;
67}
68
69static int
70aindex(char const * arr[], char const ** str, int len)
71{
72	int             l, i;
73	char            mystr[32];
74
75	mystr[len] = '\0';
76	l = strlen(strncpy(mystr, *str, len));
77	for (i = 0; i < l; i++)
78		mystr[i] = (char) tolower(mystr[i]);
79	for (i = 0; arr[i] && strcmp(mystr, arr[i]) != 0; i++);
80	if (arr[i] == NULL)
81		i = -1;
82	else {			/* Skip past it */
83		while (**str && isalpha(**str))
84			++(*str);
85		/* And any following whitespace */
86		while (**str && (**str == ',' || isspace(**str)))
87			++(*str);
88	}			/* Return index */
89	return i;
90}
91
92static int
93weekday(char const ** str)
94{
95	static char const *days[] =
96	{"sun", "mon", "tue", "wed", "thu", "fri", "sat", NULL};
97
98	return aindex(days, str, 3);
99}
100
101static int
102month(char const ** str)
103{
104	static char const *months[] =
105	{"jan", "feb", "mar", "apr", "may", "jun", "jul",
106	"aug", "sep", "oct", "nov", "dec", NULL};
107
108	return aindex(months, str, 3);
109}
110
111static void
112parse_time(char const * str, int *hour, int *min, int *sec)
113{
114	*hour = a2i(&str);
115	if ((str = strchr(str, ':')) == NULL)
116		*min = *sec = 0;
117	else {
118		++str;
119		*min = a2i(&str);
120		*sec = ((str = strchr(str, ':')) == NULL) ? 0 : atoi(++str);
121	}
122}
123
124
125static void
126parse_datesub(char const * str, int *day, int *mon, int *year)
127{
128	int             i;
129
130	static char const nchrs[] = "0123456789 \t,/-.";
131
132	if ((i = month(&str)) != -1) {
133		*mon = i;
134		if ((i = a2i(&str)) != 0)
135			*day = i;
136	} else if ((i = a2i(&str)) != 0) {
137		*day = i;
138		while (*str && strchr(nchrs + 10, *str) != NULL)
139			++str;
140		if ((i = month(&str)) != -1)
141			*mon = i;
142		else if ((i = a2i(&str)) != 0)
143			*mon = i - 1;
144	} else
145		return;
146
147	while (*str && strchr(nchrs + 10, *str) != NULL)
148		++str;
149	if (isdigit(*str)) {
150		*year = atoi(str);
151		if (*year > 1900)
152			*year -= 1900;
153		else if (*year < 32)
154			*year += 100;
155	}
156}
157
158
159/*-
160 * Parse time must be flexible, it handles the following formats:
161 * nnnnnnnnnnn		UNIX timestamp (all numeric), 0 = now
162 * 0xnnnnnnnn		UNIX timestamp in hexadecimal
163 * 0nnnnnnnnn		UNIX timestamp in octal
164 * 0			Given time
165 * +nnnn[smhdwoy]	Given time + nnnn hours, mins, days, weeks, months or years
166 * -nnnn[smhdwoy]	Given time - nnnn hours, mins, days, weeks, months or years
167 * dd[ ./-]mmm[ ./-]yy	Date }
168 * hh:mm:ss		Time } May be combined
169 */
170
171time_t
172parse_date(time_t dt, char const * str)
173{
174	char           *p;
175	int             i;
176	long            val;
177	struct tm      *T;
178
179	if (dt == 0)
180		dt = time(NULL);
181
182	while (*str && isspace(*str))
183		++str;
184
185	if (numerics(str)) {
186		val = strtol(str, &p, 0);
187		dt = val ? val : dt;
188	} else if (*str == '+' || *str == '-') {
189		val = strtol(str, &p, 0);
190		switch (*p) {
191		case 'h':
192		case 'H':	/* hours */
193			dt += (val * 3600L);
194			break;
195		case '\0':
196		case 'm':
197		case 'M':	/* minutes */
198			dt += (val * 60L);
199			break;
200		case 's':
201		case 'S':	/* seconds */
202			dt += val;
203			break;
204		case 'd':
205		case 'D':	/* days */
206			dt += (val * 86400L);
207			break;
208		case 'w':
209		case 'W':	/* weeks */
210			dt += (val * 604800L);
211			break;
212		case 'o':
213		case 'O':	/* months */
214			T = localtime(&dt);
215			T->tm_mon += (int) val;
216			i = T->tm_mday;
217			goto fixday;
218		case 'y':
219		case 'Y':	/* years */
220			T = localtime(&dt);
221			T->tm_year += (int) val;
222			i = T->tm_mday;
223	fixday:
224			dt = mktime(T);
225			T = localtime(&dt);
226			if (T->tm_mday != i) {
227				T->tm_mday = 1;
228				dt = mktime(T);
229				dt -= (time_t) 86400L;
230			}
231		default:	/* unknown */
232			break;	/* leave untouched */
233		}
234	} else {
235		char           *q, tmp[64];
236
237		/*
238		 * Skip past any weekday prefix
239		 */
240		weekday(&str);
241		str = strncpy(tmp, str, sizeof tmp - 1);
242		tmp[sizeof tmp - 1] = '\0';
243		T = localtime(&dt);
244
245		/*
246		 * See if we can break off any timezone
247		 */
248		while ((q = strrchr(tmp, ' ')) != NULL) {
249			if (strchr("(+-", q[1]) != NULL)
250				*q = '\0';
251			else {
252				int             j = 1;
253
254				while (q[j] && isupper(q[j]))
255					++j;
256				if (q[j] == '\0')
257					*q = '\0';
258				else
259					break;
260			}
261		}
262
263		/*
264		 * See if there is a time hh:mm[:ss]
265		 */
266		if ((p = strchr(tmp, ':')) == NULL) {
267
268			/*
269			 * No time string involved
270			 */
271			T->tm_hour = T->tm_min = T->tm_sec = 0;
272			parse_datesub(tmp, &T->tm_mday, &T->tm_mon, &T->tm_year);
273		} else {
274			char            datestr[64], timestr[64];
275
276			/*
277			 * Let's chip off the time string
278			 */
279			if ((q = strpbrk(p, " \t")) != NULL) {	/* Time first? */
280				int             l = q - str;
281
282				strncpy(timestr, str, l);
283				timestr[l] = '\0';
284				strncpy(datestr, q + 1, sizeof datestr);
285				datestr[sizeof datestr - 1] = '\0';
286				parse_time(timestr, &T->tm_hour, &T->tm_min, &T->tm_sec);
287				parse_datesub(datestr, &T->tm_mday, &T->tm_mon, &T->tm_year);
288			} else if ((q = strrchr(tmp, ' ')) != NULL) {	/* Time last */
289				int             l = q - tmp;
290
291				strncpy(timestr, q + 1, sizeof timestr);
292				timestr[sizeof timestr - 1] = '\0';
293				strncpy(datestr, tmp, l);
294				datestr[l] = '\0';
295			} else	/* Bail out */
296				return dt;
297			parse_time(timestr, &T->tm_hour, &T->tm_min, &T->tm_sec);
298			parse_datesub(datestr, &T->tm_mday, &T->tm_mon, &T->tm_year);
299		}
300		dt = mktime(T);
301	}
302	return dt;
303}
304