1262445Serwin/*
2262445Serwin * Copyright (C) 2014  Internet Systems Consortium, Inc. ("ISC")
3262445Serwin *
4262445Serwin * Permission to use, copy, modify, and/or distribute this software for any
5262445Serwin * purpose with or without fee is hereby granted, provided that the above
6262445Serwin * copyright notice and this permission notice appear in all copies.
7262445Serwin *
8262445Serwin * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
9262445Serwin * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
10262445Serwin * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
11262445Serwin * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
12262445Serwin * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
13262445Serwin * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
14262445Serwin * PERFORMANCE OF THIS SOFTWARE.
15262445Serwin */
16262445Serwin
17262445Serwin/*-
18262445Serwin * Copyright (c) 1997, 1998 The NetBSD Foundation, Inc.
19262445Serwin * All rights reserved.
20262445Serwin *
21262445Serwin * This code was contributed to The NetBSD Foundation by Klaus Klein.
22262445Serwin *
23262445Serwin * Redistribution and use in source and binary forms, with or without
24262445Serwin * modification, are permitted provided that the following conditions
25262445Serwin * are met:
26262445Serwin * 1. Redistributions of source code must retain the above copyright
27262445Serwin *    notice, this list of conditions and the following disclaimer.
28262445Serwin * 2. Redistributions in binary form must reproduce the above copyright
29262445Serwin *    notice, this list of conditions and the following disclaimer in the
30262445Serwin *    documentation and/or other materials provided with the distribution.
31262445Serwin *
32262445Serwin * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
33262445Serwin * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
34262445Serwin * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
35262445Serwin * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
36262445Serwin * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
37262445Serwin * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
38262445Serwin * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
39262445Serwin * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
40262445Serwin * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
41262445Serwin * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
42262445Serwin * POSSIBILITY OF SUCH DAMAGE.
43262445Serwin */
44262445Serwin
45262445Serwin#include <config.h>
46262445Serwin
47262445Serwin#include <stdio.h>
48262445Serwin#include <stdlib.h>
49262445Serwin#include <string.h>
50262445Serwin#include <time.h>
51262445Serwin#include <ctype.h>
52262445Serwin
53262445Serwin#include <isc/tm.h>
54262445Serwin#include <isc/util.h>
55262445Serwin
56262445Serwin/*
57262445Serwin * Portable conversion routines for struct tm, replacing
58262445Serwin * timegm() and strptime(), which are not available on all
59262445Serwin * platforms and don't always behave the same way when they
60262445Serwin * are.
61262445Serwin */
62262445Serwin
63262445Serwin/*
64262445Serwin * We do not implement alternate representations. However, we always
65262445Serwin * check whether a given modifier is allowed for a certain conversion.
66262445Serwin */
67262445Serwin#define ALT_E			0x01
68262445Serwin#define ALT_O			0x02
69262445Serwin#define	LEGAL_ALT(x)		{ if (alt_format & ~(x)) return (0); }
70262445Serwin
71262445Serwin#ifndef TM_YEAR_BASE
72262445Serwin#define TM_YEAR_BASE 1900
73262445Serwin#endif
74262445Serwin
75262445Serwinstatic const char *day[7] = {
76262445Serwin	"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday",
77262445Serwin	"Friday", "Saturday"
78262445Serwin};
79262445Serwinstatic const char *abday[7] = {
80262445Serwin	"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
81262445Serwin};
82262445Serwinstatic const char *mon[12] = {
83262445Serwin	"January", "February", "March", "April", "May", "June", "July",
84262445Serwin	"August", "September", "October", "November", "December"
85262445Serwin};
86262445Serwinstatic const char *abmon[12] = {
87262445Serwin	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
88262445Serwin	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
89262445Serwin};
90262445Serwinstatic const char *am_pm[2] = {
91262445Serwin	"AM", "PM"
92262445Serwin};
93262445Serwin
94262445Serwinstatic int
95262445Serwinconv_num(const char **buf, int *dest, int llim, int ulim) {
96262445Serwin	int result = 0;
97262445Serwin
98262445Serwin	/* The limit also determines the number of valid digits. */
99262445Serwin	int rulim = ulim;
100262445Serwin
101262445Serwin	if (**buf < '0' || **buf > '9')
102262445Serwin		return (0);
103262445Serwin
104262445Serwin	do {
105262445Serwin		result *= 10;
106262445Serwin		result += *(*buf)++ - '0';
107262445Serwin		rulim /= 10;
108262445Serwin	} while ((result * 10 <= ulim) &&
109262445Serwin		 rulim && **buf >= '0' && **buf <= '9');
110262445Serwin
111262445Serwin	if (result < llim || result > ulim)
112262445Serwin		return (0);
113262445Serwin
114262445Serwin	*dest = result;
115262445Serwin	return (1);
116262445Serwin}
117262445Serwin
118262445Serwintime_t
119262445Serwinisc_tm_timegm(struct tm *tm) {
120262445Serwin	time_t ret;
121262445Serwin	int i, yday = 0, leapday;
122262445Serwin	int mdays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30 };
123262445Serwin
124262445Serwin	leapday = ((((tm->tm_year + 1900 ) % 4) == 0 &&
125262445Serwin		    ((tm->tm_year + 1900 ) % 100) != 0) ||
126262445Serwin		   ((tm->tm_year + 1900 ) % 400) == 0) ? 1 : 0;
127262445Serwin	mdays[1] += leapday;
128262445Serwin
129262445Serwin	yday = tm->tm_mday - 1;
130262445Serwin	for (i = 1; i <= tm->tm_mon; i++)
131262445Serwin		yday += mdays[i - 1];
132262445Serwin	ret = tm->tm_sec +
133262445Serwin	      (60 * tm->tm_min) +
134262445Serwin	      (3600 * tm->tm_hour) +
135262445Serwin	      (86400 * (yday +
136262445Serwin		       ((tm->tm_year - 70) * 365) +
137262445Serwin		       ((tm->tm_year - 69) / 4) -
138262445Serwin		       ((tm->tm_year - 1) / 100) +
139262445Serwin		       ((tm->tm_year + 299) / 400)));
140262445Serwin	return (ret);
141262445Serwin}
142262445Serwin
143262445Serwinchar *
144262445Serwinisc_tm_strptime(const char *buf, const char *fmt, struct tm *tm) {
145262445Serwin	char c, *ret;
146262445Serwin	const char *bp;
147262445Serwin	size_t len = 0;
148262445Serwin	int alt_format, i, split_year = 0;
149262445Serwin
150262445Serwin	bp = buf;
151262445Serwin
152262445Serwin	while ((c = *fmt) != '\0') {
153262445Serwin		/* Clear `alternate' modifier prior to new conversion. */
154262445Serwin		alt_format = 0;
155262445Serwin
156262445Serwin		/* Eat up white-space. */
157262445Serwin		if (isspace((unsigned char) c)) {
158262445Serwin			while (isspace((unsigned char) *bp))
159262445Serwin				bp++;
160262445Serwin
161262445Serwin			fmt++;
162262445Serwin			continue;
163262445Serwin		}
164262445Serwin
165262445Serwin		if ((c = *fmt++) != '%')
166262445Serwin			goto literal;
167262445Serwin
168262445Serwin
169262445Serwinagain:		switch (c = *fmt++) {
170262445Serwin		case '%':	/* "%%" is converted to "%". */
171262445Serwinliteral:
172262445Serwin			if (c != *bp++)
173262445Serwin				return (0);
174262445Serwin			break;
175262445Serwin
176262445Serwin		/*
177262445Serwin		 * "Alternative" modifiers. Just set the appropriate flag
178262445Serwin		 * and start over again.
179262445Serwin		 */
180262445Serwin		case 'E':	/* "%E?" alternative conversion modifier. */
181262445Serwin			LEGAL_ALT(0);
182262445Serwin			alt_format |= ALT_E;
183262445Serwin			goto again;
184262445Serwin
185262445Serwin		case 'O':	/* "%O?" alternative conversion modifier. */
186262445Serwin			LEGAL_ALT(0);
187262445Serwin			alt_format |= ALT_O;
188262445Serwin			goto again;
189262445Serwin
190262445Serwin		/*
191262445Serwin		 * "Complex" conversion rules, implemented through recursion.
192262445Serwin		 */
193262445Serwin		case 'c':	/* Date and time, using the locale's format. */
194262445Serwin			LEGAL_ALT(ALT_E);
195262445Serwin			if (!(bp = isc_tm_strptime(bp, "%x %X", tm)))
196262445Serwin				return (0);
197262445Serwin			break;
198262445Serwin
199262445Serwin		case 'D':	/* The date as "%m/%d/%y". */
200262445Serwin			LEGAL_ALT(0);
201262445Serwin			if (!(bp = isc_tm_strptime(bp, "%m/%d/%y", tm)))
202262445Serwin				return (0);
203262445Serwin			break;
204262445Serwin
205262445Serwin		case 'R':	/* The time as "%H:%M". */
206262445Serwin			LEGAL_ALT(0);
207262445Serwin			if (!(bp = isc_tm_strptime(bp, "%H:%M", tm)))
208262445Serwin				return (0);
209262445Serwin			break;
210262445Serwin
211262445Serwin		case 'r':	/* The time in 12-hour clock representation. */
212262445Serwin			LEGAL_ALT(0);
213262445Serwin			if (!(bp = isc_tm_strptime(bp, "%I:%M:%S %p", tm)))
214262445Serwin				return (0);
215262445Serwin			break;
216262445Serwin
217262445Serwin		case 'T':	/* The time as "%H:%M:%S". */
218262445Serwin			LEGAL_ALT(0);
219262445Serwin			if (!(bp = isc_tm_strptime(bp, "%H:%M:%S", tm)))
220262445Serwin				return (0);
221262445Serwin			break;
222262445Serwin
223262445Serwin		case 'X':	/* The time, using the locale's format. */
224262445Serwin			LEGAL_ALT(ALT_E);
225262445Serwin			if (!(bp = isc_tm_strptime(bp, "%H:%M:%S", tm)))
226262445Serwin				return (0);
227262445Serwin			break;
228262445Serwin
229262445Serwin		case 'x':	/* The date, using the locale's format. */
230262445Serwin			LEGAL_ALT(ALT_E);
231262445Serwin			if (!(bp = isc_tm_strptime(bp, "%m/%d/%y", tm)))
232262445Serwin				return (0);
233262445Serwin			break;
234262445Serwin
235262445Serwin		/*
236262445Serwin		 * "Elementary" conversion rules.
237262445Serwin		 */
238262445Serwin		case 'A':	/* The day of week, using the locale's form. */
239262445Serwin		case 'a':
240262445Serwin			LEGAL_ALT(0);
241262445Serwin			for (i = 0; i < 7; i++) {
242262445Serwin				/* Full name. */
243262445Serwin				len = strlen(day[i]);
244262445Serwin				if (strncasecmp(day[i], bp, len) == 0)
245262445Serwin					break;
246262445Serwin
247262445Serwin				/* Abbreviated name. */
248262445Serwin				len = strlen(abday[i]);
249262445Serwin				if (strncasecmp(abday[i], bp, len) == 0)
250262445Serwin					break;
251262445Serwin			}
252262445Serwin
253262445Serwin			/* Nothing matched. */
254262445Serwin			if (i == 7)
255262445Serwin				return (0);
256262445Serwin
257262445Serwin			tm->tm_wday = i;
258262445Serwin			bp += len;
259262445Serwin			break;
260262445Serwin
261262445Serwin		case 'B':	/* The month, using the locale's form. */
262262445Serwin		case 'b':
263262445Serwin		case 'h':
264262445Serwin			LEGAL_ALT(0);
265262445Serwin			for (i = 0; i < 12; i++) {
266262445Serwin				/* Full name. */
267262445Serwin				len = strlen(mon[i]);
268262445Serwin				if (strncasecmp(mon[i], bp, len) == 0)
269262445Serwin					break;
270262445Serwin
271262445Serwin				/* Abbreviated name. */
272262445Serwin				len = strlen(abmon[i]);
273262445Serwin				if (strncasecmp(abmon[i], bp, len) == 0)
274262445Serwin					break;
275262445Serwin			}
276262445Serwin
277262445Serwin			/* Nothing matched. */
278262445Serwin			if (i == 12)
279262445Serwin				return (0);
280262445Serwin
281262445Serwin			tm->tm_mon = i;
282262445Serwin			bp += len;
283262445Serwin			break;
284262445Serwin
285262445Serwin		case 'C':	/* The century number. */
286262445Serwin			LEGAL_ALT(ALT_E);
287262445Serwin			if (!(conv_num(&bp, &i, 0, 99)))
288262445Serwin				return (0);
289262445Serwin
290262445Serwin			if (split_year) {
291262445Serwin				tm->tm_year = (tm->tm_year % 100) + (i * 100);
292262445Serwin			} else {
293262445Serwin				tm->tm_year = i * 100;
294262445Serwin				split_year = 1;
295262445Serwin			}
296262445Serwin			break;
297262445Serwin
298262445Serwin		case 'd':	/* The day of month. */
299262445Serwin		case 'e':
300262445Serwin			LEGAL_ALT(ALT_O);
301262445Serwin			if (!(conv_num(&bp, &tm->tm_mday, 1, 31)))
302262445Serwin				return (0);
303262445Serwin			break;
304262445Serwin
305262445Serwin		case 'k':	/* The hour (24-hour clock representation). */
306262445Serwin			LEGAL_ALT(0);
307262445Serwin			/* FALLTHROUGH */
308262445Serwin		case 'H':
309262445Serwin			LEGAL_ALT(ALT_O);
310262445Serwin			if (!(conv_num(&bp, &tm->tm_hour, 0, 23)))
311262445Serwin				return (0);
312262445Serwin			break;
313262445Serwin
314262445Serwin		case 'l':	/* The hour (12-hour clock representation). */
315262445Serwin			LEGAL_ALT(0);
316262445Serwin			/* FALLTHROUGH */
317262445Serwin		case 'I':
318262445Serwin			LEGAL_ALT(ALT_O);
319262445Serwin			if (!(conv_num(&bp, &tm->tm_hour, 1, 12)))
320262445Serwin				return (0);
321262445Serwin			if (tm->tm_hour == 12)
322262445Serwin				tm->tm_hour = 0;
323262445Serwin			break;
324262445Serwin
325262445Serwin		case 'j':	/* The day of year. */
326262445Serwin			LEGAL_ALT(0);
327262445Serwin			if (!(conv_num(&bp, &i, 1, 366)))
328262445Serwin				return (0);
329262445Serwin			tm->tm_yday = i - 1;
330262445Serwin			break;
331262445Serwin
332262445Serwin		case 'M':	/* The minute. */
333262445Serwin			LEGAL_ALT(ALT_O);
334262445Serwin			if (!(conv_num(&bp, &tm->tm_min, 0, 59)))
335262445Serwin				return (0);
336262445Serwin			break;
337262445Serwin
338262445Serwin		case 'm':	/* The month. */
339262445Serwin			LEGAL_ALT(ALT_O);
340262445Serwin			if (!(conv_num(&bp, &i, 1, 12)))
341262445Serwin				return (0);
342262445Serwin			tm->tm_mon = i - 1;
343262445Serwin			break;
344262445Serwin
345262445Serwin		case 'p':	/* The locale's equivalent of AM/PM. */
346262445Serwin			LEGAL_ALT(0);
347262445Serwin			/* AM? */
348262445Serwin			if (strcasecmp(am_pm[0], bp) == 0) {
349262445Serwin				if (tm->tm_hour > 11)
350262445Serwin					return (0);
351262445Serwin
352262445Serwin				bp += strlen(am_pm[0]);
353262445Serwin				break;
354262445Serwin			}
355262445Serwin			/* PM? */
356262445Serwin			else if (strcasecmp(am_pm[1], bp) == 0) {
357262445Serwin				if (tm->tm_hour > 11)
358262445Serwin					return (0);
359262445Serwin
360262445Serwin				tm->tm_hour += 12;
361262445Serwin				bp += strlen(am_pm[1]);
362262445Serwin				break;
363262445Serwin			}
364262445Serwin
365262445Serwin			/* Nothing matched. */
366262445Serwin			return (0);
367262445Serwin
368262445Serwin		case 'S':	/* The seconds. */
369262445Serwin			LEGAL_ALT(ALT_O);
370262445Serwin			if (!(conv_num(&bp, &tm->tm_sec, 0, 61)))
371262445Serwin				return (0);
372262445Serwin			break;
373262445Serwin
374262445Serwin		case 'U':	/* The week of year, beginning on sunday. */
375262445Serwin		case 'W':	/* The week of year, beginning on monday. */
376262445Serwin			LEGAL_ALT(ALT_O);
377262445Serwin			/*
378262445Serwin			 * XXX This is bogus, as we can not assume any valid
379262445Serwin			 * information present in the tm structure at this
380262445Serwin			 * point to calculate a real value, so just check the
381262445Serwin			 * range for now.
382262445Serwin			 */
383262445Serwin			 if (!(conv_num(&bp, &i, 0, 53)))
384262445Serwin				return (0);
385262445Serwin			 break;
386262445Serwin
387262445Serwin		case 'w':	/* The day of week, beginning on sunday. */
388262445Serwin			LEGAL_ALT(ALT_O);
389262445Serwin			if (!(conv_num(&bp, &tm->tm_wday, 0, 6)))
390262445Serwin				return (0);
391262445Serwin			break;
392262445Serwin
393262445Serwin		case 'Y':	/* The year. */
394262445Serwin			LEGAL_ALT(ALT_E);
395262445Serwin			if (!(conv_num(&bp, &i, 0, 9999)))
396262445Serwin				return (0);
397262445Serwin
398262445Serwin			tm->tm_year = i - TM_YEAR_BASE;
399262445Serwin			break;
400262445Serwin
401262445Serwin		case 'y':	/* The year within 100 years of the epoch. */
402262445Serwin			LEGAL_ALT(ALT_E | ALT_O);
403262445Serwin			if (!(conv_num(&bp, &i, 0, 99)))
404262445Serwin				return (0);
405262445Serwin
406262445Serwin			if (split_year) {
407262445Serwin				tm->tm_year = ((tm->tm_year / 100) * 100) + i;
408262445Serwin				break;
409262445Serwin			}
410262445Serwin			split_year = 1;
411262445Serwin			if (i <= 68)
412262445Serwin				tm->tm_year = i + 2000 - TM_YEAR_BASE;
413262445Serwin			else
414262445Serwin				tm->tm_year = i + 1900 - TM_YEAR_BASE;
415262445Serwin			break;
416262445Serwin
417262445Serwin		/*
418262445Serwin		 * Miscellaneous conversions.
419262445Serwin		 */
420262445Serwin		case 'n':	/* Any kind of white-space. */
421262445Serwin		case 't':
422262445Serwin			LEGAL_ALT(0);
423262445Serwin			while (isspace((unsigned char) *bp))
424262445Serwin				bp++;
425262445Serwin			break;
426262445Serwin
427262445Serwin
428262445Serwin		default:	/* Unknown/unsupported conversion. */
429262445Serwin			return (0);
430262445Serwin		}
431262445Serwin
432262445Serwin
433262445Serwin	}
434262445Serwin
435262445Serwin	/* LINTED functional specification */
436262445Serwin	DE_CONST(bp, ret);
437262445Serwin	return (ret);
438262445Serwin}
439