1214979Sdes/* $OpenBSD: strptime.c,v 1.12 2008/06/26 05:42:05 ray Exp $ */ 2214979Sdes/* $NetBSD: strptime.c,v 1.12 1998/01/20 21:39:40 mycroft Exp $ */ 3214979Sdes 4214979Sdes/*- 5214979Sdes * Copyright (c) 1997, 1998 The NetBSD Foundation, Inc. 6214979Sdes * All rights reserved. 7214979Sdes * 8214979Sdes * This code was contributed to The NetBSD Foundation by Klaus Klein. 9214979Sdes * 10214979Sdes * Redistribution and use in source and binary forms, with or without 11214979Sdes * modification, are permitted provided that the following conditions 12214979Sdes * are met: 13214979Sdes * 1. Redistributions of source code must retain the above copyright 14214979Sdes * notice, this list of conditions and the following disclaimer. 15214979Sdes * 2. Redistributions in binary form must reproduce the above copyright 16214979Sdes * notice, this list of conditions and the following disclaimer in the 17214979Sdes * documentation and/or other materials provided with the distribution. 18214979Sdes * 19214979Sdes * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20214979Sdes * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21214979Sdes * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22214979Sdes * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23214979Sdes * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24214979Sdes * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25214979Sdes * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26214979Sdes * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27214979Sdes * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28214979Sdes * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29214979Sdes * POSSIBILITY OF SUCH DAMAGE. 30214979Sdes */ 31214979Sdes 32214979Sdes/* OPENBSD ORIGINAL: lib/libc/time/strptime.c */ 33214979Sdes 34214979Sdes#include "includes.h" 35214979Sdes 36214979Sdes#ifndef HAVE_STRPTIME 37214979Sdes 38214979Sdes#define TM_YEAR_BASE 1900 /* from tzfile.h */ 39214979Sdes 40214979Sdes#include <ctype.h> 41214979Sdes#include <locale.h> 42214979Sdes#include <string.h> 43214979Sdes#include <time.h> 44214979Sdes 45214979Sdes/* #define _ctloc(x) (_CurrentTimeLocale->x) */ 46214979Sdes 47214979Sdes/* 48214979Sdes * We do not implement alternate representations. However, we always 49214979Sdes * check whether a given modifier is allowed for a certain conversion. 50214979Sdes */ 51214979Sdes#define _ALT_E 0x01 52214979Sdes#define _ALT_O 0x02 53214979Sdes#define _LEGAL_ALT(x) { if (alt_format & ~(x)) return (0); } 54214979Sdes 55214979Sdes 56214979Sdesstatic int _conv_num(const unsigned char **, int *, int, int); 57214979Sdesstatic char *_strptime(const char *, const char *, struct tm *, int); 58214979Sdes 59214979Sdes 60214979Sdeschar * 61214979Sdesstrptime(const char *buf, const char *fmt, struct tm *tm) 62214979Sdes{ 63214979Sdes return(_strptime(buf, fmt, tm, 1)); 64214979Sdes} 65214979Sdes 66214979Sdesstatic char * 67214979Sdes_strptime(const char *buf, const char *fmt, struct tm *tm, int initialize) 68214979Sdes{ 69214979Sdes unsigned char c; 70214979Sdes const unsigned char *bp; 71214979Sdes size_t len; 72214979Sdes int alt_format, i; 73214979Sdes static int century, relyear; 74214979Sdes 75214979Sdes if (initialize) { 76214979Sdes century = TM_YEAR_BASE; 77214979Sdes relyear = -1; 78214979Sdes } 79214979Sdes 80214979Sdes bp = (unsigned char *)buf; 81214979Sdes while ((c = *fmt) != '\0') { 82214979Sdes /* Clear `alternate' modifier prior to new conversion. */ 83214979Sdes alt_format = 0; 84214979Sdes 85214979Sdes /* Eat up white-space. */ 86214979Sdes if (isspace(c)) { 87214979Sdes while (isspace(*bp)) 88214979Sdes bp++; 89214979Sdes 90214979Sdes fmt++; 91214979Sdes continue; 92214979Sdes } 93214979Sdes 94214979Sdes if ((c = *fmt++) != '%') 95214979Sdes goto literal; 96214979Sdes 97214979Sdes 98214979Sdesagain: switch (c = *fmt++) { 99214979Sdes case '%': /* "%%" is converted to "%". */ 100214979Sdesliteral: 101214979Sdes if (c != *bp++) 102214979Sdes return (NULL); 103214979Sdes 104214979Sdes break; 105214979Sdes 106214979Sdes /* 107214979Sdes * "Alternative" modifiers. Just set the appropriate flag 108214979Sdes * and start over again. 109214979Sdes */ 110214979Sdes case 'E': /* "%E?" alternative conversion modifier. */ 111214979Sdes _LEGAL_ALT(0); 112214979Sdes alt_format |= _ALT_E; 113214979Sdes goto again; 114214979Sdes 115214979Sdes case 'O': /* "%O?" alternative conversion modifier. */ 116214979Sdes _LEGAL_ALT(0); 117214979Sdes alt_format |= _ALT_O; 118214979Sdes goto again; 119214979Sdes 120214979Sdes /* 121214979Sdes * "Complex" conversion rules, implemented through recursion. 122214979Sdes */ 123214979Sdes#if 0 124214979Sdes case 'c': /* Date and time, using the locale's format. */ 125214979Sdes _LEGAL_ALT(_ALT_E); 126214979Sdes if (!(bp = _strptime(bp, _ctloc(d_t_fmt), tm, 0))) 127214979Sdes return (NULL); 128214979Sdes break; 129214979Sdes#endif 130214979Sdes case 'D': /* The date as "%m/%d/%y". */ 131214979Sdes _LEGAL_ALT(0); 132214979Sdes if (!(bp = _strptime(bp, "%m/%d/%y", tm, 0))) 133214979Sdes return (NULL); 134214979Sdes break; 135214979Sdes 136214979Sdes case 'R': /* The time as "%H:%M". */ 137214979Sdes _LEGAL_ALT(0); 138214979Sdes if (!(bp = _strptime(bp, "%H:%M", tm, 0))) 139214979Sdes return (NULL); 140214979Sdes break; 141214979Sdes 142214979Sdes case 'r': /* The time as "%I:%M:%S %p". */ 143214979Sdes _LEGAL_ALT(0); 144214979Sdes if (!(bp = _strptime(bp, "%I:%M:%S %p", tm, 0))) 145214979Sdes return (NULL); 146214979Sdes break; 147214979Sdes 148214979Sdes case 'T': /* The time as "%H:%M:%S". */ 149214979Sdes _LEGAL_ALT(0); 150214979Sdes if (!(bp = _strptime(bp, "%H:%M:%S", tm, 0))) 151214979Sdes return (NULL); 152214979Sdes break; 153214979Sdes#if 0 154214979Sdes case 'X': /* The time, using the locale's format. */ 155214979Sdes _LEGAL_ALT(_ALT_E); 156214979Sdes if (!(bp = _strptime(bp, _ctloc(t_fmt), tm, 0))) 157214979Sdes return (NULL); 158214979Sdes break; 159214979Sdes 160214979Sdes case 'x': /* The date, using the locale's format. */ 161214979Sdes _LEGAL_ALT(_ALT_E); 162214979Sdes if (!(bp = _strptime(bp, _ctloc(d_fmt), tm, 0))) 163214979Sdes return (NULL); 164214979Sdes break; 165214979Sdes#endif 166214979Sdes /* 167214979Sdes * "Elementary" conversion rules. 168214979Sdes */ 169214979Sdes#if 0 170214979Sdes case 'A': /* The day of week, using the locale's form. */ 171214979Sdes case 'a': 172214979Sdes _LEGAL_ALT(0); 173214979Sdes for (i = 0; i < 7; i++) { 174214979Sdes /* Full name. */ 175214979Sdes len = strlen(_ctloc(day[i])); 176214979Sdes if (strncasecmp(_ctloc(day[i]), bp, len) == 0) 177214979Sdes break; 178214979Sdes 179214979Sdes /* Abbreviated name. */ 180214979Sdes len = strlen(_ctloc(abday[i])); 181214979Sdes if (strncasecmp(_ctloc(abday[i]), bp, len) == 0) 182214979Sdes break; 183214979Sdes } 184214979Sdes 185214979Sdes /* Nothing matched. */ 186214979Sdes if (i == 7) 187214979Sdes return (NULL); 188214979Sdes 189214979Sdes tm->tm_wday = i; 190214979Sdes bp += len; 191214979Sdes break; 192214979Sdes 193214979Sdes case 'B': /* The month, using the locale's form. */ 194214979Sdes case 'b': 195214979Sdes case 'h': 196214979Sdes _LEGAL_ALT(0); 197214979Sdes for (i = 0; i < 12; i++) { 198214979Sdes /* Full name. */ 199214979Sdes len = strlen(_ctloc(mon[i])); 200214979Sdes if (strncasecmp(_ctloc(mon[i]), bp, len) == 0) 201214979Sdes break; 202214979Sdes 203214979Sdes /* Abbreviated name. */ 204214979Sdes len = strlen(_ctloc(abmon[i])); 205214979Sdes if (strncasecmp(_ctloc(abmon[i]), bp, len) == 0) 206214979Sdes break; 207214979Sdes } 208214979Sdes 209214979Sdes /* Nothing matched. */ 210214979Sdes if (i == 12) 211214979Sdes return (NULL); 212214979Sdes 213214979Sdes tm->tm_mon = i; 214214979Sdes bp += len; 215214979Sdes break; 216214979Sdes#endif 217214979Sdes 218214979Sdes case 'C': /* The century number. */ 219214979Sdes _LEGAL_ALT(_ALT_E); 220214979Sdes if (!(_conv_num(&bp, &i, 0, 99))) 221214979Sdes return (NULL); 222214979Sdes 223214979Sdes century = i * 100; 224214979Sdes break; 225214979Sdes 226214979Sdes case 'd': /* The day of month. */ 227214979Sdes case 'e': 228214979Sdes _LEGAL_ALT(_ALT_O); 229214979Sdes if (!(_conv_num(&bp, &tm->tm_mday, 1, 31))) 230214979Sdes return (NULL); 231214979Sdes break; 232214979Sdes 233214979Sdes case 'k': /* The hour (24-hour clock representation). */ 234214979Sdes _LEGAL_ALT(0); 235214979Sdes /* FALLTHROUGH */ 236214979Sdes case 'H': 237214979Sdes _LEGAL_ALT(_ALT_O); 238214979Sdes if (!(_conv_num(&bp, &tm->tm_hour, 0, 23))) 239214979Sdes return (NULL); 240214979Sdes break; 241214979Sdes 242214979Sdes case 'l': /* The hour (12-hour clock representation). */ 243214979Sdes _LEGAL_ALT(0); 244214979Sdes /* FALLTHROUGH */ 245214979Sdes case 'I': 246214979Sdes _LEGAL_ALT(_ALT_O); 247214979Sdes if (!(_conv_num(&bp, &tm->tm_hour, 1, 12))) 248214979Sdes return (NULL); 249214979Sdes break; 250214979Sdes 251214979Sdes case 'j': /* The day of year. */ 252214979Sdes _LEGAL_ALT(0); 253214979Sdes if (!(_conv_num(&bp, &tm->tm_yday, 1, 366))) 254214979Sdes return (NULL); 255214979Sdes tm->tm_yday--; 256214979Sdes break; 257214979Sdes 258214979Sdes case 'M': /* The minute. */ 259214979Sdes _LEGAL_ALT(_ALT_O); 260214979Sdes if (!(_conv_num(&bp, &tm->tm_min, 0, 59))) 261214979Sdes return (NULL); 262214979Sdes break; 263214979Sdes 264214979Sdes case 'm': /* The month. */ 265214979Sdes _LEGAL_ALT(_ALT_O); 266214979Sdes if (!(_conv_num(&bp, &tm->tm_mon, 1, 12))) 267214979Sdes return (NULL); 268214979Sdes tm->tm_mon--; 269214979Sdes break; 270214979Sdes 271214979Sdes#if 0 272214979Sdes case 'p': /* The locale's equivalent of AM/PM. */ 273214979Sdes _LEGAL_ALT(0); 274214979Sdes /* AM? */ 275214979Sdes len = strlen(_ctloc(am_pm[0])); 276214979Sdes if (strncasecmp(_ctloc(am_pm[0]), bp, len) == 0) { 277214979Sdes if (tm->tm_hour > 12) /* i.e., 13:00 AM ?! */ 278214979Sdes return (NULL); 279214979Sdes else if (tm->tm_hour == 12) 280214979Sdes tm->tm_hour = 0; 281214979Sdes 282214979Sdes bp += len; 283214979Sdes break; 284214979Sdes } 285214979Sdes /* PM? */ 286214979Sdes len = strlen(_ctloc(am_pm[1])); 287214979Sdes if (strncasecmp(_ctloc(am_pm[1]), bp, len) == 0) { 288214979Sdes if (tm->tm_hour > 12) /* i.e., 13:00 PM ?! */ 289214979Sdes return (NULL); 290214979Sdes else if (tm->tm_hour < 12) 291214979Sdes tm->tm_hour += 12; 292214979Sdes 293214979Sdes bp += len; 294214979Sdes break; 295214979Sdes } 296214979Sdes 297214979Sdes /* Nothing matched. */ 298214979Sdes return (NULL); 299214979Sdes#endif 300214979Sdes case 'S': /* The seconds. */ 301214979Sdes _LEGAL_ALT(_ALT_O); 302214979Sdes if (!(_conv_num(&bp, &tm->tm_sec, 0, 61))) 303214979Sdes return (NULL); 304214979Sdes break; 305214979Sdes 306214979Sdes case 'U': /* The week of year, beginning on sunday. */ 307214979Sdes case 'W': /* The week of year, beginning on monday. */ 308214979Sdes _LEGAL_ALT(_ALT_O); 309214979Sdes /* 310214979Sdes * XXX This is bogus, as we can not assume any valid 311214979Sdes * information present in the tm structure at this 312214979Sdes * point to calculate a real value, so just check the 313214979Sdes * range for now. 314214979Sdes */ 315214979Sdes if (!(_conv_num(&bp, &i, 0, 53))) 316214979Sdes return (NULL); 317214979Sdes break; 318214979Sdes 319214979Sdes case 'w': /* The day of week, beginning on sunday. */ 320214979Sdes _LEGAL_ALT(_ALT_O); 321214979Sdes if (!(_conv_num(&bp, &tm->tm_wday, 0, 6))) 322214979Sdes return (NULL); 323214979Sdes break; 324214979Sdes 325214979Sdes case 'Y': /* The year. */ 326214979Sdes _LEGAL_ALT(_ALT_E); 327214979Sdes if (!(_conv_num(&bp, &i, 0, 9999))) 328214979Sdes return (NULL); 329214979Sdes 330214979Sdes relyear = -1; 331214979Sdes tm->tm_year = i - TM_YEAR_BASE; 332214979Sdes break; 333214979Sdes 334214979Sdes case 'y': /* The year within the century (2 digits). */ 335214979Sdes _LEGAL_ALT(_ALT_E | _ALT_O); 336214979Sdes if (!(_conv_num(&bp, &relyear, 0, 99))) 337214979Sdes return (NULL); 338214979Sdes break; 339214979Sdes 340214979Sdes /* 341214979Sdes * Miscellaneous conversions. 342214979Sdes */ 343214979Sdes case 'n': /* Any kind of white-space. */ 344214979Sdes case 't': 345214979Sdes _LEGAL_ALT(0); 346214979Sdes while (isspace(*bp)) 347214979Sdes bp++; 348214979Sdes break; 349214979Sdes 350214979Sdes 351214979Sdes default: /* Unknown/unsupported conversion. */ 352214979Sdes return (NULL); 353214979Sdes } 354214979Sdes 355214979Sdes 356214979Sdes } 357214979Sdes 358214979Sdes /* 359214979Sdes * We need to evaluate the two digit year spec (%y) 360214979Sdes * last as we can get a century spec (%C) at any time. 361214979Sdes */ 362214979Sdes if (relyear != -1) { 363214979Sdes if (century == TM_YEAR_BASE) { 364214979Sdes if (relyear <= 68) 365214979Sdes tm->tm_year = relyear + 2000 - TM_YEAR_BASE; 366214979Sdes else 367214979Sdes tm->tm_year = relyear + 1900 - TM_YEAR_BASE; 368214979Sdes } else { 369214979Sdes tm->tm_year = relyear + century - TM_YEAR_BASE; 370214979Sdes } 371214979Sdes } 372214979Sdes 373214979Sdes return ((char *)bp); 374214979Sdes} 375214979Sdes 376214979Sdes 377214979Sdesstatic int 378214979Sdes_conv_num(const unsigned char **buf, int *dest, int llim, int ulim) 379214979Sdes{ 380214979Sdes int result = 0; 381214979Sdes int rulim = ulim; 382214979Sdes 383214979Sdes if (**buf < '0' || **buf > '9') 384214979Sdes return (0); 385214979Sdes 386214979Sdes /* we use rulim to break out of the loop when we run out of digits */ 387214979Sdes do { 388214979Sdes result *= 10; 389214979Sdes result += *(*buf)++ - '0'; 390214979Sdes rulim /= 10; 391214979Sdes } while ((result * 10 <= ulim) && rulim && **buf >= '0' && **buf <= '9'); 392214979Sdes 393214979Sdes if (result < llim || result > ulim) 394214979Sdes return (0); 395214979Sdes 396214979Sdes *dest = result; 397214979Sdes return (1); 398214979Sdes} 399214979Sdes 400214979Sdes#endif /* HAVE_STRPTIME */ 401214979Sdes 402