xref: /minix/external/bsd/bind/dist/lib/isc/tm.c (revision bb9622b5)
1 /*	$NetBSD: tm.c,v 1.1.1.3 2014/12/10 03:34:44 christos Exp $	*/
2 
3 /*
4  * Copyright (C) 2014  Internet Systems Consortium, Inc. ("ISC")
5  *
6  * Permission to use, copy, modify, and/or distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
11  * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
12  * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
13  * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
14  * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
15  * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
16  * PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 /*-
20  * Copyright (c) 1997, 1998 The NetBSD Foundation, Inc.
21  * All rights reserved.
22  *
23  * This code was contributed to The NetBSD Foundation by Klaus Klein.
24  *
25  * Redistribution and use in source and binary forms, with or without
26  * modification, are permitted provided that the following conditions
27  * are met:
28  * 1. Redistributions of source code must retain the above copyright
29  *    notice, this list of conditions and the following disclaimer.
30  * 2. Redistributions in binary form must reproduce the above copyright
31  *    notice, this list of conditions and the following disclaimer in the
32  *    documentation and/or other materials provided with the distribution.
33  *
34  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
35  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
36  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
37  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
38  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
39  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
40  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
41  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
42  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
43  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
44  * POSSIBILITY OF SUCH DAMAGE.
45  */
46 
47 #include <config.h>
48 
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <time.h>
53 #include <ctype.h>
54 
55 #include <isc/tm.h>
56 #include <isc/util.h>
57 
58 /*
59  * Portable conversion routines for struct tm, replacing
60  * timegm() and strptime(), which are not available on all
61  * platforms and don't always behave the same way when they
62  * are.
63  */
64 
65 /*
66  * We do not implement alternate representations. However, we always
67  * check whether a given modifier is allowed for a certain conversion.
68  */
69 #define ALT_E			0x01
70 #define ALT_O			0x02
71 #define	LEGAL_ALT(x)		{ if (alt_format & ~(x)) return (0); }
72 
73 #ifndef TM_YEAR_BASE
74 #define TM_YEAR_BASE 1900
75 #endif
76 
77 static const char *day[7] = {
78 	"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday",
79 	"Friday", "Saturday"
80 };
81 static const char *abday[7] = {
82 	"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
83 };
84 static const char *mon[12] = {
85 	"January", "February", "March", "April", "May", "June", "July",
86 	"August", "September", "October", "November", "December"
87 };
88 static const char *abmon[12] = {
89 	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
90 	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
91 };
92 static const char *am_pm[2] = {
93 	"AM", "PM"
94 };
95 
96 static int
97 conv_num(const char **buf, int *dest, int llim, int ulim) {
98 	int result = 0;
99 
100 	/* The limit also determines the number of valid digits. */
101 	int rulim = ulim;
102 
103 	if (**buf < '0' || **buf > '9')
104 		return (0);
105 
106 	do {
107 		result *= 10;
108 		result += *(*buf)++ - '0';
109 		rulim /= 10;
110 	} while ((result * 10 <= ulim) &&
111 		 rulim && **buf >= '0' && **buf <= '9');
112 
113 	if (result < llim || result > ulim)
114 		return (0);
115 
116 	*dest = result;
117 	return (1);
118 }
119 
120 time_t
121 isc_tm_timegm(struct tm *tm) {
122 	time_t ret;
123 	int i, yday = 0, leapday;
124 	int mdays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30 };
125 
126 	leapday = ((((tm->tm_year + 1900 ) % 4) == 0 &&
127 		    ((tm->tm_year + 1900 ) % 100) != 0) ||
128 		   ((tm->tm_year + 1900 ) % 400) == 0) ? 1 : 0;
129 	mdays[1] += leapday;
130 
131 	yday = tm->tm_mday - 1;
132 	for (i = 1; i <= tm->tm_mon; i++)
133 		yday += mdays[i - 1];
134 	ret = tm->tm_sec +
135 	      (60 * tm->tm_min) +
136 	      (3600 * tm->tm_hour) +
137 	      (86400 * (yday +
138 		       ((tm->tm_year - 70) * 365) +
139 		       ((tm->tm_year - 69) / 4) -
140 		       ((tm->tm_year - 1) / 100) +
141 		       ((tm->tm_year + 299) / 400)));
142 	return (ret);
143 }
144 
145 char *
146 isc_tm_strptime(const char *buf, const char *fmt, struct tm *tm) {
147 	char c, *ret;
148 	const char *bp;
149 	size_t len = 0;
150 	int alt_format, i, split_year = 0;
151 
152 	REQUIRE(buf != NULL);
153 	REQUIRE(fmt != NULL);
154 	REQUIRE(tm != NULL);
155 
156 	memset(tm, 0, sizeof(struct tm));
157 
158 	bp = buf;
159 
160 	while ((c = *fmt) != '\0') {
161 		/* Clear `alternate' modifier prior to new conversion. */
162 		alt_format = 0;
163 
164 		/* Eat up white-space. */
165 		if (isspace((unsigned char) c)) {
166 			while (isspace((unsigned char) *bp))
167 				bp++;
168 
169 			fmt++;
170 			continue;
171 		}
172 
173 		if ((c = *fmt++) != '%')
174 			goto literal;
175 
176 
177 again:		switch (c = *fmt++) {
178 		case '%':	/* "%%" is converted to "%". */
179 literal:
180 			if (c != *bp++)
181 				return (0);
182 			break;
183 
184 		/*
185 		 * "Alternative" modifiers. Just set the appropriate flag
186 		 * and start over again.
187 		 */
188 		case 'E':	/* "%E?" alternative conversion modifier. */
189 			LEGAL_ALT(0);
190 			alt_format |= ALT_E;
191 			goto again;
192 
193 		case 'O':	/* "%O?" alternative conversion modifier. */
194 			LEGAL_ALT(0);
195 			alt_format |= ALT_O;
196 			goto again;
197 
198 		/*
199 		 * "Complex" conversion rules, implemented through recursion.
200 		 */
201 		case 'c':	/* Date and time, using the locale's format. */
202 			LEGAL_ALT(ALT_E);
203 			if (!(bp = isc_tm_strptime(bp, "%x %X", tm)))
204 				return (0);
205 			break;
206 
207 		case 'D':	/* The date as "%m/%d/%y". */
208 			LEGAL_ALT(0);
209 			if (!(bp = isc_tm_strptime(bp, "%m/%d/%y", tm)))
210 				return (0);
211 			break;
212 
213 		case 'R':	/* The time as "%H:%M". */
214 			LEGAL_ALT(0);
215 			if (!(bp = isc_tm_strptime(bp, "%H:%M", tm)))
216 				return (0);
217 			break;
218 
219 		case 'r':	/* The time in 12-hour clock representation. */
220 			LEGAL_ALT(0);
221 			if (!(bp = isc_tm_strptime(bp, "%I:%M:%S %p", tm)))
222 				return (0);
223 			break;
224 
225 		case 'T':	/* The time as "%H:%M:%S". */
226 			LEGAL_ALT(0);
227 			if (!(bp = isc_tm_strptime(bp, "%H:%M:%S", tm)))
228 				return (0);
229 			break;
230 
231 		case 'X':	/* The time, using the locale's format. */
232 			LEGAL_ALT(ALT_E);
233 			if (!(bp = isc_tm_strptime(bp, "%H:%M:%S", tm)))
234 				return (0);
235 			break;
236 
237 		case 'x':	/* The date, using the locale's format. */
238 			LEGAL_ALT(ALT_E);
239 			if (!(bp = isc_tm_strptime(bp, "%m/%d/%y", tm)))
240 				return (0);
241 			break;
242 
243 		/*
244 		 * "Elementary" conversion rules.
245 		 */
246 		case 'A':	/* The day of week, using the locale's form. */
247 		case 'a':
248 			LEGAL_ALT(0);
249 			for (i = 0; i < 7; i++) {
250 				/* Full name. */
251 				len = strlen(day[i]);
252 				if (strncasecmp(day[i], bp, len) == 0)
253 					break;
254 
255 				/* Abbreviated name. */
256 				len = strlen(abday[i]);
257 				if (strncasecmp(abday[i], bp, len) == 0)
258 					break;
259 			}
260 
261 			/* Nothing matched. */
262 			if (i == 7)
263 				return (0);
264 
265 			tm->tm_wday = i;
266 			bp += len;
267 			break;
268 
269 		case 'B':	/* The month, using the locale's form. */
270 		case 'b':
271 		case 'h':
272 			LEGAL_ALT(0);
273 			for (i = 0; i < 12; i++) {
274 				/* Full name. */
275 				len = strlen(mon[i]);
276 				if (strncasecmp(mon[i], bp, len) == 0)
277 					break;
278 
279 				/* Abbreviated name. */
280 				len = strlen(abmon[i]);
281 				if (strncasecmp(abmon[i], bp, len) == 0)
282 					break;
283 			}
284 
285 			/* Nothing matched. */
286 			if (i == 12)
287 				return (0);
288 
289 			tm->tm_mon = i;
290 			bp += len;
291 			break;
292 
293 		case 'C':	/* The century number. */
294 			LEGAL_ALT(ALT_E);
295 			if (!(conv_num(&bp, &i, 0, 99)))
296 				return (0);
297 
298 			if (split_year) {
299 				tm->tm_year = (tm->tm_year % 100) + (i * 100);
300 			} else {
301 				tm->tm_year = i * 100;
302 				split_year = 1;
303 			}
304 			break;
305 
306 		case 'd':	/* The day of month. */
307 		case 'e':
308 			LEGAL_ALT(ALT_O);
309 			if (!(conv_num(&bp, &tm->tm_mday, 1, 31)))
310 				return (0);
311 			break;
312 
313 		case 'k':	/* The hour (24-hour clock representation). */
314 			LEGAL_ALT(0);
315 			/* FALLTHROUGH */
316 		case 'H':
317 			LEGAL_ALT(ALT_O);
318 			if (!(conv_num(&bp, &tm->tm_hour, 0, 23)))
319 				return (0);
320 			break;
321 
322 		case 'l':	/* The hour (12-hour clock representation). */
323 			LEGAL_ALT(0);
324 			/* FALLTHROUGH */
325 		case 'I':
326 			LEGAL_ALT(ALT_O);
327 			if (!(conv_num(&bp, &tm->tm_hour, 1, 12)))
328 				return (0);
329 			if (tm->tm_hour == 12)
330 				tm->tm_hour = 0;
331 			break;
332 
333 		case 'j':	/* The day of year. */
334 			LEGAL_ALT(0);
335 			if (!(conv_num(&bp, &i, 1, 366)))
336 				return (0);
337 			tm->tm_yday = i - 1;
338 			break;
339 
340 		case 'M':	/* The minute. */
341 			LEGAL_ALT(ALT_O);
342 			if (!(conv_num(&bp, &tm->tm_min, 0, 59)))
343 				return (0);
344 			break;
345 
346 		case 'm':	/* The month. */
347 			LEGAL_ALT(ALT_O);
348 			if (!(conv_num(&bp, &i, 1, 12)))
349 				return (0);
350 			tm->tm_mon = i - 1;
351 			break;
352 
353 		case 'p':	/* The locale's equivalent of AM/PM. */
354 			LEGAL_ALT(0);
355 			/* AM? */
356 			if (strcasecmp(am_pm[0], bp) == 0) {
357 				if (tm->tm_hour > 11)
358 					return (0);
359 
360 				bp += strlen(am_pm[0]);
361 				break;
362 			}
363 			/* PM? */
364 			else if (strcasecmp(am_pm[1], bp) == 0) {
365 				if (tm->tm_hour > 11)
366 					return (0);
367 
368 				tm->tm_hour += 12;
369 				bp += strlen(am_pm[1]);
370 				break;
371 			}
372 
373 			/* Nothing matched. */
374 			return (0);
375 
376 		case 'S':	/* The seconds. */
377 			LEGAL_ALT(ALT_O);
378 			if (!(conv_num(&bp, &tm->tm_sec, 0, 61)))
379 				return (0);
380 			break;
381 
382 		case 'U':	/* The week of year, beginning on sunday. */
383 		case 'W':	/* The week of year, beginning on monday. */
384 			LEGAL_ALT(ALT_O);
385 			/*
386 			 * XXX This is bogus, as we can not assume any valid
387 			 * information present in the tm structure at this
388 			 * point to calculate a real value, so just check the
389 			 * range for now.
390 			 */
391 			 if (!(conv_num(&bp, &i, 0, 53)))
392 				return (0);
393 			 break;
394 
395 		case 'w':	/* The day of week, beginning on sunday. */
396 			LEGAL_ALT(ALT_O);
397 			if (!(conv_num(&bp, &tm->tm_wday, 0, 6)))
398 				return (0);
399 			break;
400 
401 		case 'Y':	/* The year. */
402 			LEGAL_ALT(ALT_E);
403 			if (!(conv_num(&bp, &i, 0, 9999)))
404 				return (0);
405 
406 			tm->tm_year = i - TM_YEAR_BASE;
407 			break;
408 
409 		case 'y':	/* The year within 100 years of the epoch. */
410 			LEGAL_ALT(ALT_E | ALT_O);
411 			if (!(conv_num(&bp, &i, 0, 99)))
412 				return (0);
413 
414 			if (split_year) {
415 				tm->tm_year = ((tm->tm_year / 100) * 100) + i;
416 				break;
417 			}
418 			split_year = 1;
419 			if (i <= 68)
420 				tm->tm_year = i + 2000 - TM_YEAR_BASE;
421 			else
422 				tm->tm_year = i + 1900 - TM_YEAR_BASE;
423 			break;
424 
425 		/*
426 		 * Miscellaneous conversions.
427 		 */
428 		case 'n':	/* Any kind of white-space. */
429 		case 't':
430 			LEGAL_ALT(0);
431 			while (isspace((unsigned char) *bp))
432 				bp++;
433 			break;
434 
435 
436 		default:	/* Unknown/unsupported conversion. */
437 			return (0);
438 		}
439 
440 
441 	}
442 
443 	/* LINTED functional specification */
444 	DE_CONST(bp, ret);
445 	return (ret);
446 }
447