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