Home | History | Annotate | Download | only in openbsd-compat
      1 /*	$OpenBSD: strptime.c,v 1.12 2008/06/26 05:42:05 ray Exp $ */
      2 /*	$NetBSD: strptime.c,v 1.12 1998/01/20 21:39:40 mycroft Exp $	*/
      3 
      4 /*-
      5  * Copyright (c) 1997, 1998 The NetBSD Foundation, Inc.
      6  * All rights reserved.
      7  *
      8  * This code was contributed to The NetBSD Foundation by Klaus Klein.
      9  *
     10  * Redistribution and use in source and binary forms, with or without
     11  * modification, are permitted provided that the following conditions
     12  * are met:
     13  * 1. Redistributions of source code must retain the above copyright
     14  *    notice, this list of conditions and the following disclaimer.
     15  * 2. Redistributions in binary form must reproduce the above copyright
     16  *    notice, this list of conditions and the following disclaimer in the
     17  *    documentation and/or other materials provided with the distribution.
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     29  * POSSIBILITY OF SUCH DAMAGE.
     30  */
     31 
     32 /* OPENBSD ORIGINAL: lib/libc/time/strptime.c */
     33 
     34 #include "includes.h"
     35 
     36 #ifndef HAVE_STRPTIME
     37 
     38 #define TM_YEAR_BASE 1900	/* from tzfile.h */
     39 
     40 #include <ctype.h>
     41 #include <locale.h>
     42 #include <string.h>
     43 #include <time.h>
     44 
     45 /* #define	_ctloc(x)		(_CurrentTimeLocale->x) */
     46 
     47 /*
     48  * We do not implement alternate representations. However, we always
     49  * check whether a given modifier is allowed for a certain conversion.
     50  */
     51 #define _ALT_E			0x01
     52 #define _ALT_O			0x02
     53 #define	_LEGAL_ALT(x)		{ if (alt_format & ~(x)) return (0); }
     54 
     55 
     56 static	int _conv_num(const unsigned char **, int *, int, int);
     57 static	char *_strptime(const char *, const char *, struct tm *, int);
     58 
     59 
     60 char *
     61 strptime(const char *buf, const char *fmt, struct tm *tm)
     62 {
     63 	return(_strptime(buf, fmt, tm, 1));
     64 }
     65 
     66 static char *
     67 _strptime(const char *buf, const char *fmt, struct tm *tm, int initialize)
     68 {
     69 	unsigned char c;
     70 	const unsigned char *bp;
     71 	size_t len;
     72 	int alt_format, i;
     73 	static int century, relyear;
     74 
     75 	if (initialize) {
     76 		century = TM_YEAR_BASE;
     77 		relyear = -1;
     78 	}
     79 
     80 	bp = (unsigned char *)buf;
     81 	while ((c = *fmt) != '\0') {
     82 		/* Clear `alternate' modifier prior to new conversion. */
     83 		alt_format = 0;
     84 
     85 		/* Eat up white-space. */
     86 		if (isspace(c)) {
     87 			while (isspace(*bp))
     88 				bp++;
     89 
     90 			fmt++;
     91 			continue;
     92 		}
     93 
     94 		if ((c = *fmt++) != '%')
     95 			goto literal;
     96 
     97 
     98 again:		switch (c = *fmt++) {
     99 		case '%':	/* "%%" is converted to "%". */
    100 literal:
    101 		if (c != *bp++)
    102 			return (NULL);
    103 
    104 		break;
    105 
    106 		/*
    107 		 * "Alternative" modifiers. Just set the appropriate flag
    108 		 * and start over again.
    109 		 */
    110 		case 'E':	/* "%E?" alternative conversion modifier. */
    111 			_LEGAL_ALT(0);
    112 			alt_format |= _ALT_E;
    113 			goto again;
    114 
    115 		case 'O':	/* "%O?" alternative conversion modifier. */
    116 			_LEGAL_ALT(0);
    117 			alt_format |= _ALT_O;
    118 			goto again;
    119 
    120 		/*
    121 		 * "Complex" conversion rules, implemented through recursion.
    122 		 */
    123 #if 0
    124 		case 'c':	/* Date and time, using the locale's format. */
    125 			_LEGAL_ALT(_ALT_E);
    126 			if (!(bp = _strptime(bp, _ctloc(d_t_fmt), tm, 0)))
    127 				return (NULL);
    128 			break;
    129 #endif
    130 		case 'D':	/* The date as "%m/%d/%y". */
    131 			_LEGAL_ALT(0);
    132 			if (!(bp = _strptime(bp, "%m/%d/%y", tm, 0)))
    133 				return (NULL);
    134 			break;
    135 
    136 		case 'R':	/* The time as "%H:%M". */
    137 			_LEGAL_ALT(0);
    138 			if (!(bp = _strptime(bp, "%H:%M", tm, 0)))
    139 				return (NULL);
    140 			break;
    141 
    142 		case 'r':	/* The time as "%I:%M:%S %p". */
    143 			_LEGAL_ALT(0);
    144 			if (!(bp = _strptime(bp, "%I:%M:%S %p", tm, 0)))
    145 				return (NULL);
    146 			break;
    147 
    148 		case 'T':	/* The time as "%H:%M:%S". */
    149 			_LEGAL_ALT(0);
    150 			if (!(bp = _strptime(bp, "%H:%M:%S", tm, 0)))
    151 				return (NULL);
    152 			break;
    153 #if 0
    154 		case 'X':	/* The time, using the locale's format. */
    155 			_LEGAL_ALT(_ALT_E);
    156 			if (!(bp = _strptime(bp, _ctloc(t_fmt), tm, 0)))
    157 				return (NULL);
    158 			break;
    159 
    160 		case 'x':	/* The date, using the locale's format. */
    161 			_LEGAL_ALT(_ALT_E);
    162 			if (!(bp = _strptime(bp, _ctloc(d_fmt), tm, 0)))
    163 				return (NULL);
    164 			break;
    165 #endif
    166 		/*
    167 		 * "Elementary" conversion rules.
    168 		 */
    169 #if 0
    170 		case 'A':	/* The day of week, using the locale's form. */
    171 		case 'a':
    172 			_LEGAL_ALT(0);
    173 			for (i = 0; i < 7; i++) {
    174 				/* Full name. */
    175 				len = strlen(_ctloc(day[i]));
    176 				if (strncasecmp(_ctloc(day[i]), bp, len) == 0)
    177 					break;
    178 
    179 				/* Abbreviated name. */
    180 				len = strlen(_ctloc(abday[i]));
    181 				if (strncasecmp(_ctloc(abday[i]), bp, len) == 0)
    182 					break;
    183 			}
    184 
    185 			/* Nothing matched. */
    186 			if (i == 7)
    187 				return (NULL);
    188 
    189 			tm->tm_wday = i;
    190 			bp += len;
    191 			break;
    192 
    193 		case 'B':	/* The month, using the locale's form. */
    194 		case 'b':
    195 		case 'h':
    196 			_LEGAL_ALT(0);
    197 			for (i = 0; i < 12; i++) {
    198 				/* Full name. */
    199 				len = strlen(_ctloc(mon[i]));
    200 				if (strncasecmp(_ctloc(mon[i]), bp, len) == 0)
    201 					break;
    202 
    203 				/* Abbreviated name. */
    204 				len = strlen(_ctloc(abmon[i]));
    205 				if (strncasecmp(_ctloc(abmon[i]), bp, len) == 0)
    206 					break;
    207 			}
    208 
    209 			/* Nothing matched. */
    210 			if (i == 12)
    211 				return (NULL);
    212 
    213 			tm->tm_mon = i;
    214 			bp += len;
    215 			break;
    216 #endif
    217 
    218 		case 'C':	/* The century number. */
    219 			_LEGAL_ALT(_ALT_E);
    220 			if (!(_conv_num(&bp, &i, 0, 99)))
    221 				return (NULL);
    222 
    223 			century = i * 100;
    224 			break;
    225 
    226 		case 'd':	/* The day of month. */
    227 		case 'e':
    228 			_LEGAL_ALT(_ALT_O);
    229 			if (!(_conv_num(&bp, &tm->tm_mday, 1, 31)))
    230 				return (NULL);
    231 			break;
    232 
    233 		case 'k':	/* The hour (24-hour clock representation). */
    234 			_LEGAL_ALT(0);
    235 			/* FALLTHROUGH */
    236 		case 'H':
    237 			_LEGAL_ALT(_ALT_O);
    238 			if (!(_conv_num(&bp, &tm->tm_hour, 0, 23)))
    239 				return (NULL);
    240 			break;
    241 
    242 		case 'l':	/* The hour (12-hour clock representation). */
    243 			_LEGAL_ALT(0);
    244 			/* FALLTHROUGH */
    245 		case 'I':
    246 			_LEGAL_ALT(_ALT_O);
    247 			if (!(_conv_num(&bp, &tm->tm_hour, 1, 12)))
    248 				return (NULL);
    249 			break;
    250 
    251 		case 'j':	/* The day of year. */
    252 			_LEGAL_ALT(0);
    253 			if (!(_conv_num(&bp, &tm->tm_yday, 1, 366)))
    254 				return (NULL);
    255 			tm->tm_yday--;
    256 			break;
    257 
    258 		case 'M':	/* The minute. */
    259 			_LEGAL_ALT(_ALT_O);
    260 			if (!(_conv_num(&bp, &tm->tm_min, 0, 59)))
    261 				return (NULL);
    262 			break;
    263 
    264 		case 'm':	/* The month. */
    265 			_LEGAL_ALT(_ALT_O);
    266 			if (!(_conv_num(&bp, &tm->tm_mon, 1, 12)))
    267 				return (NULL);
    268 			tm->tm_mon--;
    269 			break;
    270 
    271 #if 0
    272 		case 'p':	/* The locale's equivalent of AM/PM. */
    273 			_LEGAL_ALT(0);
    274 			/* AM? */
    275 			len = strlen(_ctloc(am_pm[0]));
    276 			if (strncasecmp(_ctloc(am_pm[0]), bp, len) == 0) {
    277 				if (tm->tm_hour > 12)	/* i.e., 13:00 AM ?! */
    278 					return (NULL);
    279 				else if (tm->tm_hour == 12)
    280 					tm->tm_hour = 0;
    281 
    282 				bp += len;
    283 				break;
    284 			}
    285 			/* PM? */
    286 			len = strlen(_ctloc(am_pm[1]));
    287 			if (strncasecmp(_ctloc(am_pm[1]), bp, len) == 0) {
    288 				if (tm->tm_hour > 12)	/* i.e., 13:00 PM ?! */
    289 					return (NULL);
    290 				else if (tm->tm_hour < 12)
    291 					tm->tm_hour += 12;
    292 
    293 				bp += len;
    294 				break;
    295 			}
    296 
    297 			/* Nothing matched. */
    298 			return (NULL);
    299 #endif
    300 		case 'S':	/* The seconds. */
    301 			_LEGAL_ALT(_ALT_O);
    302 			if (!(_conv_num(&bp, &tm->tm_sec, 0, 61)))
    303 				return (NULL);
    304 			break;
    305 
    306 		case 'U':	/* The week of year, beginning on sunday. */
    307 		case 'W':	/* The week of year, beginning on monday. */
    308 			_LEGAL_ALT(_ALT_O);
    309 			/*
    310 			 * XXX This is bogus, as we can not assume any valid
    311 			 * information present in the tm structure at this
    312 			 * point to calculate a real value, so just check the
    313 			 * range for now.
    314 			 */
    315 			 if (!(_conv_num(&bp, &i, 0, 53)))
    316 				return (NULL);
    317 			 break;
    318 
    319 		case 'w':	/* The day of week, beginning on sunday. */
    320 			_LEGAL_ALT(_ALT_O);
    321 			if (!(_conv_num(&bp, &tm->tm_wday, 0, 6)))
    322 				return (NULL);
    323 			break;
    324 
    325 		case 'Y':	/* The year. */
    326 			_LEGAL_ALT(_ALT_E);
    327 			if (!(_conv_num(&bp, &i, 0, 9999)))
    328 				return (NULL);
    329 
    330 			relyear = -1;
    331 			tm->tm_year = i - TM_YEAR_BASE;
    332 			break;
    333 
    334 		case 'y':	/* The year within the century (2 digits). */
    335 			_LEGAL_ALT(_ALT_E | _ALT_O);
    336 			if (!(_conv_num(&bp, &relyear, 0, 99)))
    337 				return (NULL);
    338 			break;
    339 
    340 		/*
    341 		 * Miscellaneous conversions.
    342 		 */
    343 		case 'n':	/* Any kind of white-space. */
    344 		case 't':
    345 			_LEGAL_ALT(0);
    346 			while (isspace(*bp))
    347 				bp++;
    348 			break;
    349 
    350 
    351 		default:	/* Unknown/unsupported conversion. */
    352 			return (NULL);
    353 		}
    354 
    355 
    356 	}
    357 
    358 	/*
    359 	 * We need to evaluate the two digit year spec (%y)
    360 	 * last as we can get a century spec (%C) at any time.
    361 	 */
    362 	if (relyear != -1) {
    363 		if (century == TM_YEAR_BASE) {
    364 			if (relyear <= 68)
    365 				tm->tm_year = relyear + 2000 - TM_YEAR_BASE;
    366 			else
    367 				tm->tm_year = relyear + 1900 - TM_YEAR_BASE;
    368 		} else {
    369 			tm->tm_year = relyear + century - TM_YEAR_BASE;
    370 		}
    371 	}
    372 
    373 	return ((char *)bp);
    374 }
    375 
    376 
    377 static int
    378 _conv_num(const unsigned char **buf, int *dest, int llim, int ulim)
    379 {
    380 	int result = 0;
    381 	int rulim = ulim;
    382 
    383 	if (**buf < '0' || **buf > '9')
    384 		return (0);
    385 
    386 	/* we use rulim to break out of the loop when we run out of digits */
    387 	do {
    388 		result *= 10;
    389 		result += *(*buf)++ - '0';
    390 		rulim /= 10;
    391 	} while ((result * 10 <= ulim) && rulim && **buf >= '0' && **buf <= '9');
    392 
    393 	if (result < llim || result > ulim)
    394 		return (0);
    395 
    396 	*dest = result;
    397 	return (1);
    398 }
    399 
    400 #endif /* HAVE_STRPTIME */
    401 
    402