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