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