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