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 #include <config.h> 34 #include "roken.h" 35 #ifdef TEST_STRPFTIME 36 #include "strpftime-test.h" 37 #endif 38 #include <ctype.h> 39 40 static const char *abb_weekdays[] = { 41 "Sun", 42 "Mon", 43 "Tue", 44 "Wed", 45 "Thu", 46 "Fri", 47 "Sat", 48 NULL 49 }; 50 51 static const char *full_weekdays[] = { 52 "Sunday", 53 "Monday", 54 "Tuesday", 55 "Wednesday", 56 "Thursday", 57 "Friday", 58 "Saturday", 59 NULL 60 }; 61 62 static const char *abb_month[] = { 63 "Jan", 64 "Feb", 65 "Mar", 66 "Apr", 67 "May", 68 "Jun", 69 "Jul", 70 "Aug", 71 "Sep", 72 "Oct", 73 "Nov", 74 "Dec", 75 NULL 76 }; 77 78 static const char *full_month[] = { 79 "January", 80 "February", 81 "March", 82 "April", 83 "May", 84 "June", 85 "July", 86 "August", 87 "September", 88 "October", 89 "November", 90 "December", 91 NULL, 92 }; 93 94 static const char *ampm[] = { 95 "am", 96 "pm", 97 NULL 98 }; 99 100 /* 101 * Try to match `*buf' to one of the strings in `strs'. Return the 102 * index of the matching string (or -1 if none). Also advance buf. 103 */ 104 105 static int 106 match_string (const char **buf, const char **strs) 107 { 108 int i = 0; 109 110 for (i = 0; strs[i] != NULL; ++i) { 111 int len = strlen (strs[i]); 112 113 if (strncasecmp (*buf, strs[i], len) == 0) { 114 *buf += len; 115 return i; 116 } 117 } 118 return -1; 119 } 120 121 /* 122 * Try to match `*buf' to at the most `n' characters and return the 123 * resulting number in `num'. Returns 0 or an error. Also advance 124 * buf. 125 */ 126 127 static int 128 parse_number (const char **buf, int n, int *num) 129 { 130 char *s, *str; 131 int i; 132 133 str = malloc(n + 1); 134 if (str == NULL) 135 return -1; 136 137 /* skip whitespace */ 138 for (; **buf != '\0' && isspace((unsigned char)(**buf)); (*buf)++) 139 ; 140 141 /* parse at least n characters */ 142 for (i = 0; **buf != '\0' && i < n && isdigit((unsigned char)(**buf)); i++, (*buf)++) 143 str[i] = **buf; 144 str[i] = '\0'; 145 146 *num = strtol (str, &s, 10); 147 free(str); 148 if (s == str) 149 return -1; 150 151 return 0; 152 } 153 154 /* 155 * tm_year is relative this year 156 */ 157 158 const int tm_year_base = 1900; 159 160 /* 161 * Return TRUE iff `year' was a leap year. 162 */ 163 164 static int 165 is_leap_year (int year) 166 { 167 return (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0); 168 } 169 170 /* 171 * Return the weekday [0,6] (0 = Sunday) of the first day of `year' 172 */ 173 174 static int 175 first_day (int year) 176 { 177 int ret = 4; 178 179 for (; year > 1970; --year) 180 ret = (ret + (is_leap_year (year) ? 366 : 365)) % 7; 181 return ret; 182 } 183 184 /* 185 * Set `timeptr' given `wnum' (week number [0, 53]) 186 */ 187 188 static void 189 set_week_number_sun (struct tm *timeptr, int wnum) 190 { 191 int fday = first_day (timeptr->tm_year + tm_year_base); 192 193 timeptr->tm_yday = wnum * 7 + timeptr->tm_wday - fday; 194 if (timeptr->tm_yday < 0) { 195 timeptr->tm_wday = fday; 196 timeptr->tm_yday = 0; 197 } 198 } 199 200 /* 201 * Set `timeptr' given `wnum' (week number [0, 53]) 202 */ 203 204 static void 205 set_week_number_mon (struct tm *timeptr, int wnum) 206 { 207 int fday = (first_day (timeptr->tm_year + tm_year_base) + 6) % 7; 208 209 timeptr->tm_yday = wnum * 7 + (timeptr->tm_wday + 6) % 7 - fday; 210 if (timeptr->tm_yday < 0) { 211 timeptr->tm_wday = (fday + 1) % 7; 212 timeptr->tm_yday = 0; 213 } 214 } 215 216 /* 217 * Set `timeptr' given `wnum' (week number [0, 53]) 218 */ 219 220 static void 221 set_week_number_mon4 (struct tm *timeptr, int wnum) 222 { 223 int fday = (first_day (timeptr->tm_year + tm_year_base) + 6) % 7; 224 int offset = 0; 225 226 if (fday < 4) 227 offset += 7; 228 229 timeptr->tm_yday = offset + (wnum - 1) * 7 + timeptr->tm_wday - fday; 230 if (timeptr->tm_yday < 0) { 231 timeptr->tm_wday = fday; 232 timeptr->tm_yday = 0; 233 } 234 } 235 236 /* 237 * 238 */ 239 240 ROKEN_LIB_FUNCTION char * ROKEN_LIB_CALL 241 strptime (const char *buf, const char *format, struct tm *timeptr) 242 { 243 char c; 244 245 for (; (c = *format) != '\0'; ++format) { 246 char *s; 247 int ret; 248 249 if (isspace ((unsigned char)c)) { 250 while (isspace ((unsigned char)*buf)) 251 ++buf; 252 } else if (c == '%' && format[1] != '\0') { 253 c = *++format; 254 if (c == 'E' || c == 'O') 255 c = *++format; 256 switch (c) { 257 case 'A' : 258 ret = match_string (&buf, full_weekdays); 259 if (ret < 0) 260 return NULL; 261 timeptr->tm_wday = ret; 262 break; 263 case 'a' : 264 ret = match_string (&buf, abb_weekdays); 265 if (ret < 0) 266 return NULL; 267 timeptr->tm_wday = ret; 268 break; 269 case 'B' : 270 ret = match_string (&buf, full_month); 271 if (ret < 0) 272 return NULL; 273 timeptr->tm_mon = ret; 274 break; 275 case 'b' : 276 case 'h' : 277 ret = match_string (&buf, abb_month); 278 if (ret < 0) 279 return NULL; 280 timeptr->tm_mon = ret; 281 break; 282 case 'C' : 283 if (parse_number(&buf, 2, &ret)) 284 return NULL; 285 timeptr->tm_year = (ret * 100) - tm_year_base; 286 break; 287 case 'c' : 288 abort (); 289 case 'D' : /* %m/%d/%y */ 290 s = strptime (buf, "%m/%d/%y", timeptr); 291 if (s == NULL) 292 return NULL; 293 buf = s; 294 break; 295 case 'd' : 296 case 'e' : 297 if (parse_number(&buf, 2, &ret)) 298 return NULL; 299 timeptr->tm_mday = ret; 300 break; 301 case 'H' : 302 case 'k' : 303 if (parse_number(&buf, 2, &ret)) 304 return NULL; 305 timeptr->tm_hour = ret; 306 break; 307 case 'I' : 308 case 'l' : 309 if (parse_number(&buf, 2, &ret)) 310 return NULL; 311 if (ret == 12) 312 timeptr->tm_hour = 0; 313 else 314 timeptr->tm_hour = ret; 315 break; 316 case 'j' : 317 if (parse_number(&buf, 3, &ret)) 318 return NULL; 319 if (ret == 0) 320 return NULL; 321 timeptr->tm_yday = ret - 1; 322 break; 323 case 'm' : 324 if (parse_number(&buf, 2, &ret)) 325 return NULL; 326 if (ret == 0) 327 return NULL; 328 timeptr->tm_mon = ret - 1; 329 break; 330 case 'M' : 331 if (parse_number(&buf, 2, &ret)) 332 return NULL; 333 timeptr->tm_min = ret; 334 break; 335 case 'n' : 336 while (isspace ((unsigned char)*buf)) 337 buf++; 338 break; 339 case 'p' : 340 ret = match_string (&buf, ampm); 341 if (ret < 0) 342 return NULL; 343 if (timeptr->tm_hour == 0) { 344 if (ret == 1) 345 timeptr->tm_hour = 12; 346 } else 347 timeptr->tm_hour += 12; 348 break; 349 case 'r' : /* %I:%M:%S %p */ 350 s = strptime (buf, "%I:%M:%S %p", timeptr); 351 if (s == NULL) 352 return NULL; 353 buf = s; 354 break; 355 case 'R' : /* %H:%M */ 356 s = strptime (buf, "%H:%M", timeptr); 357 if (s == NULL) 358 return NULL; 359 buf = s; 360 break; 361 case 'S' : 362 if (parse_number(&buf, 2, &ret)) 363 return NULL; 364 timeptr->tm_sec = ret; 365 break; 366 case 't' : 367 while (isspace ((unsigned char)*buf)) 368 buf++; 369 break; 370 case 'T' : /* %H:%M:%S */ 371 case 'X' : 372 s = strptime (buf, "%H:%M:%S", timeptr); 373 if (s == NULL) 374 return NULL; 375 buf = s; 376 break; 377 case 'u' : 378 if (parse_number(&buf, 1, &ret)) 379 return NULL; 380 if (ret <= 0) 381 return NULL; 382 timeptr->tm_wday = ret - 1; 383 break; 384 case 'w' : 385 if (parse_number(&buf, 1, &ret)) 386 return NULL; 387 timeptr->tm_wday = ret; 388 break; 389 case 'U' : 390 if (parse_number(&buf, 2, &ret)) 391 return NULL; 392 set_week_number_sun (timeptr, ret); 393 break; 394 case 'V' : 395 if (parse_number(&buf, 2, &ret)) 396 return NULL; 397 set_week_number_mon4 (timeptr, ret); 398 break; 399 case 'W' : 400 if (parse_number(&buf, 2, &ret)) 401 return NULL; 402 set_week_number_mon (timeptr, ret); 403 break; 404 case 'x' : 405 s = strptime (buf, "%Y:%m:%d", timeptr); 406 if (s == NULL) 407 return NULL; 408 buf = s; 409 break; 410 case 'y' : 411 if (parse_number(&buf, 2, &ret)) 412 return NULL; 413 if (ret < 70) 414 timeptr->tm_year = 100 + ret; 415 else 416 timeptr->tm_year = ret; 417 break; 418 case 'Y' : 419 if (parse_number(&buf, 4, &ret)) 420 return NULL; 421 timeptr->tm_year = ret - tm_year_base; 422 break; 423 case 'Z' : 424 abort (); 425 case '\0' : 426 --format; 427 /* FALLTHROUGH */ 428 case '%' : 429 if (*buf == '%') 430 ++buf; 431 else 432 return NULL; 433 break; 434 default : 435 if (*buf == '%' || *++buf == c) 436 ++buf; 437 else 438 return NULL; 439 break; 440 } 441 } else { 442 if (*buf == c) 443 ++buf; 444 else 445 return NULL; 446 } 447 } 448 return rk_UNCONST(buf); 449 } 450