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