1/*
2 * Copyright (c) 1999 - 2002 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
39static const char *abb_weekdays[] = {
40    "Sun",
41    "Mon",
42    "Tue",
43    "Wed",
44    "Thu",
45    "Fri",
46    "Sat",
47};
48
49static const char *full_weekdays[] = {
50    "Sunday",
51    "Monday",
52    "Tuesday",
53    "Wednesday",
54    "Thursday",
55    "Friday",
56    "Saturday",
57};
58
59static const char *abb_month[] = {
60    "Jan",
61    "Feb",
62    "Mar",
63    "Apr",
64    "May",
65    "Jun",
66    "Jul",
67    "Aug",
68    "Sep",
69    "Oct",
70    "Nov",
71    "Dec"
72};
73
74static const char *full_month[] = {
75    "January",
76    "February",
77    "Mars",
78    "April",
79    "May",
80    "June",
81    "July",
82    "August",
83    "September",
84    "October",
85    "November",
86    "December"
87};
88
89static const char *ampm[] = {
90    "AM",
91    "PM"
92};
93
94/*
95 * Convert hour in [0, 24] to [12 1 - 11 12 1 - 11 12]
96 */
97
98static int
99hour_24to12 (int hour)
100{
101    int ret = hour % 12;
102
103    if (ret == 0)
104	ret = 12;
105    return ret;
106}
107
108/*
109 * Return AM or PM for `hour'
110 */
111
112static const char *
113hour_to_ampm (int hour)
114{
115    return ampm[hour / 12];
116}
117
118/*
119 * Return the week number of `tm' (Sunday being the first day of the week)
120 * as [0, 53]
121 */
122
123static int
124week_number_sun (const struct tm *tm)
125{
126    return (tm->tm_yday + 7 - (tm->tm_yday % 7 - tm->tm_wday + 7) % 7) / 7;
127}
128
129/*
130 * Return the week number of `tm' (Monday being the first day of the week)
131 * as [0, 53]
132 */
133
134static int
135week_number_mon (const struct tm *tm)
136{
137    int wday = (tm->tm_wday + 6) % 7;
138
139    return (tm->tm_yday + 7 - (tm->tm_yday % 7 - wday + 7) % 7) / 7;
140}
141
142/*
143 * Return the week number of `tm' (Monday being the first day of the
144 * week) as [01, 53].  Week number one is the one that has four or more
145 * days in that year.
146 */
147
148static int
149week_number_mon4 (const struct tm *tm)
150{
151    int wday  = (tm->tm_wday + 6) % 7;
152    int w1day = (wday - tm->tm_yday % 7 + 7) % 7;
153    int ret;
154
155    ret = (tm->tm_yday + w1day) / 7;
156    if (w1day >= 4)
157	--ret;
158    if (ret == -1)
159	ret = 53;
160    else
161	++ret;
162    return ret;
163}
164
165/*
166 *
167 */
168
169ROKEN_LIB_FUNCTION size_t ROKEN_LIB_CALL
170strftime (char *buf, size_t maxsize, const char *format,
171	  const struct tm *tm)
172{
173    size_t n = 0;
174    int ret;
175
176    while (*format != '\0' && n < maxsize) {
177	if (*format == '%') {
178	    ++format;
179	    if(*format == 'E' || *format == 'O')
180		++format;
181	    switch (*format) {
182	    case 'a' :
183		ret = snprintf (buf, maxsize - n,
184				"%s", abb_weekdays[tm->tm_wday]);
185		break;
186	    case 'A' :
187		ret = snprintf (buf, maxsize - n,
188				"%s", full_weekdays[tm->tm_wday]);
189		break;
190	    case 'h' :
191	    case 'b' :
192		ret = snprintf (buf, maxsize - n,
193				"%s", abb_month[tm->tm_mon]);
194		break;
195	    case 'B' :
196		ret = snprintf (buf, maxsize - n,
197				"%s", full_month[tm->tm_mon]);
198		break;
199	    case 'c' :
200		ret = snprintf (buf, maxsize - n,
201				"%d:%02d:%02d %02d:%02d:%02d",
202				tm->tm_year,
203				tm->tm_mon + 1,
204				tm->tm_mday,
205				tm->tm_hour,
206				tm->tm_min,
207				tm->tm_sec);
208		break;
209	    case 'C' :
210		ret = snprintf (buf, maxsize - n,
211				"%02d", (tm->tm_year + 1900) / 100);
212		break;
213	    case 'd' :
214		ret = snprintf (buf, maxsize - n,
215				"%02d", tm->tm_mday);
216		break;
217	    case 'D' :
218		ret = snprintf (buf, maxsize - n,
219				"%02d/%02d/%02d",
220				tm->tm_mon + 1,
221				tm->tm_mday,
222				(tm->tm_year + 1900) % 100);
223		break;
224	    case 'e' :
225		ret = snprintf (buf, maxsize - n,
226				"%2d", tm->tm_mday);
227		break;
228	    case 'F':
229		ret = snprintf (buf, maxsize - n,
230				"%04d-%02d-%02d", tm->tm_year + 1900,
231				tm->tm_mon + 1, tm->tm_mday);
232		break;
233	    case 'g':
234		/* last two digits of week-based year */
235		abort();
236	    case 'G':
237		/* week-based year */
238		abort();
239	    case 'H' :
240		ret = snprintf (buf, maxsize - n,
241				"%02d", tm->tm_hour);
242		break;
243	    case 'I' :
244		ret = snprintf (buf, maxsize - n,
245				"%02d",
246				hour_24to12 (tm->tm_hour));
247		break;
248	    case 'j' :
249		ret = snprintf (buf, maxsize - n,
250				"%03d", tm->tm_yday + 1);
251		break;
252	    case 'k' :
253		ret = snprintf (buf, maxsize - n,
254				"%2d", tm->tm_hour);
255		break;
256	    case 'l' :
257		ret = snprintf (buf, maxsize - n,
258				"%2d",
259				hour_24to12 (tm->tm_hour));
260		break;
261	    case 'm' :
262		ret = snprintf (buf, maxsize - n,
263				"%02d", tm->tm_mon + 1);
264		break;
265	    case 'M' :
266		ret = snprintf (buf, maxsize - n,
267				"%02d", tm->tm_min);
268		break;
269	    case 'n' :
270		ret = snprintf (buf, maxsize - n, "\n");
271		break;
272	    case 'p' :
273		ret = snprintf (buf, maxsize - n, "%s",
274				hour_to_ampm (tm->tm_hour));
275		break;
276	    case 'r' :
277		ret = snprintf (buf, maxsize - n,
278				"%02d:%02d:%02d %s",
279				hour_24to12 (tm->tm_hour),
280				tm->tm_min,
281				tm->tm_sec,
282				hour_to_ampm (tm->tm_hour));
283		break;
284	    case 'R' :
285		ret = snprintf (buf, maxsize - n,
286				"%02d:%02d",
287				tm->tm_hour,
288				tm->tm_min);
289		break;
290	    case 's' :
291		ret = snprintf (buf, maxsize - n,
292				"%d", (int)mktime(rk_UNCONST(tm)));
293		break;
294	    case 'S' :
295		ret = snprintf (buf, maxsize - n,
296				"%02d", tm->tm_sec);
297		break;
298	    case 't' :
299		ret = snprintf (buf, maxsize - n, "\t");
300		break;
301	    case 'T' :
302	    case 'X' :
303		ret = snprintf (buf, maxsize - n,
304				"%02d:%02d:%02d",
305				tm->tm_hour,
306				tm->tm_min,
307				tm->tm_sec);
308		break;
309	    case 'u' :
310		ret = snprintf (buf, maxsize - n,
311				"%d", (tm->tm_wday == 0) ? 7 : tm->tm_wday);
312		break;
313	    case 'U' :
314		ret = snprintf (buf, maxsize - n,
315				"%02d", week_number_sun (tm));
316		break;
317	    case 'V' :
318		ret = snprintf (buf, maxsize - n,
319				"%02d", week_number_mon4 (tm));
320		break;
321	    case 'w' :
322		ret = snprintf (buf, maxsize - n,
323				"%d", tm->tm_wday);
324		break;
325	    case 'W' :
326		ret = snprintf (buf, maxsize - n,
327				"%02d", week_number_mon (tm));
328		break;
329	    case 'x' :
330		ret = snprintf (buf, maxsize - n,
331				"%d:%02d:%02d",
332				tm->tm_year,
333				tm->tm_mon + 1,
334				tm->tm_mday);
335		break;
336	    case 'y' :
337		ret = snprintf (buf, maxsize - n,
338				"%02d", (tm->tm_year + 1900) % 100);
339		break;
340	    case 'Y' :
341		ret = snprintf (buf, maxsize - n,
342				"%d", tm->tm_year + 1900);
343		break;
344	    case 'z':
345		ret = snprintf (buf, maxsize - n,
346				"%ld",
347#if defined(HAVE_STRUCT_TM_TM_GMTOFF)
348				(long)tm->tm_gmtoff
349#elif defined(HAVE_TIMEZONE)
350#ifdef HAVE_ALTZONE
351				tm->tm_isdst ?
352				(long)altzone :
353#endif
354				(long)timezone
355#else
356#error Where in timezone chaos are you?
357#endif
358				);
359		break;
360	    case 'Z' :
361		ret = snprintf (buf, maxsize - n,
362				"%s",
363
364#if defined(HAVE_STRUCT_TM_TM_ZONE)
365				tm->tm_zone
366#elif defined(HAVE_TIMEZONE)
367				tzname[tm->tm_isdst]
368#else
369#error what?
370#endif
371		    );
372		break;
373	    case '\0' :
374		--format;
375		/* FALLTHROUGH */
376	    case '%' :
377		ret = snprintf (buf, maxsize - n,
378				"%%");
379		break;
380	    default :
381		ret = snprintf (buf, maxsize - n,
382				"%%%c", *format);
383		break;
384	    }
385	    if (ret < 0 || ret >= (int)(maxsize - n))
386		return 0;
387	    n   += ret;
388	    buf += ret;
389	    ++format;
390	} else {
391	    *buf++ = *format++;
392	    ++n;
393	}
394    }
395    *buf = '\0';
396    return n;
397}
398