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