Home | History | Annotate | Line # | Download | only in isc
      1 /*	$NetBSD: tm.c,v 1.1 2024/02/18 20:57:50 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;
    102 		result += *(*buf)++ - '0';
    103 		rulim /= 10;
    104 	} while ((result * 10 <= ulim) && rulim && **buf >= '0' &&
    105 		 **buf <= '9');
    106 
    107 	if (result < llim || result > ulim) {
    108 		return (0);
    109 	}
    110 
    111 	*dest = result;
    112 	return (1);
    113 }
    114 
    115 time_t
    116 isc_tm_timegm(struct tm *tm) {
    117 	time_t ret;
    118 	int i, yday = 0, leapday;
    119 	int mdays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30 };
    120 
    121 	leapday = ((((tm->tm_year + 1900) % 4) == 0 &&
    122 		    ((tm->tm_year + 1900) % 100) != 0) ||
    123 		   ((tm->tm_year + 1900) % 400) == 0)
    124 			  ? 1
    125 			  : 0;
    126 	mdays[1] += leapday;
    127 
    128 	yday = tm->tm_mday - 1;
    129 	for (i = 1; i <= tm->tm_mon; i++) {
    130 		yday += mdays[i - 1];
    131 	}
    132 	ret = tm->tm_sec + (60 * tm->tm_min) + (3600 * tm->tm_hour) +
    133 	      (86400 *
    134 	       (yday + ((tm->tm_year - 70) * 365) + ((tm->tm_year - 69) / 4) -
    135 		((tm->tm_year - 1) / 100) + ((tm->tm_year + 299) / 400)));
    136 	return (ret);
    137 }
    138 
    139 char *
    140 isc_tm_strptime(const char *buf, const char *fmt, struct tm *tm) {
    141 	char c, *ret;
    142 	const char *bp;
    143 	size_t len = 0;
    144 	int alt_format, i, split_year = 0;
    145 
    146 	REQUIRE(buf != NULL);
    147 	REQUIRE(fmt != NULL);
    148 	REQUIRE(tm != NULL);
    149 
    150 	memset(tm, 0, sizeof(struct tm));
    151 
    152 	bp = buf;
    153 
    154 	while ((c = *fmt) != '\0') {
    155 		/* Clear `alternate' modifier prior to new conversion. */
    156 		alt_format = 0;
    157 
    158 		/* Eat up white-space. */
    159 		if (isspace((unsigned char)c)) {
    160 			while (isspace((unsigned char)*bp)) {
    161 				bp++;
    162 			}
    163 
    164 			fmt++;
    165 			continue;
    166 		}
    167 
    168 		if ((c = *fmt++) != '%') {
    169 			goto literal;
    170 		}
    171 
    172 	again:
    173 		switch (c = *fmt++) {
    174 		case '%': /* "%%" is converted to "%". */
    175 		literal:
    176 			if (c != *bp++) {
    177 				return (0);
    178 			}
    179 			break;
    180 
    181 		/*
    182 		 * "Alternative" modifiers. Just set the appropriate flag
    183 		 * and start over again.
    184 		 */
    185 		case 'E': /* "%E?" alternative conversion modifier. */
    186 			LEGAL_ALT(0);
    187 			alt_format |= ALT_E;
    188 			goto again;
    189 
    190 		case 'O': /* "%O?" alternative conversion modifier. */
    191 			LEGAL_ALT(0);
    192 			alt_format |= ALT_O;
    193 			goto again;
    194 
    195 		/*
    196 		 * "Complex" conversion rules, implemented through recursion.
    197 		 */
    198 		case 'c': /* Date and time, using the locale's format. */
    199 			LEGAL_ALT(ALT_E);
    200 			if (!(bp = isc_tm_strptime(bp, "%x %X", tm))) {
    201 				return (0);
    202 			}
    203 			break;
    204 
    205 		case 'D': /* The date as "%m/%d/%y". */
    206 			LEGAL_ALT(0);
    207 			if (!(bp = isc_tm_strptime(bp, "%m/%d/%y", tm))) {
    208 				return (0);
    209 			}
    210 			break;
    211 
    212 		case 'R': /* The time as "%H:%M". */
    213 			LEGAL_ALT(0);
    214 			if (!(bp = isc_tm_strptime(bp, "%H:%M", tm))) {
    215 				return (0);
    216 			}
    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 			}
    224 			break;
    225 
    226 		case 'T': /* The time as "%H:%M:%S". */
    227 			LEGAL_ALT(0);
    228 			if (!(bp = isc_tm_strptime(bp, "%H:%M:%S", tm))) {
    229 				return (0);
    230 			}
    231 			break;
    232 
    233 		case 'X': /* The time, using the locale's format. */
    234 			LEGAL_ALT(ALT_E);
    235 			if (!(bp = isc_tm_strptime(bp, "%H:%M:%S", tm))) {
    236 				return (0);
    237 			}
    238 			break;
    239 
    240 		case 'x': /* The date, using the locale's format. */
    241 			LEGAL_ALT(ALT_E);
    242 			if (!(bp = isc_tm_strptime(bp, "%m/%d/%y", tm))) {
    243 				return (0);
    244 			}
    245 			break;
    246 
    247 		/*
    248 		 * "Elementary" conversion rules.
    249 		 */
    250 		case 'A': /* The day of week, using the locale's form. */
    251 		case 'a':
    252 			LEGAL_ALT(0);
    253 			for (i = 0; i < 7; i++) {
    254 				/* Full name. */
    255 				len = strlen(day[i]);
    256 				if (strncasecmp(day[i], bp, len) == 0) {
    257 					break;
    258 				}
    259 
    260 				/* Abbreviated name. */
    261 				len = strlen(abday[i]);
    262 				if (strncasecmp(abday[i], bp, len) == 0) {
    263 					break;
    264 				}
    265 			}
    266 
    267 			/* Nothing matched. */
    268 			if (i == 7) {
    269 				return (0);
    270 			}
    271 
    272 			tm->tm_wday = i;
    273 			bp += len;
    274 			break;
    275 
    276 		case 'B': /* The month, using the locale's form. */
    277 		case 'b':
    278 		case 'h':
    279 			LEGAL_ALT(0);
    280 			for (i = 0; i < 12; i++) {
    281 				/* Full name. */
    282 				len = strlen(mon[i]);
    283 				if (strncasecmp(mon[i], bp, len) == 0) {
    284 					break;
    285 				}
    286 
    287 				/* Abbreviated name. */
    288 				len = strlen(abmon[i]);
    289 				if (strncasecmp(abmon[i], bp, len) == 0) {
    290 					break;
    291 				}
    292 			}
    293 
    294 			/* Nothing matched. */
    295 			if (i == 12) {
    296 				return (0);
    297 			}
    298 
    299 			tm->tm_mon = i;
    300 			bp += len;
    301 			break;
    302 
    303 		case 'C': /* The century number. */
    304 			LEGAL_ALT(ALT_E);
    305 			if (!(conv_num(&bp, &i, 0, 99))) {
    306 				return (0);
    307 			}
    308 
    309 			if (split_year) {
    310 				tm->tm_year = (tm->tm_year % 100) + (i * 100);
    311 			} else {
    312 				tm->tm_year = i * 100;
    313 				split_year = 1;
    314 			}
    315 			break;
    316 
    317 		case 'd': /* The day of month. */
    318 		case 'e':
    319 			LEGAL_ALT(ALT_O);
    320 			if (!(conv_num(&bp, &tm->tm_mday, 1, 31))) {
    321 				return (0);
    322 			}
    323 			break;
    324 
    325 		case 'k': /* The hour (24-hour clock representation). */
    326 			LEGAL_ALT(0);
    327 			FALLTHROUGH;
    328 		case 'H':
    329 			LEGAL_ALT(ALT_O);
    330 			if (!(conv_num(&bp, &tm->tm_hour, 0, 23))) {
    331 				return (0);
    332 			}
    333 			break;
    334 
    335 		case 'l': /* The hour (12-hour clock representation). */
    336 			LEGAL_ALT(0);
    337 			FALLTHROUGH;
    338 		case 'I':
    339 			LEGAL_ALT(ALT_O);
    340 			if (!(conv_num(&bp, &tm->tm_hour, 1, 12))) {
    341 				return (0);
    342 			}
    343 			if (tm->tm_hour == 12) {
    344 				tm->tm_hour = 0;
    345 			}
    346 			break;
    347 
    348 		case 'j': /* The day of year. */
    349 			LEGAL_ALT(0);
    350 			if (!(conv_num(&bp, &i, 1, 366))) {
    351 				return (0);
    352 			}
    353 			tm->tm_yday = i - 1;
    354 			break;
    355 
    356 		case 'M': /* The minute. */
    357 			LEGAL_ALT(ALT_O);
    358 			if (!(conv_num(&bp, &tm->tm_min, 0, 59))) {
    359 				return (0);
    360 			}
    361 			break;
    362 
    363 		case 'm': /* The month. */
    364 			LEGAL_ALT(ALT_O);
    365 			if (!(conv_num(&bp, &i, 1, 12))) {
    366 				return (0);
    367 			}
    368 			tm->tm_mon = i - 1;
    369 			break;
    370 
    371 		case 'p': /* The locale's equivalent of AM/PM. */
    372 			LEGAL_ALT(0);
    373 			/* AM? */
    374 			if (strcasecmp(am_pm[0], bp) == 0) {
    375 				if (tm->tm_hour > 11) {
    376 					return (0);
    377 				}
    378 
    379 				bp += strlen(am_pm[0]);
    380 				break;
    381 			}
    382 			/* PM? */
    383 			else if (strcasecmp(am_pm[1], bp) == 0)
    384 			{
    385 				if (tm->tm_hour > 11) {
    386 					return (0);
    387 				}
    388 
    389 				tm->tm_hour += 12;
    390 				bp += strlen(am_pm[1]);
    391 				break;
    392 			}
    393 
    394 			/* Nothing matched. */
    395 			return (0);
    396 
    397 		case 'S': /* The seconds. */
    398 			LEGAL_ALT(ALT_O);
    399 			if (!(conv_num(&bp, &tm->tm_sec, 0, 61))) {
    400 				return (0);
    401 			}
    402 			break;
    403 
    404 		case 'U': /* The week of year, beginning on sunday. */
    405 		case 'W': /* The week of year, beginning on monday. */
    406 			LEGAL_ALT(ALT_O);
    407 			/*
    408 			 * XXX This is bogus, as we can not assume any valid
    409 			 * information present in the tm structure at this
    410 			 * point to calculate a real value, so just check the
    411 			 * range for now.
    412 			 */
    413 			if (!(conv_num(&bp, &i, 0, 53))) {
    414 				return (0);
    415 			}
    416 			break;
    417 
    418 		case 'w': /* The day of week, beginning on sunday. */
    419 			LEGAL_ALT(ALT_O);
    420 			if (!(conv_num(&bp, &tm->tm_wday, 0, 6))) {
    421 				return (0);
    422 			}
    423 			break;
    424 
    425 		case 'Y': /* The year. */
    426 			LEGAL_ALT(ALT_E);
    427 			if (!(conv_num(&bp, &i, 0, 9999))) {
    428 				return (0);
    429 			}
    430 
    431 			tm->tm_year = i - TM_YEAR_BASE;
    432 			break;
    433 
    434 		case 'y': /* The year within 100 years of the epoch. */
    435 			LEGAL_ALT(ALT_E | ALT_O);
    436 			if (!(conv_num(&bp, &i, 0, 99))) {
    437 				return (0);
    438 			}
    439 
    440 			if (split_year) {
    441 				tm->tm_year = ((tm->tm_year / 100) * 100) + i;
    442 				break;
    443 			}
    444 			split_year = 1;
    445 			if (i <= 68) {
    446 				tm->tm_year = i + 2000 - TM_YEAR_BASE;
    447 			} else {
    448 				tm->tm_year = i + 1900 - TM_YEAR_BASE;
    449 			}
    450 			break;
    451 
    452 		/*
    453 		 * Miscellaneous conversions.
    454 		 */
    455 		case 'n': /* Any kind of white-space. */
    456 		case 't':
    457 			LEGAL_ALT(0);
    458 			while (isspace((unsigned char)*bp)) {
    459 				bp++;
    460 			}
    461 			break;
    462 
    463 		default: /* Unknown/unsupported conversion. */
    464 			return (0);
    465 		}
    466 	}
    467 
    468 	/* LINTED functional specification */
    469 	DE_CONST(bp, ret);
    470 	return (ret);
    471 }
    472