Mon May 6 14:53:26 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
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, 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, max_preceding)
111       const struct tm *const tp;
112       int starting_day;
113       int max_preceding;
114 {
115   int wday, dl, base;
116
117   wday = tp->tm_wday - starting_day;
118   if (wday < 0)
119     wday += 7;
120
121   /* Set DL to the day in the year of the first day of the week
122      containing the day specified in TP.  */
123   dl = tp->tm_yday - wday;
124
125   /* For the computation following ISO 8601:1988 we set the number of
126      the week containing January 1st to 1 if this week has more than
127      MAX_PRECEDING days in the new year.  For ISO 8601 this number is
128      3, for the other representation it is 7 (i.e., not to be
129      fulfilled).  */
130   base = ((dl + 7) % 7) > max_preceding ? 1 : 0;
131
132   /* If DL is negative we compute the result as 0 unless we have to
133      compute it according ISO 8601.  In this case we have to return 53
134      or 1 if the week containing January 1st has less than 4 days in
135      the new year or not.  If DL is not negative we calculate the
136      number of complete weeks for our week (DL / 7) plus 1 (because
137      only for DL < 0 we are in week 0/53 and plus the number of the
138      first week computed in the last step.  */
139   return dl < 0 ? (dl < -max_preceding ? 53 : base)
140                 : base + 1 + dl / 7;
141 }
142
143 #ifndef _NL_CURRENT
144 static char const weekday_name[][10] =
145   {
146     "Sunday", "Monday", "Tuesday", "Wednesday",
147     "Thursday", "Friday", "Saturday"
148   };
149 static char const month_name[][10] =
150   {
151     "January", "February", "March", "April", "May", "June",
152     "July", "August", "September", "October", "November", "December"
153   };
154 #endif
155
156 /* Write information from TP into S according to the format
157    string FORMAT, writing no more that MAXSIZE characters
158    (including the terminating '\0') and returning number of
159    characters written.  If S is NULL, nothing will be written
160    anywhere, so to determine how many characters would be
161    written, use NULL for S and (size_t) UINT_MAX for MAXSIZE.  */
162 size_t
163 strftime (s, maxsize, format, tp)
164       char *s;
165       size_t maxsize;
166       const char *format;
167       register const struct tm *tp;
168 {
169   int hour12 = tp->tm_hour;
170 #ifdef _NL_CURRENT
171   const char *const a_wkday = _NL_CURRENT (LC_TIME, ABDAY_1 + tp->tm_wday);
172   const char *const f_wkday = _NL_CURRENT (LC_TIME, DAY_1 + tp->tm_wday);
173   const char *const a_month = _NL_CURRENT (LC_TIME, ABMON_1 + tp->tm_mon);
174   const char *const f_month = _NL_CURRENT (LC_TIME, MON_1 + tp->tm_mon);
175   const char *const ampm = _NL_CURRENT (LC_TIME,
176                                         hour12 > 11 ? PM_STR : AM_STR);
177   size_t aw_len = strlen(a_wkday);
178   size_t am_len = strlen(a_month);
179   size_t ap_len = strlen (ampm);
180 #else
181   const char *const f_wkday = weekday_name[tp->tm_wday];
182   const char *const f_month = month_name[tp->tm_mon];
183   const char *const a_wkday = f_wkday;
184   const char *const a_month = f_month;
185   const char *const ampm = "AMPM" + 2 * (hour12 > 11);
186   size_t aw_len = 3;
187   size_t am_len = 3;
188   size_t ap_len = 2;
189 #endif
190   size_t wkday_len = strlen(f_wkday);
191   size_t month_len = strlen(f_month);
192   const unsigned int y_week0 = week (tp, 0, 7);
193   const unsigned int y_week1 = week (tp, 1, 7);
194   const unsigned int y_week2 = week (tp, 1, 3);
195   const char *zone;
196   size_t zonelen;
197   register size_t i = 0;
198   register char *p = s;
199   register const char *f;
200   char number_fmt[5];
201
202   /* Initialize the buffer we will use for the sprintf format for numbers.  */
203   number_fmt[0] = '%';
204
205   zone = 0;
206 #if HAVE_TM_ZONE
207   zone = (const char *) tp->tm_zone;
208 #endif
209 #if HAVE_TZNAME
210   if (!(zone && *zone) && tp->tm_isdst >= 0)
211     zone = tzname[tp->tm_isdst];
212 #endif
213   if (!(zone && *zone))
214     zone = "???";
215
216   zonelen = strlen (zone);
217
218   if (hour12 > 12)
219     hour12 -= 12;
220   else
221     if (hour12 == 0) hour12 = 12;
222
223   for (f = format; *f != '\0'; ++f)
224     {
225       enum { pad_zero, pad_space, pad_none } pad; /* Padding for number.  */
226       unsigned int maxdigits;   /* Max digits for numeric format.  */
227       unsigned int number_value; /* Numeric value to be printed.  */
228       const char *subfmt;
229
230 #if HAVE_MBLEN
231       if (!isascii(*f))
232         {
233           /* Non-ASCII, may be a multibyte.  */
234           int len = mblen(f, strlen(f));
235           if (len > 0)
236             {
237               cpy(len, f);
238               continue;
239             }
240         }
241 #endif
242
243       if (*f != '%')
244         {
245           add(1, *p = *f);
246           continue;
247         }
248
249       /* Check for flags that can modify a number format.  */
250       ++f;
251       switch (*f)
252         {
253         case '_':
254           pad = pad_space;
255           ++f;
256           break;
257         case '-':
258           pad = pad_none;
259           ++f;
260           break;
261         default:
262           pad = pad_zero;
263           break;
264         }
265
266       /* Now do the specified format.  */
267       switch (*f)
268         {
269         case '\0':
270         case '%':
271           add(1, *p = *f);
272           break;
273
274         case 'a':
275           cpy(aw_len, a_wkday);
276           break;
277
278         case 'A':
279           cpy(wkday_len, f_wkday);
280           break;
281
282         case 'b':
283         case 'h':               /* GNU extension.  */
284           cpy(am_len, a_month);
285           break;
286
287         case 'B':
288           cpy(month_len, f_month);
289           break;
290
291         case 'c':
292 #ifdef _NL_CURRENT
293           subfmt = _NL_CURRENT (LC_TIME, D_T_FMT);
294 #else
295           subfmt = "%a %b %d %H:%M:%S %Z %Y";
296 #endif
297         subformat:
298           {
299             size_t len = strftime (p, maxsize - i, subfmt, tp);
300             if (len == 0 && *subfmt)
301               return 0;
302             add(len, );
303           }
304           break;
305
306 #define DO_NUMBER(digits, value) \
307           maxdigits = digits; number_value = value; goto do_number
308 #define DO_NUMBER_NOPAD(digits, value) \
309           maxdigits = digits; number_value = value; goto do_number_nopad
310
311         case 'C':
312           DO_NUMBER (2, (1900 + tp->tm_year) / 100);
313
314         case 'x':
315 #ifdef _NL_CURRENT
316           subfmt = _NL_CURRENT (LC_TIME, D_FMT);
317           goto subformat;
318 #endif
319           /* Fall through.  */
320         case 'D':               /* GNU extension.  */
321           subfmt = "%m/%d/%y";
322           goto subformat;
323
324         case 'd':
325           DO_NUMBER (2, tp->tm_mday);
326
327         case 'e':               /* GNU extension: %d, but blank-padded.  */
328           DO_NUMBER_NOPAD (2, tp->tm_mday);
329
330           /* All numeric formats set MAXDIGITS and NUMBER_VALUE and then
331              jump to one of these two labels.  */
332
333         do_number_nopad:
334           /* Force `-' flag.  */
335           pad = pad_none;
336
337         do_number:
338           {
339             /* Format the number according to the PAD flag.  */
340
341             register char *nf = &number_fmt[1];
342             int printed;
343
344             switch (pad)
345               {
346               case pad_zero:
347                 *nf++ = '0';
348               case pad_space:
349                 *nf++ = '0' + maxdigits;
350               case pad_none:
351                 *nf++ = 'u';
352                 *nf = '\0';
353               }
354
355 #ifdef _LIBC
356             add (maxdigits, printed = sprintf (p, number_fmt, number_value));
357 #else
358             add (maxdigits, sprintf (p, number_fmt, number_value);
359                  printed = strlen (p));
360 #endif
361
362             break;
363           }
364
365
366         case 'H':
367           DO_NUMBER (2, tp->tm_hour);
368
369         case 'I':
370           DO_NUMBER (2, hour12);
371
372         case 'k':               /* GNU extension.  */
373           DO_NUMBER_NOPAD (2, tp->tm_hour);
374
375         case 'l':               /* GNU extension.  */
376           DO_NUMBER_NOPAD (2, hour12);
377
378         case 'j':
379           DO_NUMBER (3, 1 + tp->tm_yday);
380
381         case 'M':
382           DO_NUMBER (2, tp->tm_min);
383
384         case 'm':
385           DO_NUMBER (2, tp->tm_mon + 1);
386
387         case 'n':               /* GNU extension.  */
388           add (1, *p = '\n');
389           break;
390
391         case 'p':
392           cpy(ap_len, ampm);
393           break;
394
395         case 'R':               /* GNU extension.  */
396           subfmt = "%H:%M";
397           goto subformat;
398
399         case 'r':               /* GNU extension.  */
400           subfmt = "%I:%M:%S %p";
401           goto subformat;
402
403         case 'S':
404           DO_NUMBER (2, tp->tm_sec);
405
406         case 'X':
407 #ifdef _NL_CURRENT
408           subfmt = _NL_CURRENT (LC_TIME, T_FMT);
409           goto subformat;
410 #endif
411           /* Fall through.  */
412         case 'T':               /* GNU extenstion.  */
413           subfmt = "%H:%M:%S";
414           goto subformat;
415
416         case 't':               /* GNU extenstion.  */
417           add (1, *p = '\t');
418           break;
419
420         case 'U':
421           DO_NUMBER (2, y_week0);
422
423         case 'V':
424           DO_NUMBER (2, y_week2);
425
426         case 'W':
427           DO_NUMBER (2, y_week1);
428
429         case 'w':
430           DO_NUMBER (2, tp->tm_wday);
431
432         case 'Y':
433           DO_NUMBER (4, 1900 + tp->tm_year);
434
435         case 'y':
436           DO_NUMBER (2, tp->tm_year % 100);
437
438         case 'Z':
439           cpy(zonelen, zone);
440           break;
441
442         default:
443           /* Bad format.  */
444           break;
445         }
446     }
447
448   if (p)
449     *p = '\0';
450   return i;
451 }