1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 1997 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ 28 /* All Rights Reserved */ 29 30 #pragma ident "%Z%%M% %I% %E% SMI" 31 32 #if !defined(lint) && defined(SCCSIDS) 33 static char *sccsid = "%Z%%M% %I% %E% SMI"; 34 #endif 35 36 #include <ctype.h> 37 #include <locale.h> 38 #include <time.h> 39 40 static char *strmatch(/*char *cp, char *string*/); 41 static char *yearmatch(/*char *cp, char *format, struct tm *tm, 42 int *hadyearp*/); 43 static char *cvtnum(/*char *cp, int *nump*/); 44 static char *skipnws(/*char *format*/); 45 46 extern char *getlocale_time(); 47 #define NULL 0 48 49 char * 50 strptime(buf, format, tm) 51 char *buf; 52 char *format; 53 struct tm *tm; 54 { 55 register char *cp, *p; 56 register int c, ch; 57 register int i; 58 register struct dtconv *dtcp; 59 int hadyear; 60 61 (void) getlocale_time(); 62 dtcp = localdtconv(); /* get locale's strings */ 63 64 cp = buf; 65 while ((c = *format++) != '\0') { 66 if (c == '%') { 67 switch (*format++) { 68 69 case '%': /* Percent sign */ 70 if (*cp++ != '%') 71 return (NULL); 72 break; 73 74 case 'a': /* Abbreviated weekday name */ 75 case 'A': /* Weekday name */ 76 for (i = 0; i < 7; i++) { 77 if ((p = strmatch(cp, 78 dtcp->weekday_names[i], 79 *format)) != NULL 80 || (p = strmatch(cp, 81 dtcp->abbrev_weekday_names[i], 82 *format)) != NULL) 83 goto match_wday; 84 } 85 return (NULL); /* no match */ 86 87 match_wday: 88 tm->tm_wday = i; 89 cp = p; 90 break; 91 92 case 'h': 93 case 'b': /* Abbreviated month name */ 94 case 'B': /* Month name */ 95 for (i = 0; i < 12; i++) { 96 if ((p = strmatch(cp, 97 dtcp->month_names[i], 98 *format)) != NULL 99 || (p = strmatch(cp, 100 dtcp->abbrev_month_names[i], 101 *format)) != NULL) 102 goto match_month; 103 } 104 return (NULL); /* no match */ 105 106 match_month: 107 tm->tm_mon = i; 108 cp = p; 109 break; 110 111 case 'c': /* date and time representation */ 112 cp = strptime(cp, "%x %X", tm); 113 if (cp == NULL) 114 return (NULL); 115 break; 116 117 case 'C': /* long date and time representation */ 118 cp = strptime(cp, dtcp->ldate_format, tm); 119 if (cp == NULL) 120 return (NULL); 121 break; 122 123 case 'd': /* Day of month, with leading zero */ 124 case 'e': /* Day of month without leading zero */ 125 cp = cvtnum(cp, &tm->tm_mday); 126 if (cp == NULL) 127 return (NULL); /* no digits */ 128 if (tm->tm_mday > 31) 129 return (NULL); 130 if ((c = *cp) == '\0' 131 || isspace((unsigned char)c)) 132 format = skipnws(format); 133 break; 134 135 case 'D': /* Shorthand for %m/%d/%y */ 136 cp = strptime(cp, "%m/%d/%y", tm); 137 if (cp == NULL) 138 return (NULL); 139 break; 140 141 case 'H': /* Hour (24 hour version) */ 142 case 'k': /* Hour (24 hour version) */ 143 cp = cvtnum(cp, &tm->tm_hour); 144 if (cp == NULL) 145 return (NULL); /* no digits */ 146 if (tm->tm_hour > 23) 147 return (NULL); 148 if ((c = *cp) == '\0' 149 || isspace((unsigned char)c)) 150 format = skipnws(format); 151 break; 152 153 case 'I': /* Hour (12 hour version) */ 154 case 'l': /* Hour (12 hour version) */ 155 cp = cvtnum(cp, &tm->tm_hour); 156 if (cp == NULL) 157 return (NULL); /* no digits */ 158 if (tm->tm_hour == 12) 159 tm->tm_hour = 0; 160 else if (tm->tm_hour > 11) 161 return (NULL); 162 if ((c = *cp) == '\0' 163 || isspace((unsigned char)c)) 164 format = skipnws(format); 165 break; 166 167 case 'j': /* Julian date */ 168 cp = cvtnum(cp, &tm->tm_yday); 169 if (cp == NULL) 170 return (NULL); /* no digits */ 171 if (tm->tm_yday > 365) 172 return (NULL); 173 break; 174 175 case 'm': /* Month number */ 176 cp = cvtnum(cp, &tm->tm_mon); 177 if (cp == NULL) 178 return (NULL); /* no digits */ 179 tm->tm_mon--; 180 if (tm->tm_mon < 0 || tm->tm_mon > 11) 181 return (NULL); 182 if ((c = *cp) == '\0' 183 || isspace((unsigned char)c)) 184 format = skipnws(format); 185 break; 186 187 case 'M': /* Minute */ 188 /* 189 * This is optional; if we're at the end of the 190 * string, or the next character is white 191 * space, don't try to match it. 192 */ 193 if ((c = *cp) != '\0' 194 && !isspace((unsigned char)c)) { 195 cp = cvtnum(cp, &tm->tm_min); 196 if (cp == NULL) 197 return (NULL); /* no digits */ 198 if (tm->tm_min > 59) 199 return (NULL); 200 } 201 if ((c = *cp) == '\0' 202 || isspace((unsigned char)c)) 203 format = skipnws(format); 204 break; 205 206 case 'p': /* AM or PM */ 207 if ((p = strmatch(cp, dtcp->am_string, 208 *format)) != NULL) { 209 /* 210 * AM. 211 */ 212 if (tm->tm_hour == 12) 213 tm->tm_hour = 0; 214 cp = p; 215 } else if ((p = strmatch(cp, dtcp->pm_string, 216 *format)) != NULL) { 217 /* 218 * PM. 219 */ 220 if (tm->tm_hour > 12) 221 return (NULL); /* error */ 222 else if (tm->tm_hour != 12) 223 tm->tm_hour += 12; 224 cp = p; 225 } 226 break; 227 228 case 'r': /* Shorthand for %I:%M:%S AM or PM */ 229 cp = strptime(cp, "%I:%M:%S %p", tm); 230 if (cp == NULL) 231 return (NULL); 232 break; 233 234 case 'R': /* Time as %H:%M */ 235 cp = strptime(cp, "%H:%M", tm); 236 if (cp == NULL) 237 return (NULL); 238 break; 239 240 case 'S': /* Seconds */ 241 /* 242 * This is optional; if we're at the end of the 243 * string, or the next character is white 244 * space, don't try to match it. 245 */ 246 if ((c = *cp) != '\0' 247 && !isspace((unsigned char)c)) { 248 cp = cvtnum(cp, &tm->tm_sec); 249 if (cp == NULL) 250 return (NULL); /* no digits */ 251 if (tm->tm_sec > 59) 252 return (NULL); 253 } 254 if ((c = *cp) == '\0' 255 || isspace((unsigned char)c)) 256 format = skipnws(format); 257 break; 258 259 case 'T': /* Shorthand for %H:%M:%S */ 260 cp = strptime(cp, "%H:%M:%S", tm); 261 if (cp == NULL) 262 return (NULL); 263 break; 264 265 case 'x': /* Localized date format */ 266 cp = strptime(cp, dtcp->sdate_format, tm); 267 if (cp == NULL) 268 return (NULL); 269 break; 270 271 case 'X': /* Localized time format */ 272 cp = strptime(cp, dtcp->time_format, tm); 273 if (cp == NULL) 274 return (NULL); 275 break; 276 277 case 'y': /* Year in the form yy */ 278 cp = yearmatch(cp, format, tm, &hadyear); 279 if (cp == NULL) 280 return (NULL); 281 if (hadyear) { 282 if (tm->tm_year < 69) 283 tm->tm_year += 100; 284 } 285 return (cp); /* match is complete */ 286 287 case 'Y': /* Year in the form ccyy */ 288 cp = yearmatch(cp, format, tm, &hadyear); 289 if (cp == NULL) 290 return (NULL); 291 if (hadyear) { 292 tm->tm_year -= 1900; 293 if (tm->tm_year < 0) 294 return (NULL); 295 } 296 return (cp); /* match is complete */ 297 298 default: 299 return (NULL); /* unknown conversion */ 300 } 301 } else { 302 if (isspace((unsigned char)c)) { 303 while ((ch = *cp++) != '\0' 304 && isspace((unsigned char)ch)) 305 ; 306 cp--; 307 } else { 308 if (*cp++ != c) 309 return (NULL); 310 } 311 } 312 } 313 return (cp); 314 } 315 316 /* 317 * Try to match the beginning of the string pointed to by "cp" with the string 318 * pointed to by "string". The match is independent of the case of either 319 * string. 320 * 321 * "termc" is the next character in the format string following the one for 322 * which this match is being done. If the match succeeds, make sure the next 323 * character after the match is either '\0', or that it would match "termc". 324 * 325 * If both matches succeed, return a pointer to the next character after the 326 * first match. Otherwise, return NULL. 327 */ 328 static char * 329 strmatch(cp, string, termc) 330 register char *cp; 331 register char *string; 332 char termc; 333 { 334 register unsigned char c, strc; 335 336 /* 337 * Match the beginning portion of "cp" with "string". 338 */ 339 while ((strc = *string++) != '\0') { 340 c = *cp++; 341 if (isupper(c)) 342 c = tolower(c); 343 if (isupper(strc)) 344 strc = tolower(strc); 345 if (c != strc) 346 return (NULL); 347 } 348 349 if ((c = *cp) != '\0') { 350 if (isspace((unsigned char)termc)) { 351 if (!isspace(c)) 352 return (NULL); 353 } else { 354 if (c != (unsigned char)termc) 355 return (NULL); 356 } 357 } 358 return (cp); 359 } 360 361 /* 362 * Try to match a %y or %Y specification. 363 * If it matches, try matching the rest of the format. If it succeeds, just 364 * return. Otherwise, try backing the scan up, ignoring the %y/%Y and any 365 * following non-white-space string. If that succeeds, just return. (This 366 * permits a missing year to be detected if it's at the beginning of a date, as 367 * well as if it's at the end of a date, so that formats such as "%Y/%m/%d" can 368 * match "3/14" and default the year.) 369 * 370 * Set "*hadyearp" to indicate whether a year was specified or not. 371 */ 372 static char * 373 yearmatch(cp, format, tm, hadyearp) 374 register char *cp; 375 char *format; 376 struct tm *tm; 377 int *hadyearp; 378 { 379 register int c; 380 char *savecp; 381 int saveyear; 382 383 /* 384 * This is optional; if we're at the end of the 385 * string, or the next character is white 386 * space, don't try to match it. 387 */ 388 if ((c = *cp) != '\0' && !isspace((unsigned char)c)) { 389 savecp = cp; 390 saveyear = tm->tm_year; 391 cp = cvtnum(cp, &tm->tm_year); 392 if (cp == NULL) 393 return (NULL); /* no digits */ 394 if ((c = *cp) == '\0' 395 || isspace((unsigned char)c)) 396 format = skipnws(format); 397 398 /* 399 * Year can also be optional if it's at 400 * the *beginning* of a date. We check 401 * this by trying to parse the rest of 402 * the date here. If we succeed, OK; 403 * otherwise, we skip over the %y and 404 * try again. 405 */ 406 cp = strptime(cp, format, tm); 407 if (cp != NULL) 408 *hadyearp = 1; 409 else { 410 *hadyearp = 0; 411 cp = savecp; 412 format = skipnws(format); 413 tm->tm_year = saveyear; 414 cp = strptime(cp, format, tm); 415 } 416 } else { 417 *hadyearp = 0; 418 if ((c = *cp) == '\0' 419 || isspace((unsigned char)c)) 420 format = skipnws(format); 421 cp = strptime(cp, format, tm); 422 } 423 424 return (cp); 425 } 426 427 /* 428 * Try to match a (decimal) number in the string pointed to by "cp". 429 * If the match succeeds, store the result in the "int" pointed to by "nump" 430 * and return a pointer to the character following the number in the string. 431 * If it fails, return NULL. 432 */ 433 static char * 434 cvtnum(cp, nump) 435 register char *cp; 436 int *nump; 437 { 438 register int c; 439 register int i; 440 441 c = (unsigned char)*cp++; 442 if (!isdigit(c)) 443 return (NULL); /* no digits */ 444 i = 0; 445 do { 446 i = i*10 + c - '0'; 447 c = (unsigned char)*cp++; 448 } while (isdigit(c)); 449 *nump = i; 450 return (cp - 1); 451 } 452 453 /* 454 * If a format item (such as %H, hours) is followed by a non-white-space 455 * character other than "%", and the part of the string that matched the format 456 * item is followed by white space, the string of non-white-space, 457 * non-format-item characters following that format item may be omitted. 458 */ 459 static char * 460 skipnws(format) 461 register char *format; 462 { 463 register char c; 464 465 /* 466 * Skip over non-white-space, non-digit characters. "%" is special. 467 */ 468 while ((c = *format) != '\0' && !isspace((unsigned char)c)) { 469 if (c == '%') { 470 /* 471 * This is a format item. If it's %%, skip it as 472 * that's a non-white space, non-digit character. 473 */ 474 if (*(format + 1) == '%') 475 format++; /* skip % */ 476 else 477 break; 478 } 479 format++; 480 } 481 482 return (format); 483 } 484