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