1 /* $NetBSD: strptime.c,v 1.1.1.1 2011/04/13 18:15:43 elric Exp $ */ 2 3 /* 4 * Copyright (c) 1999, 2003, 2005 Kungliga Tekniska Högskolan 5 * (Royal Institute of Technology, Stockholm, Sweden). 6 * All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 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 * 3. Neither the name of KTH nor the names of its contributors may be 20 * used to endorse or promote products derived from this software without 21 * specific prior written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY KTH AND ITS CONTRIBUTORS ``AS IS'' AND ANY 24 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 26 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KTH OR ITS CONTRIBUTORS BE 27 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 28 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 29 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 30 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 31 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 32 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 33 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ 34 35 #include <config.h> 36 #include <krb5/roken.h> 37 #ifdef TEST_STRPFTIME 38 #include "strpftime-test.h" 39 #endif 40 #include <ctype.h> 41 42 static const char *abb_weekdays[] = { 43 "Sun", 44 "Mon", 45 "Tue", 46 "Wed", 47 "Thu", 48 "Fri", 49 "Sat", 50 NULL 51 }; 52 53 static const char *full_weekdays[] = { 54 "Sunday", 55 "Monday", 56 "Tuesday", 57 "Wednesday", 58 "Thursday", 59 "Friday", 60 "Saturday", 61 NULL 62 }; 63 64 static const char *abb_month[] = { 65 "Jan", 66 "Feb", 67 "Mar", 68 "Apr", 69 "May", 70 "Jun", 71 "Jul", 72 "Aug", 73 "Sep", 74 "Oct", 75 "Nov", 76 "Dec", 77 NULL 78 }; 79 80 static const char *full_month[] = { 81 "January", 82 "February", 83 "March", 84 "April", 85 "May", 86 "June", 87 "July", 88 "August", 89 "September", 90 "October", 91 "November", 92 "December", 93 NULL, 94 }; 95 96 static const char *ampm[] = { 97 "am", 98 "pm", 99 NULL 100 }; 101 102 /* 103 * Try to match `*buf' to one of the strings in `strs'. Return the 104 * index of the matching string (or -1 if none). Also advance buf. 105 */ 106 107 static int 108 match_string (const char **buf, const char **strs) 109 { 110 int i = 0; 111 112 for (i = 0; strs[i] != NULL; ++i) { 113 int len = strlen (strs[i]); 114 115 if (strncasecmp (*buf, strs[i], len) == 0) { 116 *buf += len; 117 return i; 118 } 119 } 120 return -1; 121 } 122 123 /* 124 * Try to match `*buf' to at the most `n' characters and return the 125 * resulting number in `num'. Returns 0 or an error. Also advance 126 * buf. 127 */ 128 129 static int 130 parse_number (const char **buf, int n, int *num) 131 { 132 char *s, *str; 133 int i; 134 135 str = malloc(n + 1); 136 if (str == NULL) 137 return -1; 138 139 /* skip whitespace */ 140 for (; **buf != '\0' && isspace((unsigned char)(**buf)); (*buf)++) 141 ; 142 143 /* parse at least n characters */ 144 for (i = 0; **buf != '\0' && i < n && isdigit((unsigned char)(**buf)); i++, (*buf)++) 145 str[i] = **buf; 146 str[i] = '\0'; 147 148 *num = strtol (str, &s, 10); 149 free(str); 150 if (s == str) 151 return -1; 152 153 return 0; 154 } 155 156 /* 157 * tm_year is relative this year 158 */ 159 160 const int tm_year_base = 1900; 161 162 /* 163 * Return TRUE iff `year' was a leap year. 164 */ 165 166 static int 167 is_leap_year (int year) 168 { 169 return (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0); 170 } 171 172 /* 173 * Return the weekday [0,6] (0 = Sunday) of the first day of `year' 174 */ 175 176 static int 177 first_day (int year) 178 { 179 int ret = 4; 180 181 for (; year > 1970; --year) 182 ret = (ret + (is_leap_year (year) ? 366 : 365)) % 7; 183 return ret; 184 } 185 186 /* 187 * Set `timeptr' given `wnum' (week number [0, 53]) 188 */ 189 190 static void 191 set_week_number_sun (struct tm *timeptr, int wnum) 192 { 193 int fday = first_day (timeptr->tm_year + tm_year_base); 194 195 timeptr->tm_yday = wnum * 7 + timeptr->tm_wday - fday; 196 if (timeptr->tm_yday < 0) { 197 timeptr->tm_wday = fday; 198 timeptr->tm_yday = 0; 199 } 200 } 201 202 /* 203 * Set `timeptr' given `wnum' (week number [0, 53]) 204 */ 205 206 static void 207 set_week_number_mon (struct tm *timeptr, int wnum) 208 { 209 int fday = (first_day (timeptr->tm_year + tm_year_base) + 6) % 7; 210 211 timeptr->tm_yday = wnum * 7 + (timeptr->tm_wday + 6) % 7 - fday; 212 if (timeptr->tm_yday < 0) { 213 timeptr->tm_wday = (fday + 1) % 7; 214 timeptr->tm_yday = 0; 215 } 216 } 217 218 /* 219 * Set `timeptr' given `wnum' (week number [0, 53]) 220 */ 221 222 static void 223 set_week_number_mon4 (struct tm *timeptr, int wnum) 224 { 225 int fday = (first_day (timeptr->tm_year + tm_year_base) + 6) % 7; 226 int offset = 0; 227 228 if (fday < 4) 229 offset += 7; 230 231 timeptr->tm_yday = offset + (wnum - 1) * 7 + timeptr->tm_wday - fday; 232 if (timeptr->tm_yday < 0) { 233 timeptr->tm_wday = fday; 234 timeptr->tm_yday = 0; 235 } 236 } 237 238 /* 239 * 240 */ 241 242 ROKEN_LIB_FUNCTION char * ROKEN_LIB_CALL 243 strptime (const char *buf, const char *format, struct tm *timeptr) 244 { 245 char c; 246 247 for (; (c = *format) != '\0'; ++format) { 248 char *s; 249 int ret; 250 251 if (isspace ((unsigned char)c)) { 252 while (isspace ((unsigned char)*buf)) 253 ++buf; 254 } else if (c == '%' && format[1] != '\0') { 255 c = *++format; 256 if (c == 'E' || c == 'O') 257 c = *++format; 258 switch (c) { 259 case 'A' : 260 ret = match_string (&buf, full_weekdays); 261 if (ret < 0) 262 return NULL; 263 timeptr->tm_wday = ret; 264 break; 265 case 'a' : 266 ret = match_string (&buf, abb_weekdays); 267 if (ret < 0) 268 return NULL; 269 timeptr->tm_wday = ret; 270 break; 271 case 'B' : 272 ret = match_string (&buf, full_month); 273 if (ret < 0) 274 return NULL; 275 timeptr->tm_mon = ret; 276 break; 277 case 'b' : 278 case 'h' : 279 ret = match_string (&buf, abb_month); 280 if (ret < 0) 281 return NULL; 282 timeptr->tm_mon = ret; 283 break; 284 case 'C' : 285 if (parse_number(&buf, 2, &ret)) 286 return NULL; 287 timeptr->tm_year = (ret * 100) - tm_year_base; 288 break; 289 case 'c' : 290 abort (); 291 case 'D' : /* %m/%d/%y */ 292 s = strptime (buf, "%m/%d/%y", timeptr); 293 if (s == NULL) 294 return NULL; 295 buf = s; 296 break; 297 case 'd' : 298 case 'e' : 299 if (parse_number(&buf, 2, &ret)) 300 return NULL; 301 timeptr->tm_mday = ret; 302 break; 303 case 'H' : 304 case 'k' : 305 if (parse_number(&buf, 2, &ret)) 306 return NULL; 307 timeptr->tm_hour = ret; 308 break; 309 case 'I' : 310 case 'l' : 311 if (parse_number(&buf, 2, &ret)) 312 return NULL; 313 if (ret == 12) 314 timeptr->tm_hour = 0; 315 else 316 timeptr->tm_hour = ret; 317 break; 318 case 'j' : 319 if (parse_number(&buf, 3, &ret)) 320 return NULL; 321 if (ret == 0) 322 return NULL; 323 timeptr->tm_yday = ret - 1; 324 break; 325 case 'm' : 326 if (parse_number(&buf, 2, &ret)) 327 return NULL; 328 if (ret == 0) 329 return NULL; 330 timeptr->tm_mon = ret - 1; 331 break; 332 case 'M' : 333 if (parse_number(&buf, 2, &ret)) 334 return NULL; 335 timeptr->tm_min = ret; 336 break; 337 case 'n' : 338 while (isspace ((unsigned char)*buf)) 339 buf++; 340 break; 341 case 'p' : 342 ret = match_string (&buf, ampm); 343 if (ret < 0) 344 return NULL; 345 if (timeptr->tm_hour == 0) { 346 if (ret == 1) 347 timeptr->tm_hour = 12; 348 } else 349 timeptr->tm_hour += 12; 350 break; 351 case 'r' : /* %I:%M:%S %p */ 352 s = strptime (buf, "%I:%M:%S %p", timeptr); 353 if (s == NULL) 354 return NULL; 355 buf = s; 356 break; 357 case 'R' : /* %H:%M */ 358 s = strptime (buf, "%H:%M", timeptr); 359 if (s == NULL) 360 return NULL; 361 buf = s; 362 break; 363 case 'S' : 364 if (parse_number(&buf, 2, &ret)) 365 return NULL; 366 timeptr->tm_sec = ret; 367 break; 368 case 't' : 369 while (isspace ((unsigned char)*buf)) 370 buf++; 371 break; 372 case 'T' : /* %H:%M:%S */ 373 case 'X' : 374 s = strptime (buf, "%H:%M:%S", timeptr); 375 if (s == NULL) 376 return NULL; 377 buf = s; 378 break; 379 case 'u' : 380 if (parse_number(&buf, 1, &ret)) 381 return NULL; 382 if (ret <= 0) 383 return NULL; 384 timeptr->tm_wday = ret - 1; 385 break; 386 case 'w' : 387 if (parse_number(&buf, 1, &ret)) 388 return NULL; 389 timeptr->tm_wday = ret; 390 break; 391 case 'U' : 392 if (parse_number(&buf, 2, &ret)) 393 return NULL; 394 set_week_number_sun (timeptr, ret); 395 break; 396 case 'V' : 397 if (parse_number(&buf, 2, &ret)) 398 return NULL; 399 set_week_number_mon4 (timeptr, ret); 400 break; 401 case 'W' : 402 if (parse_number(&buf, 2, &ret)) 403 return NULL; 404 set_week_number_mon (timeptr, ret); 405 break; 406 case 'x' : 407 s = strptime (buf, "%Y:%m:%d", timeptr); 408 if (s == NULL) 409 return NULL; 410 buf = s; 411 break; 412 case 'y' : 413 if (parse_number(&buf, 2, &ret)) 414 return NULL; 415 if (ret < 70) 416 timeptr->tm_year = 100 + ret; 417 else 418 timeptr->tm_year = ret; 419 break; 420 case 'Y' : 421 if (parse_number(&buf, 4, &ret)) 422 return NULL; 423 timeptr->tm_year = ret - tm_year_base; 424 break; 425 case 'Z' : 426 abort (); 427 case '\0' : 428 --format; 429 /* FALLTHROUGH */ 430 case '%' : 431 if (*buf == '%') 432 ++buf; 433 else 434 return NULL; 435 break; 436 default : 437 if (*buf == '%' || *++buf == c) 438 ++buf; 439 else 440 return NULL; 441 break; 442 } 443 } else { 444 if (*buf == c) 445 ++buf; 446 else 447 return NULL; 448 } 449 } 450 return rk_UNCONST(buf); 451 } 452