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