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