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