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