155682Smarkm/* 2233294Sstas * Copyright (c) 1999, 2003, 2005 Kungliga Tekniska H��gskolan 3233294Sstas * (Royal Institute of Technology, Stockholm, Sweden). 4233294Sstas * All rights reserved. 555682Smarkm * 6233294Sstas * Redistribution and use in source and binary forms, with or without 7233294Sstas * modification, are permitted provided that the following conditions 8233294Sstas * are met: 955682Smarkm * 10233294Sstas * 1. Redistributions of source code must retain the above copyright 11233294Sstas * notice, this list of conditions and the following disclaimer. 1255682Smarkm * 13233294Sstas * 2. Redistributions in binary form must reproduce the above copyright 14233294Sstas * notice, this list of conditions and the following disclaimer in the 15233294Sstas * documentation and/or other materials provided with the distribution. 1655682Smarkm * 1755682Smarkm * 3. Neither the name of KTH nor the names of its contributors may be 1855682Smarkm * used to endorse or promote products derived from this software without 1955682Smarkm * specific prior written permission. 2055682Smarkm * 2155682Smarkm * THIS SOFTWARE IS PROVIDED BY KTH AND ITS CONTRIBUTORS ``AS IS'' AND ANY 2255682Smarkm * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 2355682Smarkm * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 2455682Smarkm * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KTH OR ITS CONTRIBUTORS BE 2555682Smarkm * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 2655682Smarkm * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 2755682Smarkm * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 2855682Smarkm * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 2955682Smarkm * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 3055682Smarkm * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 3155682Smarkm * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ 3255682Smarkm 3355682Smarkm#include <config.h> 34233294Sstas#include "roken.h" 35178825Sdfr#ifdef TEST_STRPFTIME 36178825Sdfr#include "strpftime-test.h" 37178825Sdfr#endif 3855682Smarkm#include <ctype.h> 3955682Smarkm 4055682Smarkmstatic const char *abb_weekdays[] = { 4155682Smarkm "Sun", 4255682Smarkm "Mon", 4355682Smarkm "Tue", 4455682Smarkm "Wed", 4555682Smarkm "Thu", 4655682Smarkm "Fri", 4755682Smarkm "Sat", 4855682Smarkm NULL 4955682Smarkm}; 5055682Smarkm 5155682Smarkmstatic const char *full_weekdays[] = { 5255682Smarkm "Sunday", 5355682Smarkm "Monday", 5455682Smarkm "Tuesday", 5555682Smarkm "Wednesday", 5655682Smarkm "Thursday", 5755682Smarkm "Friday", 5855682Smarkm "Saturday", 5955682Smarkm NULL 6055682Smarkm}; 6155682Smarkm 6255682Smarkmstatic const char *abb_month[] = { 6355682Smarkm "Jan", 6455682Smarkm "Feb", 6555682Smarkm "Mar", 6655682Smarkm "Apr", 6755682Smarkm "May", 6855682Smarkm "Jun", 6955682Smarkm "Jul", 7055682Smarkm "Aug", 7155682Smarkm "Sep", 7255682Smarkm "Oct", 7355682Smarkm "Nov", 7455682Smarkm "Dec", 7555682Smarkm NULL 7655682Smarkm}; 7755682Smarkm 7855682Smarkmstatic const char *full_month[] = { 7955682Smarkm "January", 8055682Smarkm "February", 81178825Sdfr "March", 8255682Smarkm "April", 8355682Smarkm "May", 8455682Smarkm "June", 8555682Smarkm "July", 8655682Smarkm "August", 8755682Smarkm "September", 8855682Smarkm "October", 8955682Smarkm "November", 9055682Smarkm "December", 9155682Smarkm NULL, 9255682Smarkm}; 9355682Smarkm 9455682Smarkmstatic const char *ampm[] = { 9555682Smarkm "am", 9655682Smarkm "pm", 9755682Smarkm NULL 9855682Smarkm}; 9955682Smarkm 10055682Smarkm/* 10155682Smarkm * Try to match `*buf' to one of the strings in `strs'. Return the 10255682Smarkm * index of the matching string (or -1 if none). Also advance buf. 10355682Smarkm */ 10455682Smarkm 10555682Smarkmstatic int 10655682Smarkmmatch_string (const char **buf, const char **strs) 10755682Smarkm{ 10855682Smarkm int i = 0; 10955682Smarkm 11055682Smarkm for (i = 0; strs[i] != NULL; ++i) { 11155682Smarkm int len = strlen (strs[i]); 11255682Smarkm 11355682Smarkm if (strncasecmp (*buf, strs[i], len) == 0) { 11455682Smarkm *buf += len; 11555682Smarkm return i; 11655682Smarkm } 11755682Smarkm } 11855682Smarkm return -1; 11955682Smarkm} 12055682Smarkm 12155682Smarkm/* 122178825Sdfr * Try to match `*buf' to at the most `n' characters and return the 123178825Sdfr * resulting number in `num'. Returns 0 or an error. Also advance 124178825Sdfr * buf. 125178825Sdfr */ 12655682Smarkm 127178825Sdfrstatic int 128178825Sdfrparse_number (const char **buf, int n, int *num) 129178825Sdfr{ 130178825Sdfr char *s, *str; 131178825Sdfr int i; 132178825Sdfr 133178825Sdfr str = malloc(n + 1); 134178825Sdfr if (str == NULL) 135178825Sdfr return -1; 136178825Sdfr 137178825Sdfr /* skip whitespace */ 138178825Sdfr for (; **buf != '\0' && isspace((unsigned char)(**buf)); (*buf)++) 139178825Sdfr ; 140178825Sdfr 141178825Sdfr /* parse at least n characters */ 142178825Sdfr for (i = 0; **buf != '\0' && i < n && isdigit((unsigned char)(**buf)); i++, (*buf)++) 143178825Sdfr str[i] = **buf; 144178825Sdfr str[i] = '\0'; 145178825Sdfr 146178825Sdfr *num = strtol (str, &s, 10); 147178825Sdfr free(str); 148178825Sdfr if (s == str) 149178825Sdfr return -1; 150178825Sdfr 151178825Sdfr return 0; 152178825Sdfr} 153178825Sdfr 154178825Sdfr/* 155178825Sdfr * tm_year is relative this year 156178825Sdfr */ 157178825Sdfr 15855682Smarkmconst int tm_year_base = 1900; 15955682Smarkm 16055682Smarkm/* 16155682Smarkm * Return TRUE iff `year' was a leap year. 16255682Smarkm */ 16355682Smarkm 16455682Smarkmstatic int 16555682Smarkmis_leap_year (int year) 16655682Smarkm{ 16755682Smarkm return (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0); 16855682Smarkm} 16955682Smarkm 17055682Smarkm/* 17155682Smarkm * Return the weekday [0,6] (0 = Sunday) of the first day of `year' 17255682Smarkm */ 17355682Smarkm 17455682Smarkmstatic int 17555682Smarkmfirst_day (int year) 17655682Smarkm{ 17755682Smarkm int ret = 4; 17855682Smarkm 17955682Smarkm for (; year > 1970; --year) 180233294Sstas ret = (ret + (is_leap_year (year) ? 366 : 365)) % 7; 18155682Smarkm return ret; 18255682Smarkm} 18355682Smarkm 18455682Smarkm/* 18555682Smarkm * Set `timeptr' given `wnum' (week number [0, 53]) 18655682Smarkm */ 18755682Smarkm 18855682Smarkmstatic void 18955682Smarkmset_week_number_sun (struct tm *timeptr, int wnum) 19055682Smarkm{ 19155682Smarkm int fday = first_day (timeptr->tm_year + tm_year_base); 19255682Smarkm 19355682Smarkm timeptr->tm_yday = wnum * 7 + timeptr->tm_wday - fday; 19455682Smarkm if (timeptr->tm_yday < 0) { 19555682Smarkm timeptr->tm_wday = fday; 19655682Smarkm timeptr->tm_yday = 0; 19755682Smarkm } 19855682Smarkm} 19955682Smarkm 20055682Smarkm/* 20155682Smarkm * Set `timeptr' given `wnum' (week number [0, 53]) 20255682Smarkm */ 20355682Smarkm 20455682Smarkmstatic void 20555682Smarkmset_week_number_mon (struct tm *timeptr, int wnum) 20655682Smarkm{ 20755682Smarkm int fday = (first_day (timeptr->tm_year + tm_year_base) + 6) % 7; 20855682Smarkm 20955682Smarkm timeptr->tm_yday = wnum * 7 + (timeptr->tm_wday + 6) % 7 - fday; 21055682Smarkm if (timeptr->tm_yday < 0) { 21155682Smarkm timeptr->tm_wday = (fday + 1) % 7; 21255682Smarkm timeptr->tm_yday = 0; 21355682Smarkm } 21455682Smarkm} 21555682Smarkm 21655682Smarkm/* 21755682Smarkm * Set `timeptr' given `wnum' (week number [0, 53]) 21855682Smarkm */ 21955682Smarkm 22055682Smarkmstatic void 22155682Smarkmset_week_number_mon4 (struct tm *timeptr, int wnum) 22255682Smarkm{ 22355682Smarkm int fday = (first_day (timeptr->tm_year + tm_year_base) + 6) % 7; 22455682Smarkm int offset = 0; 22555682Smarkm 22655682Smarkm if (fday < 4) 22755682Smarkm offset += 7; 22855682Smarkm 22955682Smarkm timeptr->tm_yday = offset + (wnum - 1) * 7 + timeptr->tm_wday - fday; 23055682Smarkm if (timeptr->tm_yday < 0) { 23155682Smarkm timeptr->tm_wday = fday; 23255682Smarkm timeptr->tm_yday = 0; 23355682Smarkm } 23455682Smarkm} 23555682Smarkm 23655682Smarkm/* 23755682Smarkm * 23855682Smarkm */ 23955682Smarkm 240233294SstasROKEN_LIB_FUNCTION char * ROKEN_LIB_CALL 24155682Smarkmstrptime (const char *buf, const char *format, struct tm *timeptr) 24255682Smarkm{ 24355682Smarkm char c; 24455682Smarkm 24555682Smarkm for (; (c = *format) != '\0'; ++format) { 24655682Smarkm char *s; 24755682Smarkm int ret; 24855682Smarkm 249178825Sdfr if (isspace ((unsigned char)c)) { 250178825Sdfr while (isspace ((unsigned char)*buf)) 25155682Smarkm ++buf; 25255682Smarkm } else if (c == '%' && format[1] != '\0') { 25355682Smarkm c = *++format; 25455682Smarkm if (c == 'E' || c == 'O') 25555682Smarkm c = *++format; 25655682Smarkm switch (c) { 25755682Smarkm case 'A' : 25855682Smarkm ret = match_string (&buf, full_weekdays); 25955682Smarkm if (ret < 0) 26055682Smarkm return NULL; 26155682Smarkm timeptr->tm_wday = ret; 26255682Smarkm break; 26355682Smarkm case 'a' : 26455682Smarkm ret = match_string (&buf, abb_weekdays); 26555682Smarkm if (ret < 0) 26655682Smarkm return NULL; 26755682Smarkm timeptr->tm_wday = ret; 26855682Smarkm break; 26955682Smarkm case 'B' : 27055682Smarkm ret = match_string (&buf, full_month); 27155682Smarkm if (ret < 0) 27255682Smarkm return NULL; 27355682Smarkm timeptr->tm_mon = ret; 27455682Smarkm break; 27555682Smarkm case 'b' : 27655682Smarkm case 'h' : 27755682Smarkm ret = match_string (&buf, abb_month); 27855682Smarkm if (ret < 0) 27955682Smarkm return NULL; 28055682Smarkm timeptr->tm_mon = ret; 28155682Smarkm break; 28255682Smarkm case 'C' : 283178825Sdfr if (parse_number(&buf, 2, &ret)) 28455682Smarkm return NULL; 28555682Smarkm timeptr->tm_year = (ret * 100) - tm_year_base; 28655682Smarkm break; 28755682Smarkm case 'c' : 28855682Smarkm abort (); 28955682Smarkm case 'D' : /* %m/%d/%y */ 29055682Smarkm s = strptime (buf, "%m/%d/%y", timeptr); 29155682Smarkm if (s == NULL) 29255682Smarkm return NULL; 29355682Smarkm buf = s; 29455682Smarkm break; 29555682Smarkm case 'd' : 29655682Smarkm case 'e' : 297178825Sdfr if (parse_number(&buf, 2, &ret)) 29855682Smarkm return NULL; 29955682Smarkm timeptr->tm_mday = ret; 30055682Smarkm break; 30155682Smarkm case 'H' : 30255682Smarkm case 'k' : 303178825Sdfr if (parse_number(&buf, 2, &ret)) 30455682Smarkm return NULL; 30555682Smarkm timeptr->tm_hour = ret; 30655682Smarkm break; 30755682Smarkm case 'I' : 30855682Smarkm case 'l' : 309178825Sdfr if (parse_number(&buf, 2, &ret)) 31055682Smarkm return NULL; 31155682Smarkm if (ret == 12) 31255682Smarkm timeptr->tm_hour = 0; 31355682Smarkm else 31455682Smarkm timeptr->tm_hour = ret; 31555682Smarkm break; 31655682Smarkm case 'j' : 317178825Sdfr if (parse_number(&buf, 3, &ret)) 31855682Smarkm return NULL; 319178825Sdfr if (ret == 0) 320178825Sdfr return NULL; 32155682Smarkm timeptr->tm_yday = ret - 1; 32255682Smarkm break; 32355682Smarkm case 'm' : 324178825Sdfr if (parse_number(&buf, 2, &ret)) 32555682Smarkm return NULL; 326178825Sdfr if (ret == 0) 327178825Sdfr return NULL; 32855682Smarkm timeptr->tm_mon = ret - 1; 32955682Smarkm break; 33055682Smarkm case 'M' : 331178825Sdfr if (parse_number(&buf, 2, &ret)) 33255682Smarkm return NULL; 33355682Smarkm timeptr->tm_min = ret; 33455682Smarkm break; 33555682Smarkm case 'n' : 336178825Sdfr while (isspace ((unsigned char)*buf)) 337178825Sdfr buf++; 33855682Smarkm break; 33955682Smarkm case 'p' : 34055682Smarkm ret = match_string (&buf, ampm); 34155682Smarkm if (ret < 0) 34255682Smarkm return NULL; 34355682Smarkm if (timeptr->tm_hour == 0) { 34455682Smarkm if (ret == 1) 34555682Smarkm timeptr->tm_hour = 12; 34655682Smarkm } else 34755682Smarkm timeptr->tm_hour += 12; 34855682Smarkm break; 34955682Smarkm case 'r' : /* %I:%M:%S %p */ 35055682Smarkm s = strptime (buf, "%I:%M:%S %p", timeptr); 35155682Smarkm if (s == NULL) 35255682Smarkm return NULL; 35355682Smarkm buf = s; 35455682Smarkm break; 35555682Smarkm case 'R' : /* %H:%M */ 35655682Smarkm s = strptime (buf, "%H:%M", timeptr); 35755682Smarkm if (s == NULL) 35855682Smarkm return NULL; 35955682Smarkm buf = s; 36055682Smarkm break; 36155682Smarkm case 'S' : 362178825Sdfr if (parse_number(&buf, 2, &ret)) 36355682Smarkm return NULL; 36455682Smarkm timeptr->tm_sec = ret; 36555682Smarkm break; 36655682Smarkm case 't' : 367178825Sdfr while (isspace ((unsigned char)*buf)) 368178825Sdfr buf++; 36955682Smarkm break; 37055682Smarkm case 'T' : /* %H:%M:%S */ 37155682Smarkm case 'X' : 37255682Smarkm s = strptime (buf, "%H:%M:%S", timeptr); 37355682Smarkm if (s == NULL) 37455682Smarkm return NULL; 37555682Smarkm buf = s; 37655682Smarkm break; 37755682Smarkm case 'u' : 378178825Sdfr if (parse_number(&buf, 1, &ret)) 37955682Smarkm return NULL; 380178825Sdfr if (ret <= 0) 381178825Sdfr return NULL; 38255682Smarkm timeptr->tm_wday = ret - 1; 38355682Smarkm break; 38455682Smarkm case 'w' : 385178825Sdfr if (parse_number(&buf, 1, &ret)) 38655682Smarkm return NULL; 38755682Smarkm timeptr->tm_wday = ret; 38855682Smarkm break; 38955682Smarkm case 'U' : 390178825Sdfr if (parse_number(&buf, 2, &ret)) 39155682Smarkm return NULL; 39255682Smarkm set_week_number_sun (timeptr, ret); 39355682Smarkm break; 39455682Smarkm case 'V' : 395178825Sdfr if (parse_number(&buf, 2, &ret)) 39655682Smarkm return NULL; 39755682Smarkm set_week_number_mon4 (timeptr, ret); 39855682Smarkm break; 39955682Smarkm case 'W' : 400178825Sdfr if (parse_number(&buf, 2, &ret)) 40155682Smarkm return NULL; 40255682Smarkm set_week_number_mon (timeptr, ret); 40355682Smarkm break; 40455682Smarkm case 'x' : 40555682Smarkm s = strptime (buf, "%Y:%m:%d", timeptr); 40655682Smarkm if (s == NULL) 40755682Smarkm return NULL; 40855682Smarkm buf = s; 40955682Smarkm break; 41055682Smarkm case 'y' : 411178825Sdfr if (parse_number(&buf, 2, &ret)) 41255682Smarkm return NULL; 41355682Smarkm if (ret < 70) 41455682Smarkm timeptr->tm_year = 100 + ret; 41555682Smarkm else 41655682Smarkm timeptr->tm_year = ret; 41755682Smarkm break; 41855682Smarkm case 'Y' : 419178825Sdfr if (parse_number(&buf, 4, &ret)) 42055682Smarkm return NULL; 42155682Smarkm timeptr->tm_year = ret - tm_year_base; 42255682Smarkm break; 42355682Smarkm case 'Z' : 42455682Smarkm abort (); 42555682Smarkm case '\0' : 42655682Smarkm --format; 42755682Smarkm /* FALLTHROUGH */ 42855682Smarkm case '%' : 42955682Smarkm if (*buf == '%') 43055682Smarkm ++buf; 43155682Smarkm else 43255682Smarkm return NULL; 43355682Smarkm break; 43455682Smarkm default : 43555682Smarkm if (*buf == '%' || *++buf == c) 43655682Smarkm ++buf; 43755682Smarkm else 43855682Smarkm return NULL; 43955682Smarkm break; 44055682Smarkm } 44155682Smarkm } else { 44255682Smarkm if (*buf == c) 44355682Smarkm ++buf; 44455682Smarkm else 44555682Smarkm return NULL; 44655682Smarkm } 44755682Smarkm } 448178825Sdfr return rk_UNCONST(buf); 44955682Smarkm} 450