Merge from vendor branch CVS:
[dragonfly.git] / lib / libc / stdtime / strftime.c
1 /*
2  * Copyright (c) 1989 The Regents of the University of California.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms are permitted
6  * provided that the above copyright notice and this paragraph are
7  * duplicated in all such forms and that any documentation,
8  * advertising materials, and other materials related to such
9  * distribution and use acknowledge that the software was developed
10  * by the University of California, Berkeley.  The name of the
11  * University may not be used to endorse or promote products derived
12  * from this software without specific prior written permission.
13  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
14  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
15  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
16  *
17  * @(#)strftime.c       7.38
18  * $FreeBSD: src/lib/libc/stdtime/strftime.c,v 1.25.2.4 2002/03/12 17:24:54 phantom Exp $
19  * $DragonFly: src/lib/libc/stdtime/strftime.c,v 1.4 2005/03/16 07:22:07 joerg Exp $
20  */
21
22 #include "namespace.h"
23 #include "private.h"
24
25 #ifndef LIBC_SCCS
26 #ifndef lint
27 static const char       sccsid[] = "@(#)strftime.c      5.4 (Berkeley) 3/14/89";
28 #endif /* !defined lint */
29 #endif /* !defined LIBC_SCCS */
30
31 #include "tzfile.h"
32 #include <fcntl.h>
33 #include <sys/stat.h>
34 #include "un-namespace.h"
35 #include "timelocal.h"
36
37 static char *   _add(const char *, char *, const char *);
38 static char *   _conv(int, const char *, char *, const char *);
39 static char *   _fmt(const char *, const struct tm *, char *, const char *);
40
41 size_t
42 strftime(char *const s, const size_t maxsize, const char * const format,
43          const struct tm * const t)
44 {
45         char *p;
46
47         tzset();
48         p = _fmt(((format == NULL) ? "%c" : format), t, s, s + maxsize);
49         if (p == s + maxsize)
50                 return 0;
51         *p = '\0';
52         return p - s;
53 }
54
55 static char *
56 _fmt(const char *format, const struct tm * const t, char *pt,
57      const char * const ptlim)
58 {
59         int Ealternative, Oalternative;
60         struct lc_time_T *tptr = __get_current_time_locale();
61
62         for ( ; *format; ++format) {
63                 if (*format == '%') {
64                         Ealternative = 0;
65                         Oalternative = 0;
66 label:
67                         switch (*++format) {
68                         case '\0':
69                                 --format;
70                                 break;
71                         case 'A':
72                                 pt = _add((t->tm_wday < 0 || t->tm_wday > 6) ?
73                                         "?" : tptr->weekday[t->tm_wday],
74                                         pt, ptlim);
75                                 continue;
76                         case 'a':
77                                 pt = _add((t->tm_wday < 0 || t->tm_wday > 6) ?
78                                         "?" : tptr->wday[t->tm_wday],
79                                         pt, ptlim);
80                                 continue;
81                         case 'B':
82                                 pt = _add((t->tm_mon < 0 || t->tm_mon > 11) ? 
83                                         "?" : (Oalternative ? tptr->alt_month :
84                                         tptr->month)[t->tm_mon],
85                                         pt, ptlim);
86                                 continue;
87                         case 'b':
88                         case 'h':
89                                 pt = _add((t->tm_mon < 0 || t->tm_mon > 11) ?
90                                         "?" : tptr->mon[t->tm_mon],
91                                         pt, ptlim);
92                                 continue;
93                         case 'C':
94                                 /*
95                                 ** %C used to do a...
96                                 **      _fmt("%a %b %e %X %Y", t);
97                                 ** ...whereas now POSIX 1003.2 calls for
98                                 ** something completely different.
99                                 ** (ado, 1993-05-24)
100                                 */
101                                 pt = _conv((t->tm_year + TM_YEAR_BASE) / 100,
102                                         "%02d", pt, ptlim);
103                                 continue;
104                         case 'c':
105                                 pt = _fmt(tptr->c_fmt, t, pt, ptlim);
106                                 continue;
107                         case 'D':
108                                 pt = _fmt("%m/%d/%y", t, pt, ptlim);
109                                 continue;
110                         case 'd':
111                                 pt = _conv(t->tm_mday, "%02d", pt, ptlim);
112                                 continue;
113                         case 'E':
114                                 if (Ealternative || Oalternative)
115                                         break;
116                                 Ealternative++;
117                                 goto label;
118                         case 'O':
119                                 /*
120                                 ** POSIX locale extensions, a la
121                                 ** Arnold Robbins' strftime version 3.0.
122                                 ** The sequences
123                                 **      %Ec %EC %Ex %EX %Ey %EY
124                                 **      %Od %oe %OH %OI %Om %OM
125                                 **      %OS %Ou %OU %OV %Ow %OW %Oy
126                                 ** are supposed to provide alternate
127                                 ** representations.
128                                 ** (ado, 1993-05-24)
129                                 **
130                                 ** FreeBSD extensions
131                                 **      %OB %Ef %EF
132                                 */
133                                 if (Ealternative || Oalternative)
134                                         break;
135                                 Oalternative++;
136                                 goto label;
137                         case 'e':
138                                 pt = _conv(t->tm_mday, "%2d", pt, ptlim);
139                                 continue;
140                         case 'F':
141                                 pt = _fmt("%Y-%m-%d", t, pt, ptlim);
142                                 continue;
143                         case 'H':
144                                 pt = _conv(t->tm_hour, "%02d", pt, ptlim);
145                                 continue;
146                         case 'I':
147                                 pt = _conv((t->tm_hour % 12) ?
148                                         (t->tm_hour % 12) : 12,
149                                         "%02d", pt, ptlim);
150                                 continue;
151                         case 'j':
152                                 pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim);
153                                 continue;
154                         case 'k':
155                                 /*
156                                 ** This used to be...
157                                 **      _conv(t->tm_hour % 12 ?
158                                 **              t->tm_hour % 12 : 12, 2, ' ');
159                                 ** ...and has been changed to the below to
160                                 ** match SunOS 4.1.1 and Arnold Robbins'
161                                 ** strftime version 3.0.  That is, "%k" and
162                                 ** "%l" have been swapped.
163                                 ** (ado, 1993-05-24)
164                                 */
165                                 pt = _conv(t->tm_hour, "%2d", pt, ptlim);
166                                 continue;
167 #ifdef KITCHEN_SINK
168                         case 'K':
169                                 /*
170                                 ** After all this time, still unclaimed!
171                                 */
172                                 pt = _add("kitchen sink", pt, ptlim);
173                                 continue;
174 #endif /* defined KITCHEN_SINK */
175                         case 'l':
176                                 /*
177                                 ** This used to be...
178                                 **      _conv(t->tm_hour, 2, ' ');
179                                 ** ...and has been changed to the below to
180                                 ** match SunOS 4.1.1 and Arnold Robbin's
181                                 ** strftime version 3.0.  That is, "%k" and
182                                 ** "%l" have been swapped.
183                                 ** (ado, 1993-05-24)
184                                 */
185                                 pt = _conv((t->tm_hour % 12) ?
186                                         (t->tm_hour % 12) : 12,
187                                         "%2d", pt, ptlim);
188                                 continue;
189                         case 'M':
190                                 pt = _conv(t->tm_min, "%02d", pt, ptlim);
191                                 continue;
192                         case 'm':
193                                 pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim);
194                                 continue;
195                         case 'n':
196                                 pt = _add("\n", pt, ptlim);
197                                 continue;
198                         case 'p':
199                                 pt = _add((t->tm_hour >= 12) ?
200                                         tptr->pm :
201                                         tptr->am,
202                                         pt, ptlim);
203                                 continue;
204                         case 'R':
205                                 pt = _fmt("%H:%M", t, pt, ptlim);
206                                 continue;
207                         case 'r':
208                                 pt = _fmt(tptr->ampm_fmt, t, pt, ptlim);
209                                 continue;
210                         case 'S':
211                                 pt = _conv(t->tm_sec, "%02d", pt, ptlim);
212                                 continue;
213                         case 's':
214                                 {
215                                         struct tm       tm;
216                                         char            buf[INT_STRLEN_MAXIMUM(
217                                                                 time_t) + 1];
218                                         time_t          mkt;
219
220                                         tm = *t;
221                                         mkt = mktime(&tm);
222                                         if (TYPE_SIGNED(time_t))
223                                                 (void) sprintf(buf, "%ld",
224                                                         (long) mkt);
225                                         else    (void) sprintf(buf, "%lu",
226                                                         (unsigned long) mkt);
227                                         pt = _add(buf, pt, ptlim);
228                                 }
229                                 continue;
230                         case 'T':
231                                 pt = _fmt("%H:%M:%S", t, pt, ptlim);
232                                 continue;
233                         case 't':
234                                 pt = _add("\t", pt, ptlim);
235                                 continue;
236                         case 'U':
237                                 pt = _conv((t->tm_yday + 7 - t->tm_wday) / 7,
238                                         "%02d", pt, ptlim);
239                                 continue;
240                         case 'u':
241                                 /*
242                                 ** From Arnold Robbins' strftime version 3.0:
243                                 ** "ISO 8601: Weekday as a decimal number
244                                 ** [1 (Monday) - 7]"
245                                 ** (ado, 1993-05-24)
246                                 */
247                                 pt = _conv((t->tm_wday == 0) ? 7 : t->tm_wday,
248                                         "%d", pt, ptlim);
249                                 continue;
250                         case 'V':       /* ISO 8601 week number */
251                         case 'G':       /* ISO 8601 year (four digits) */
252                         case 'g':       /* ISO 8601 year (two digits) */
253 /*
254 ** From Arnold Robbins' strftime version 3.0:  "the week number of the
255 ** year (the first Monday as the first day of week 1) as a decimal number
256 ** (01-53)."
257 ** (ado, 1993-05-24)
258 **
259 ** From "http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html" by Markus Kuhn:
260 ** "Week 01 of a year is per definition the first week which has the
261 ** Thursday in this year, which is equivalent to the week which contains
262 ** the fourth day of January. In other words, the first week of a new year
263 ** is the week which has the majority of its days in the new year. Week 01
264 ** might also contain days from the previous year and the week before week
265 ** 01 of a year is the last week (52 or 53) of the previous year even if
266 ** it contains days from the new year. A week starts with Monday (day 1)
267 ** and ends with Sunday (day 7).  For example, the first week of the year
268 ** 1997 lasts from 1996-12-30 to 1997-01-05..."
269 ** (ado, 1996-01-02)
270 */
271                                 {
272                                         int     year;
273                                         int     yday;
274                                         int     wday;
275                                         int     w;
276
277                                         year = t->tm_year + TM_YEAR_BASE;
278                                         yday = t->tm_yday;
279                                         wday = t->tm_wday;
280                                         for ( ; ; ) {
281                                                 int     len;
282                                                 int     bot;
283                                                 int     top;
284
285                                                 len = isleap(year) ?
286                                                         DAYSPERLYEAR :
287                                                         DAYSPERNYEAR;
288                                                 /*
289                                                 ** What yday (-3 ... 3) does
290                                                 ** the ISO year begin on?
291                                                 */
292                                                 bot = ((yday + 11 - wday) %
293                                                         DAYSPERWEEK) - 3;
294                                                 /*
295                                                 ** What yday does the NEXT
296                                                 ** ISO year begin on?
297                                                 */
298                                                 top = bot -
299                                                         (len % DAYSPERWEEK);
300                                                 if (top < -3)
301                                                         top += DAYSPERWEEK;
302                                                 top += len;
303                                                 if (yday >= top) {
304                                                         ++year;
305                                                         w = 1;
306                                                         break;
307                                                 }
308                                                 if (yday >= bot) {
309                                                         w = 1 + ((yday - bot) /
310                                                                 DAYSPERWEEK);
311                                                         break;
312                                                 }
313                                                 --year;
314                                                 yday += isleap(year) ?
315                                                         DAYSPERLYEAR :
316                                                         DAYSPERNYEAR;
317                                         }
318 #ifdef XPG4_1994_04_09
319                                         if ((w == 52
320                                              && t->tm_mon == TM_JANUARY)
321                                             || (w == 1
322                                                 && t->tm_mon == TM_DECEMBER))
323                                                 w = 53;
324 #endif /* defined XPG4_1994_04_09 */
325                                         if (*format == 'V')
326                                                 pt = _conv(w, "%02d",
327                                                         pt, ptlim);
328                                         else if (*format == 'g') {
329                                                 pt = _conv(year % 100, "%02d",
330                                                         pt, ptlim);
331                                         } else  pt = _conv(year, "%04d",
332                                                         pt, ptlim);
333                                 }
334                                 continue;
335                         case 'v':
336                                 /*
337                                 ** From Arnold Robbins' strftime version 3.0:
338                                 ** "date as dd-bbb-YYYY"
339                                 ** (ado, 1993-05-24)
340                                 */
341                                 pt = _fmt("%e-%b-%Y", t, pt, ptlim);
342                                 continue;
343                         case 'W':
344                                 pt = _conv((t->tm_yday + 7 -
345                                         (t->tm_wday ?
346                                         (t->tm_wday - 1) : 6)) / 7,
347                                         "%02d", pt, ptlim);
348                                 continue;
349                         case 'w':
350                                 pt = _conv(t->tm_wday, "%d", pt, ptlim);
351                                 continue;
352                         case 'X':
353                                 pt = _fmt(tptr->X_fmt, t, pt, ptlim);
354                                 continue;
355                         case 'x':
356                                 pt = _fmt(tptr->x_fmt, t, pt, ptlim);
357                                 continue;
358                         case 'y':
359                                 pt = _conv((t->tm_year + TM_YEAR_BASE) % 100,
360                                         "%02d", pt, ptlim);
361                                 continue;
362                         case 'Y':
363                                 pt = _conv(t->tm_year + TM_YEAR_BASE, "%04d",
364                                         pt, ptlim);
365                                 continue;
366                         case 'Z':
367                                 if (t->tm_zone != NULL)
368                                         pt = _add(t->tm_zone, pt, ptlim);
369                                 else
370                                 if (t->tm_isdst == 0 || t->tm_isdst == 1) {
371                                         pt = _add(tzname[t->tm_isdst],
372                                                 pt, ptlim);
373                                 } else  pt = _add("?", pt, ptlim);
374                                 continue;
375                         case 'z':
376                                 {
377                                         long absoff;
378                                         if (t->tm_gmtoff >= 0) {
379                                                 absoff = t->tm_gmtoff;
380                                                 pt = _add("+", pt, ptlim);
381                                         } else {
382                                                 absoff = -t->tm_gmtoff;
383                                                 pt = _add("-", pt, ptlim);
384                                         }
385                                         pt = _conv(absoff / 3600, "%02d",
386                                                 pt, ptlim);
387                                         pt = _conv((absoff % 3600) / 60, "%02d",
388                                                 pt, ptlim);
389                                 };
390                                 continue;
391                         case '+':
392                                 pt = _fmt(tptr->date_fmt, t, pt, ptlim);
393                                 continue;
394                         case '%':
395                         /*
396                          * X311J/88-090 (4.12.3.5): if conversion char is
397                          * undefined, behavior is undefined.  Print out the
398                          * character itself as printf(3) also does.
399                          */
400                         default:
401                                 break;
402                         }
403                 }
404                 if (pt == ptlim)
405                         break;
406                 *pt++ = *format;
407         }
408         return pt;
409 }
410
411 static char *
412 _conv(const int n, const char * const format, char * const pt,
413       const char * const ptlim)
414 {
415         char    buf[INT_STRLEN_MAXIMUM(int) + 1];
416
417         (void) sprintf(buf, format, n);
418         return _add(buf, pt, ptlim);
419 }
420
421 static char *
422 _add(const char *str, char *pt, const char * const ptlim)
423 {
424         while (pt < ptlim && (*pt = *str++) != '\0')
425                 ++pt;
426         return pt;
427 }