New version from glibc.
[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 /* Uncomment following line in the production version.  */
81 /* #define NDEBUG */
82 #include <assert.h>
83
84 static unsigned int week __P ((const struct tm *const, int, int));
85
86
87 #define add(n, f)                                                             \
88   do                                                                          \
89     {                                                                         \
90       i += (n);                                                               \
91       if (i >= maxsize)                                                       \
92         return 0;                                                             \
93       else                                                                    \
94         if (p)                                                                \
95           {                                                                   \
96             f;                                                                \
97             p += (n);                                                         \
98           }                                                                   \
99     } while (0)
100 #define cpy(n, s)       add ((n), memcpy((PTR) p, (PTR) (s), (n)))
101
102 #ifdef _LIBC
103 #define fmt(n, args)    add((n), if (sprintf args != (n)) return 0)
104 #else
105 #define fmt(n, args)    add((n), sprintf args; if (strlen (p) != (n)) return 0)
106 #endif
107
108
109
110 /* Return the week in the year specified by TP,
111    with weeks starting on STARTING_DAY.  */
112 #ifdef  __GNUC__
113 inline
114 #endif
115 static unsigned int
116 week (tp, starting_day, max_preceding)
117       const struct tm *const tp;
118       int starting_day;
119       int max_preceding;
120 {
121   int wday, dl, base;
122
123   wday = tp->tm_wday - starting_day;
124   if (wday < 0)
125     wday += 7;
126
127   /* Set DL to the day in the year of the first day of the week
128      containing the day specified in TP.  */
129   dl = tp->tm_yday - wday;
130
131   /* For the computation following ISO 8601:1988 we set the number of
132      the week containing January 1st to 1 if this week has more than
133      MAX_PRECEDING days in the new year.  For ISO 8601 this number is
134      3, for the other representation it is 7 (i.e., not to be
135      fulfilled).  */
136   base = ((dl + 7) % 7) > max_preceding ? 1 : 0;
137
138   /* If DL is negative we compute the result as 0 unless we have to
139      compute it according ISO 8601.  In this case we have to return 53
140      or 1 if the week containing January 1st has less than 4 days in
141      the new year or not.  If DL is not negative we calculate the
142      number of complete weeks for our week (DL / 7) plus 1 (because
143      only for DL < 0 we are in week 0/53 and plus the number of the
144      first week computed in the last step.  */
145   return dl < 0 ? (dl < -max_preceding ? 53 : base)
146                 : base + 1 + dl / 7;
147 }
148
149 #ifndef _NL_CURRENT
150 static char const weekday_name[][10] =
151   {
152     "Sunday", "Monday", "Tuesday", "Wednesday",
153     "Thursday", "Friday", "Saturday"
154   };
155 static char const month_name[][10] =
156   {
157     "January", "February", "March", "April", "May", "June",
158     "July", "August", "September", "October", "November", "December"
159   };
160 #endif
161
162 /* Write information from TP into S according to the format
163    string FORMAT, writing no more that MAXSIZE characters
164    (including the terminating '\0') and returning number of
165    characters written.  If S is NULL, nothing will be written
166    anywhere, so to determine how many characters would be
167    written, use NULL for S and (size_t) UINT_MAX for MAXSIZE.  */
168 size_t
169 strftime (s, maxsize, format, tp)
170       char *s;
171       size_t maxsize;
172       const char *format;
173       register const struct tm *tp;
174 {
175   int hour12 = tp->tm_hour;
176 #ifdef _NL_CURRENT
177   const char *const a_wkday = _NL_CURRENT (LC_TIME, ABDAY_1 + tp->tm_wday);
178   const char *const f_wkday = _NL_CURRENT (LC_TIME, DAY_1 + tp->tm_wday);
179   const char *const a_month = _NL_CURRENT (LC_TIME, ABMON_1 + tp->tm_mon);
180   const char *const f_month = _NL_CURRENT (LC_TIME, MON_1 + tp->tm_mon);
181   const char *const ampm = _NL_CURRENT (LC_TIME,
182                                         hour12 > 11 ? PM_STR : AM_STR);
183   size_t aw_len = strlen(a_wkday);
184   size_t am_len = strlen(a_month);
185   size_t ap_len = strlen (ampm);
186 #else
187   const char *const f_wkday = weekday_name[tp->tm_wday];
188   const char *const f_month = month_name[tp->tm_mon];
189   const char *const a_wkday = f_wkday;
190   const char *const a_month = f_month;
191   const char *const ampm = "AMPM" + 2 * (hour12 > 11);
192   size_t aw_len = 3;
193   size_t am_len = 3;
194   size_t ap_len = 2;
195 #endif
196   size_t wkday_len = strlen (f_wkday);
197   size_t month_len = strlen (f_month);
198   const unsigned int y_week0 = week (tp, 0, 7);
199   const unsigned int y_week1 = week (tp, 1, 7);
200   const unsigned int y_week2 = week (tp, 1, 3);
201   const char *zone;
202   size_t zonelen;
203   register size_t i = 0;
204   register char *p = s;
205   register const char *f;
206   char number_fmt[5];
207
208   /* Initialize the buffer we will use for the sprintf format for numbers.  */
209   number_fmt[0] = '%';
210
211   zone = 0;
212 #if HAVE_TM_ZONE
213   zone = (const char *) tp->tm_zone;
214 #endif
215 #if HAVE_TZNAME
216   if (!(zone && *zone) && tp->tm_isdst >= 0)
217     zone = tzname[tp->tm_isdst];
218 #endif
219   if (!(zone && *zone))
220     zone = "???";
221
222   zonelen = strlen (zone);
223
224   if (hour12 > 12)
225     hour12 -= 12;
226   else
227     if (hour12 == 0) hour12 = 12;
228
229   for (f = format; *f != '\0'; ++f)
230     {
231       enum { pad_zero, pad_space, pad_none } pad; /* Padding for number.  */
232       unsigned int maxdigits;   /* Max digits for numeric format.  */
233       unsigned int number_value; /* Numeric value to be printed.  */
234       const char *subfmt;
235
236 #if HAVE_MBLEN
237       if (!isascii (*f))
238         {
239           /* Non-ASCII, may be a multibyte.  */
240           int len = mblen (f, strlen (f));
241           if (len > 0)
242             {
243               cpy(len, f);
244               continue;
245             }
246         }
247 #endif
248
249       if (*f != '%')
250         {
251           add (1, *p = *f);
252           continue;
253         }
254
255       /* Check for flags that can modify a number format.  */
256       ++f;
257       switch (*f)
258         {
259         case '_':
260           pad = pad_space;
261           ++f;
262           break;
263         case '-':
264           pad = pad_none;
265           ++f;
266           break;
267         default:
268           pad = pad_zero;
269           break;
270         }
271
272       /* Now do the specified format.  */
273       switch (*f)
274         {
275         case '\0':
276         case '%':
277           add (1, *p = *f);
278           break;
279
280         case 'a':
281           cpy (aw_len, a_wkday);
282           break;
283
284         case 'A':
285           cpy (wkday_len, f_wkday);
286           break;
287
288         case 'b':
289         case 'h':               /* GNU extension.  */
290           cpy (am_len, a_month);
291           break;
292
293         case 'B':
294           cpy (month_len, f_month);
295           break;
296
297         case 'c':
298 #ifdef _NL_CURRENT
299           subfmt = _NL_CURRENT (LC_TIME, D_T_FMT);
300 #else
301           subfmt = "%a %b %d %H:%M:%S %Z %Y";
302 #endif
303         subformat:
304           {
305             size_t len = strftime (p, maxsize - i, subfmt, tp);
306             if (len == 0 && *subfmt)
307               return 0;
308             add (len, ;);
309           }
310           break;
311
312 #define DO_NUMBER(digits, value) \
313           maxdigits = digits; number_value = value; goto do_number
314 #define DO_NUMBER_SPACEPAD(digits, value) \
315           maxdigits = digits; number_value = value; goto do_number_spacepad
316
317         case 'C':
318           DO_NUMBER (2, (1900 + tp->tm_year) / 100);
319
320         case 'x':
321 #ifdef _NL_CURRENT
322           subfmt = _NL_CURRENT (LC_TIME, D_FMT);
323           goto subformat;
324 #endif
325           /* Fall through.  */
326         case 'D':               /* GNU extension.  */
327           subfmt = "%m/%d/%y";
328           goto subformat;
329
330         case 'd':
331           DO_NUMBER (2, tp->tm_mday);
332
333         case 'e':               /* GNU extension: %d, but blank-padded.  */
334           DO_NUMBER_SPACEPAD (2, tp->tm_mday);
335
336           /* All numeric formats set MAXDIGITS and NUMBER_VALUE and then
337              jump to one of these two labels.  */
338
339         do_number_spacepad:
340           /* Force `_' flag.  */
341           pad = pad_space;
342
343         do_number:
344           {
345             /* Format the number according to the PAD flag.  */
346
347             register char *nf = &number_fmt[1];
348             int printed = maxdigits;
349
350             switch (pad)
351               {
352               case pad_zero:
353                 *nf++ = '0';
354               case pad_space:
355                 *nf++ = '0' + maxdigits;
356               case pad_none:
357                 *nf++ = 'u';
358                 *nf = '\0';
359               }
360
361 #ifdef _LIBC
362             add (maxdigits, printed = sprintf (p, number_fmt, number_value));
363 #else
364             add (maxdigits, sprintf (p, number_fmt, number_value);
365                  printed = strlen (p));
366 #endif
367             /* Back up if fewer than MAXDIGITS chars written for pad_none.  */
368             p -= maxdigits - printed;
369             i -= maxdigits - printed;
370
371             break;
372           }
373
374
375         case 'H':
376           DO_NUMBER (2, tp->tm_hour);
377
378         case 'I':
379           DO_NUMBER (2, hour12);
380
381         case 'k':               /* GNU extension.  */
382           DO_NUMBER_SPACEPAD (2, tp->tm_hour);
383
384         case 'l':               /* GNU extension.  */
385           DO_NUMBER_SPACEPAD (2, hour12);
386
387         case 'j':
388           DO_NUMBER (3, 1 + tp->tm_yday);
389
390         case 'M':
391           DO_NUMBER (2, tp->tm_min);
392
393         case 'm':
394           DO_NUMBER (2, tp->tm_mon + 1);
395
396         case 'n':               /* GNU extension.  */
397           add (1, *p = '\n');
398           break;
399
400         case 'p':
401           cpy (ap_len, ampm);
402           break;
403
404         case 'R':               /* GNU extension.  */
405           subfmt = "%H:%M";
406           goto subformat;
407
408         case 'r':               /* GNU extension.  */
409           subfmt = "%I:%M:%S %p";
410           goto subformat;
411
412         case 'S':
413           DO_NUMBER (2, tp->tm_sec);
414
415         case 's':               /* GNU extension.  */
416           {
417             struct tm writable_tm = *tp;
418             unsigned long int num = (unsigned long int) mktime (&writable_tm);
419             /* `3 * sizeof (unsigned long int)' is an approximation of
420                the size of the decimal representation of NUM, valid
421                for sizes <= 16.  */
422             int printed = 3 * sizeof (unsigned long int);
423             maxdigits = printed;
424             assert (sizeof (unsigned long int) <= 16);
425 #ifdef _LIBC
426             add (maxdigits, printed = sprintf (p, "%lu", num));
427 #else
428             add (maxdigits, sprintf (p, "%lu", num); printed = strlen (p));
429 #endif
430             /* Back up if fewer than MAXDIGITS chars written for pad_none.  */
431             p -= maxdigits - printed;
432             i -= maxdigits - printed;
433           }
434         break;
435
436         case 'X':
437 #ifdef _NL_CURRENT
438           subfmt = _NL_CURRENT (LC_TIME, T_FMT);
439           goto subformat;
440 #endif
441           /* Fall through.  */
442         case 'T':               /* GNU extension.  */
443           subfmt = "%H:%M:%S";
444           goto subformat;
445
446         case 't':               /* GNU extension.  */
447           add (1, *p = '\t');
448           break;
449
450         case 'U':
451           DO_NUMBER (2, y_week0);
452
453         case 'V':
454           DO_NUMBER (2, y_week2);
455
456         case 'W':
457           DO_NUMBER (2, y_week1);
458
459         case 'w':
460           DO_NUMBER (2, tp->tm_wday);
461
462         case 'Y':
463           DO_NUMBER (4, 1900 + tp->tm_year);
464
465         case 'y':
466           DO_NUMBER (2, tp->tm_year % 100);
467
468         case 'Z':
469           cpy(zonelen, zone);
470           break;
471
472         case 'z':
473           {
474             struct tm tml = *tp;
475             struct tm tmg;
476             time_t t;
477             time_t offset = 0;
478             int diff;
479
480             t = __mktime_internal (&tml, __localtime_r, &offset);
481
482             /* Canonicalize the local time.  */
483             if (t == (time_t) -1 || __localtime_r (&t, &tml) == NULL)
484               /* We didn't managed to get the local time.  Assume it
485                  GMT as a reasonable default value.  */
486               diff = 0;
487             else
488               {
489                 __gmtime_r (&t, &tmg);
490
491                 /* Compute the difference.  */
492                 diff = tml.tm_min - tmg.tm_min;
493                 diff += 60 * (tml.tm_hour - tmg.tm_hour);
494
495                 if (tml.tm_mon != tmg.tm_mon)
496                   {
497                     /* We assume no timezone differs from UTC by more
498                        than +- 23 hours.  This should be safe.  */
499                     if (tmg.tm_mday == 1)
500                       tml.tm_mday = 0;
501                     else /* tml.tm_mday == 1 */
502                       tmg.tm_mday = 0;
503                   }
504                 diff += 1440 * (tml.tm_mday - tmg.tm_mday);
505               }
506
507             if (diff < 0)
508               {
509                 add (1, *p = '-');
510                 diff = -diff;
511               }
512             else
513               add (1, *p = '+');
514
515             pad = pad_zero;
516             DO_NUMBER (4, ((diff / 60) % 24) * 100 + diff % 60);
517           }
518
519         default:
520           /* Bad format.  */
521           add (1, *p = *f);
522           break;
523         }
524     }
525
526   if (p)
527     *p = '\0';
528   return i;
529 }