fix off by 1 error
[rrdtool.git] / src / strftime.c
1 /**
2  *
3  * strftime.c
4  *
5  * implements the ansi c function strftime()
6  *
7  * written 6 september 1989 by jim nutt
8  * released into the public domain by jim nutt
9  *
10  * modified 21-Oct-89 by Rob Duff
11  *
12  * modified 08-Dec-04 by Tobi Oetiker (added %V)
13 **/
14
15 #include <stddef.h>     /* for size_t */
16 #include <stdarg.h>     /* for va_arg */
17 #include <time.h>       /* for struct tm */
18 #include "strftime.h"
19
20 /* Define your own defaults in config.h if necessary */
21 #if defined(TZNAME_STD) && defined(TZNAME_DST)
22 char     *tzname_[2] = { TZNAME_STD, TZNAME_DST };
23 #else
24 #define tzname_ tzname
25 #endif
26
27 static char *aday[] = {
28     "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
29 };
30
31 static char *day[] = {
32     "Sunday", "Monday", "Tuesday", "Wednesday",
33     "Thursday", "Friday", "Saturday"
34 };
35
36 static char *amonth[] = {
37     "Jan", "Feb", "Mar", "Apr", "May", "Jun",
38     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
39 };
40
41 static char *month[] = {
42     "January", "February", "March", "April", "May", "June",
43     "July", "August", "September", "October", "November", "December"
44 };
45
46 static char buf[26];
47
48 static void strfmt(
49     char *str,
50     const char *fmt,
51     ...);
52
53 /**
54  *
55  * size_t strftime_(char *str,
56  *                  size_t maxs,
57  *                  const char *fmt,
58  *                  const struct tm *t)
59  *
60  *      this functions acts much like a sprintf for time/date output.
61  *      given a pointer to an output buffer, a format string and a
62  *      time, it copies the time to the output buffer formatted in
63  *      accordance with the format string.  the parameters are used
64  *      as follows:
65  *
66  *          str is a pointer to the output buffer, there should
67  *          be at least maxs characters available at the address
68  *          pointed to by str.
69  *
70  *          maxs is the maximum number of characters to be copied
71  *          into the output buffer, included the '\0' terminator
72  *
73  *          fmt is the format string.  a percent sign (%) is used
74  *          to indicate that the following character is a special
75  *          format character.  the following are valid format
76  *          characters:
77  *
78  *              %A      full weekday name (Monday)
79  *              %a      abbreviated weekday name (Mon)
80  *              %B      full month name (January)
81  *              %b      abbreviated month name (Jan)
82  *              %c      standard date and time representation
83  *              %d      day-of-month (01-31)
84  *              %H      hour (24 hour clock) (00-23)
85  *              %I      hour (12 hour clock) (01-12)
86  *              %j      day-of-year (001-366)
87  *              %M      minute (00-59)
88  *              %m      month (01-12)
89  *              %p      local equivalent of AM or PM
90  *              %S      second (00-59)
91  *              %U      week-of-year, first day sunday (00-53)
92  *              %W      week-of-year, first day monday (00-53)
93  *              %V      ISO 8601 Week number 
94  *              %w      weekday (0-6, sunday is 0)
95  *              %X      standard time representation
96  *              %x      standard date representation
97  *              %Y      year with century
98  *              %y      year without century (00-99)
99  *              %Z      timezone name
100  *              %%      percent sign
101  *
102  *      the standard date string is equivalent to:
103  *
104  *          %a %b %d %Y
105  *
106  *      the standard time string is equivalent to:
107  *
108  *          %H:%M:%S
109  *
110  *      the standard date and time string is equivalent to:
111  *
112  *          %a %b %d %H:%M:%S %Y
113  *
114  *      strftime_() returns the number of characters placed in the
115  *      buffer, not including the terminating \0, or zero if more
116  *      than maxs characters were produced.
117  *
118 **/
119
120 size_t strftime_(
121     char *s,
122     size_t maxs,
123     const char *f,
124     const struct tm *t)
125 {
126     int       w, d;
127     char     *p, *q, *r;
128
129     p = s;
130     q = s + maxs - 1;
131     while ((*f != '\0')) {
132         if (*f++ == '%') {
133             r = buf;
134             switch (*f++) {
135             case '%':
136                 r = "%";
137                 break;
138
139             case 'a':
140                 r = aday[t->tm_wday];
141                 break;
142
143             case 'A':
144                 r = day[t->tm_wday];
145                 break;
146
147             case 'b':
148                 r = amonth[t->tm_mon];
149                 break;
150
151             case 'B':
152                 r = month[t->tm_mon];
153                 break;
154
155             case 'c':
156                 strfmt(r, "%0 %0 %2 %2:%2:%2 %4",
157                        aday[t->tm_wday], amonth[t->tm_mon],
158                        t->tm_mday, t->tm_hour, t->tm_min,
159                        t->tm_sec, t->tm_year + 1900);
160                 break;
161
162             case 'd':
163                 strfmt(r, "%2", t->tm_mday);
164                 break;
165
166             case 'H':
167                 strfmt(r, "%2", t->tm_hour);
168                 break;
169
170             case 'I':
171                 strfmt(r, "%2", (t->tm_hour % 12) ? t->tm_hour % 12 : 12);
172                 break;
173
174             case 'j':
175                 strfmt(r, "%3", t->tm_yday + 1);
176                 break;
177
178             case 'm':
179                 strfmt(r, "%2", t->tm_mon + 1);
180                 break;
181
182             case 'M':
183                 strfmt(r, "%2", t->tm_min);
184                 break;
185
186             case 'p':
187                 r = (t->tm_hour > 11) ? "PM" : "AM";
188                 break;
189
190             case 'S':
191                 strfmt(r, "%2", t->tm_sec);
192                 break;
193
194             case 'U':
195                 w = t->tm_yday / 7;
196                 if (t->tm_yday % 7 > t->tm_wday)
197                     w++;
198                 strfmt(r, "%2", w);
199                 break;
200
201             case 'W':
202                 w = t->tm_yday / 7;
203                 if (t->tm_yday % 7 > (t->tm_wday + 6) % 7)
204                     w++;
205                 strfmt(r, "%2", w);
206                 break;
207
208             case 'V':
209
210                 /* ISO 8601 Week Of Year:
211                    If the week (Monday - Sunday) containing January 1 has four or more
212                    days in the new year, then it is week 1; otherwise it is week 53 of
213                    the previous year and the next week is week one. */
214
215                 w = (t->tm_yday + 7 - (t->tm_wday ? t->tm_wday - 1 : 6)) / 7;
216                 d = (t->tm_yday + 7 - (t->tm_wday ? t->tm_wday - 1 : 6)) % 7;
217
218                 if (d >= 4) {
219                     w++;
220                 } else if (w == 0) {
221                     w = 53;
222                 }
223                 strfmt(r, "%2", w);
224                 break;
225
226             case 'w':
227                 strfmt(r, "%1", t->tm_wday);
228                 break;
229
230             case 'x':
231                 strfmt(r, "%3s %3s %2 %4", aday[t->tm_wday],
232                        amonth[t->tm_mon], t->tm_mday, t->tm_year + 1900);
233                 break;
234
235             case 'X':
236                 strfmt(r, "%2:%2:%2", t->tm_hour, t->tm_min, t->tm_sec);
237                 break;
238
239             case 'y':
240                 strfmt(r, "%2", t->tm_year % 100);
241                 break;
242
243             case 'Y':
244                 strfmt(r, "%4", t->tm_year + 1900);
245                 break;
246
247             case 'Z':
248                 r = (t->tm_isdst && tzname_[1][0]) ? tzname_[1] : tzname_[0];
249                 break;
250
251             default:
252                 buf[0] = '%';   /* reconstruct the format */
253                 buf[1] = f[-1];
254                 buf[2] = '\0';
255                 if (buf[1] == 0)
256                     f--;    /* back up if at end of string */
257             }
258             while (*r) {
259                 if (p == q) {
260                     *q = '\0';
261                     return 0;
262                 }
263                 *p++ = *r++;
264             }
265         } else {
266             if (p == q) {
267                 *q = '\0';
268                 return 0;
269             }
270             *p++ = f[-1];
271         }
272     }
273     *p = '\0';
274     return p - s;
275 }
276
277 /*
278  *  stdarg.h
279  *
280 typedef void *va_list;
281 #define va_start(vp,v) (vp=((char*)&v)+sizeof(v))
282 #define va_arg(vp,t) (*((t*)(vp))++)
283 #define va_end(vp)
284  *
285  */
286
287 static int powers[5] = { 1, 10, 100, 1000, 10000 };
288
289 /**
290  * static void strfmt(char *str, char *fmt);
291  *
292  * simple sprintf for strftime
293  *
294  * each format descriptor is of the form %n
295  * where n goes from zero to four
296  *
297  * 0    -- string %s
298  * 1..4 -- int %?.?d
299  *
300 **/
301
302 static void strfmt(
303     char *str,
304     const char *fmt,
305     ...)
306 {
307     int       ival, ilen;
308     char     *sval;
309     va_list   vp;
310
311     va_start(vp, fmt);
312     while (*fmt) {
313         if (*fmt++ == '%') {
314             ilen = *fmt++ - '0';
315             if (ilen == 0) {    /* zero means string arg */
316                 sval = va_arg(vp, char *);
317
318                 while (*sval)
319                     *str++ = *sval++;
320             } else {    /* always leading zeros */
321
322                 ival = va_arg(vp, int);
323
324                 while (ilen) {
325                     ival %= powers[ilen--];
326                     *str++ = (char) ('0' + ival / powers[ilen]);
327                 }
328             }
329         } else
330             *str++ = fmt[-1];
331     }
332     *str = '\0';
333     va_end(vp);
334 }
335
336 #ifdef TEST
337
338 #include <stdio.h>      /* for printf */
339 #include <time.h>       /* for strftime */
340
341 char      test[80];
342
343 int main(
344     int argc,
345     char *argv[])
346 {
347     int       len;
348     char     *fmt;
349     time_t    now;
350
351     time(&now);
352
353     fmt = (argc == 1) ? "%I:%M %p\n%c\n" : argv[1];
354     len = strftime_(test, sizeof test, fmt, localtime(&now));
355     printf("%d: %s\n", len, test);
356     return !len;
357 }
358
359 #endif                          /* TEST */