8c5e0cba9eb373ce4d875e5e40ecc22587f07f91
[kopensolaris-gnu/glibc.git] / time / strftime.c
1 /* Copyright (C) 1991, 92, 93, 94, 95, 96 Free Software Foundation, Inc.
2 This file is part of the GNU C Library.
3
4 The GNU C Library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public License as
6 published by the Free Software Foundation; either version 2 of the
7 License, or (at your option) any later version.
8
9 The GNU C Library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 Library General Public License for more details.
13
14 You should have received a copy of the GNU Library General Public
15 License along with the GNU C Library; see the file COPYING.LIB.  If
16 not, write to the Free Software Foundation, Inc., 675 Mass Ave,
17 Cambridge, MA 02139, USA.  */
18
19 #ifdef HAVE_CONFIG_H
20 # include <config.h>
21 #endif
22
23 #ifdef _LIBC
24 # define HAVE_LIMITS_H 1
25 # define HAVE_MBLEN 1
26 # define HAVE_TM_ZONE 1
27 # define STDC_HEADERS 1
28 # include <ansidecl.h>
29 # include "../locale/localeinfo.h"
30 #endif
31
32 #include <stdio.h>
33 #include <sys/types.h>          /* Some systems define `time_t' here.  */
34
35 #ifdef TIME_WITH_SYS_TIME
36 # include <sys/time.h>
37 # include <time.h>
38 #else
39 # ifdef HAVE_SYS_TIME_H
40 #  include <sys/time.h>
41 # else
42 #  include <time.h>
43 # endif
44 #endif
45
46 #if HAVE_MBLEN
47 # include <ctype.h>
48 #endif
49
50 #if HAVE_LIMITS_H
51 # include <limits.h>
52 #endif
53
54 #if STDC_HEADERS
55 # include <stddef.h>
56 # include <stdlib.h>
57 # include <string.h>
58 #else
59 # define memcpy(d, s, n) bcopy (s, d, n)
60 #endif
61
62 #ifndef __P
63 #if defined (__GNUC__) || (defined (__STDC__) && __STDC__)
64 #define __P(args) args
65 #else
66 #define __P(args) ()
67 #endif  /* GCC.  */
68 #endif  /* Not __P.  */
69
70 #ifndef PTR
71 #ifdef __STDC__
72 #define PTR void *
73 #else
74 #define PTR char *
75 #endif
76 #endif
77
78 static unsigned int week __P((const struct tm *const, int));
79
80
81 #define add(n, f)                                                             \
82   do                                                                          \
83     {                                                                         \
84       i += (n);                                                               \
85       if (i >= maxsize)                                                       \
86         return 0;                                                             \
87       else                                                                    \
88         if (p)                                                                \
89           {                                                                   \
90             f;                                                                \
91             p += (n);                                                         \
92           }                                                                   \
93     } while (0)
94 #define cpy(n, s)       add((n), memcpy((PTR) p, (PTR) (s), (n)))
95
96 #ifdef _LIBC
97 #define fmt(n, args)    add((n), if (sprintf args != (n)) return 0)
98 #else
99 #define fmt(n, args)    add((n), sprintf args; if (strlen (p) != (n)) return 0)
100 #endif
101
102
103
104 /* Return the week in the year specified by TP,
105    with weeks starting on STARTING_DAY.  */
106 #ifdef  __GNUC__
107 inline
108 #endif
109 static unsigned int
110 week (tp, starting_day)
111       const struct tm *const tp;
112       int starting_day;
113 {
114   int wday, dl;
115
116   wday = tp->tm_wday - starting_day;
117   if (wday < 0)
118     wday += 7;
119
120   /* Set DL to the day in the year of the last day of the week previous to the
121      one containing the day specified in TP.  If DL is negative or zero, the
122      day specified in TP is in the first week of the year.  Otherwise,
123      calculate the number of complete weeks before our week (DL / 7) and
124      add any partial week at the start of the year (DL % 7).  */
125   dl = tp->tm_yday - wday;
126   return dl <= 0 ? 0 : ((dl / 7) + ((dl % 7) == 0 ? 0 : 1));
127 }
128
129 #ifndef _NL_CURRENT
130 static char const weekday_name[][10] =
131   {
132     "Sunday", "Monday", "Tuesday", "Wednesday",
133     "Thursday", "Friday", "Saturday"
134   };
135 static char const month_name[][10] =
136   {
137     "January", "February", "March", "April", "May", "June",
138     "July", "August", "September", "October", "November", "December"
139   };
140 #endif
141
142 /* Write information from TP into S according to the format
143    string FORMAT, writing no more that MAXSIZE characters
144    (including the terminating '\0') and returning number of
145    characters written.  If S is NULL, nothing will be written
146    anywhere, so to determine how many characters would be
147    written, use NULL for S and (size_t) UINT_MAX for MAXSIZE.  */
148 size_t
149 strftime (s, maxsize, format, tp)
150       char *s;
151       size_t maxsize;
152       const char *format;
153       register const struct tm *tp;
154 {
155   int hour12 = tp->tm_hour;
156 #ifdef _NL_CURRENT
157   const char *const a_wkday = _NL_CURRENT (LC_TIME, ABDAY_1 + tp->tm_wday);
158   const char *const f_wkday = _NL_CURRENT (LC_TIME, DAY_1 + tp->tm_wday);
159   const char *const a_month = _NL_CURRENT (LC_TIME, ABMON_1 + tp->tm_mon);
160   const char *const f_month = _NL_CURRENT (LC_TIME, MON_1 + tp->tm_mon);
161   const char *const ampm = _NL_CURRENT (LC_TIME,
162                                         hour12 > 11 ? PM_STR : AM_STR);
163   size_t aw_len = strlen(a_wkday);
164   size_t am_len = strlen(a_month);
165   size_t ap_len = strlen (ampm);
166 #else
167   const char *const f_wkday = weekday_name[tp->tm_wday];
168   const char *const f_month = month_name[tp->tm_mon];
169   const char *const a_wkday = f_wkday;
170   const char *const a_month = f_month;
171   const char *const ampm = "AMPM" + 2 * (hour12 > 11);
172   size_t aw_len = 3;
173   size_t am_len = 3;
174   size_t ap_len = 2;
175 #endif
176   size_t wkday_len = strlen(f_wkday);
177   size_t month_len = strlen(f_month);
178   const unsigned int y_week0 = week (tp, 0);
179   const unsigned int y_week1 = week (tp, 1);
180   const char *zone;
181   size_t zonelen;
182   register size_t i = 0;
183   register char *p = s;
184   register const char *f;
185   char number_fmt[5];
186
187   /* Initialize the buffer we will use for the sprintf format for numbers.  */
188   number_fmt[0] = '%';
189
190   zone = 0;
191 #if HAVE_TM_ZONE
192   zone = (const char *) tp->tm_zone;
193 #endif
194 #if HAVE_TZNAME
195   if (!(zone && *zone) && tp->tm_isdst >= 0)
196     zone = tzname[tp->tm_isdst];
197 #endif
198   if (!(zone && *zone))
199     zone = "???";
200
201   zonelen = strlen (zone);
202
203   if (hour12 > 12)
204     hour12 -= 12;
205   else
206     if (hour12 == 0) hour12 = 12;
207
208   for (f = format; *f != '\0'; ++f)
209     {
210       enum { pad_zero, pad_space, pad_none } pad; /* Padding for number.  */
211       unsigned int maxdigits;   /* Max digits for numeric format.  */
212       unsigned int number_value; /* Numeric value to be printed.  */
213       const char *subfmt;
214
215 #if HAVE_MBLEN
216       if (!isascii(*f))
217         {
218           /* Non-ASCII, may be a multibyte.  */
219           int len = mblen(f, strlen(f));
220           if (len > 0)
221             {
222               cpy(len, f);
223               continue;
224             }
225         }
226 #endif
227
228       if (*f != '%')
229         {
230           add(1, *p = *f);
231           continue;
232         }
233
234       /* Check for flags that can modify a number format.  */
235       ++f;
236       switch (*f)
237         {
238         case '_':
239           pad = pad_space;
240           ++f;
241           break;
242         case '-':
243           pad = pad_none;
244           ++f;
245           break;
246         default:
247           pad = pad_zero;
248           break;
249         }
250
251       /* Now do the specified format.  */
252       switch (*f)
253         {
254         case '\0':
255         case '%':
256           add(1, *p = *f);
257           break;
258
259         case 'a':
260           cpy(aw_len, a_wkday);
261           break;
262
263         case 'A':
264           cpy(wkday_len, f_wkday);
265           break;
266
267         case 'b':
268         case 'h':               /* GNU extension.  */
269           cpy(am_len, a_month);
270           break;
271
272         case 'B':
273           cpy(month_len, f_month);
274           break;
275
276         case 'c':
277 #ifdef _NL_CURRENT
278           subfmt = _NL_CURRENT (LC_TIME, D_T_FMT);
279 #else
280           subfmt = "%a %b %d %H:%M:%S %Z %Y";
281 #endif
282         subformat:
283           {
284             size_t len = strftime (p, maxsize - i, subfmt, tp);
285             if (len == 0 && *subfmt)
286               return 0;
287             add(len, );
288           }
289           break;
290
291 #define DO_NUMBER(digits, value) \
292           maxdigits = digits; number_value = value; goto do_number
293 #define DO_NUMBER_NOPAD(digits, value) \
294           maxdigits = digits; number_value = value; goto do_number_nopad
295
296         case 'C':
297           DO_NUMBER (2, (1900 + tp->tm_year) / 100);
298
299         case 'x':
300 #ifdef _NL_CURRENT
301           subfmt = _NL_CURRENT (LC_TIME, D_FMT);
302           goto subformat;
303 #endif
304           /* Fall through.  */
305         case 'D':               /* GNU extension.  */
306           subfmt = "%m/%d/%y";
307           goto subformat;
308
309         case 'd':
310           DO_NUMBER (2, tp->tm_mday);
311
312         case 'e':               /* GNU extension: %d, but blank-padded.  */
313           DO_NUMBER_NOPAD (2, tp->tm_mday);
314
315           /* All numeric formats set MAXDIGITS and NUMBER_VALUE and then
316              jump to one of these two labels.  */
317
318         do_number_nopad:
319           /* Force `-' flag.  */
320           pad = pad_none;
321
322         do_number:
323           {
324             /* Format the number according to the PAD flag.  */
325
326             register char *nf = &number_fmt[1];
327             int printed;
328
329             switch (pad)
330               {
331               case pad_zero:
332                 *nf++ = '0';
333               case pad_space:
334                 *nf++ = '0' + maxdigits;
335               case pad_none:
336                 *nf++ = 'u';
337                 *nf = '\0';
338               }
339
340 #ifdef _LIBC
341             add (maxdigits, printed = sprintf (p, number_fmt, number_value));
342 #else
343             add (sprintf (p, number_fmt, number_value);
344                  printed = strlen (p));
345 #endif
346
347             break;
348           }
349
350
351         case 'H':
352           DO_NUMBER (2, tp->tm_hour);
353
354         case 'I':
355           DO_NUMBER (2, hour12);
356
357         case 'k':               /* GNU extension.  */
358           DO_NUMBER_NOPAD (2, tp->tm_hour);
359
360         case 'l':               /* GNU extension.  */
361           DO_NUMBER_NOPAD (2, hour12);
362
363         case 'j':
364           DO_NUMBER (3, 1 + tp->tm_yday);
365
366         case 'M':
367           DO_NUMBER (2, tp->tm_min);
368
369         case 'm':
370           DO_NUMBER (2, tp->tm_mon + 1);
371
372         case 'n':               /* GNU extension.  */
373           add (1, *p = '\n');
374           break;
375
376         case 'p':
377           cpy(ap_len, ampm);
378           break;
379
380         case 'R':               /* GNU extension.  */
381           subfmt = "%H:%M";
382           goto subformat;
383
384         case 'r':               /* GNU extension.  */
385           subfmt = "%I:%M:%S %p";
386           goto subformat;
387
388         case 'S':
389           DO_NUMBER (2, tp->tm_sec);
390
391         case 'X':
392 #ifdef _NL_CURRENT
393           subfmt = _NL_CURRENT (LC_TIME, T_FMT);
394           goto subformat;
395 #endif
396           /* Fall through.  */
397         case 'T':               /* GNU extenstion.  */
398           subfmt = "%H:%M:%S";
399           goto subformat;
400
401         case 't':               /* GNU extenstion.  */
402           add (1, *p = '\t');
403           break;
404
405         case 'U':
406           DO_NUMBER (2, y_week0);
407
408         case 'W':
409           DO_NUMBER (2, y_week1);
410
411         case 'w':
412           DO_NUMBER (2, tp->tm_wday);
413
414         case 'Y':
415           DO_NUMBER (4, 1900 + tp->tm_year);
416
417         case 'y':
418           DO_NUMBER (2, tp->tm_year % 100);
419
420         case 'Z':
421           cpy(zonelen, zone);
422           break;
423
424         default:
425           /* Bad format.  */
426           break;
427         }
428     }
429
430   if (p)
431     *p = '\0';
432   return i;
433 }