Wed Jan 24 04:18:36 1996 Paul Eggert <eggert@twinsun.com>
[platform/upstream/linaro-glibc.git] / time / strftime.c
1 /* Extensions for GNU date that are still missing here:
2    -
3    _
4 */
5
6 /* Copyright (C) 1991, 1992, 1993, 1994, 1995 Free Software Foundation, Inc.
7 This file is part of the GNU C Library.
8
9 The GNU C Library is free software; you can redistribute it and/or
10 modify it under the terms of the GNU Library General Public License as
11 published by the Free Software Foundation; either version 2 of the
12 License, or (at your option) any later version.
13
14 The GNU C Library is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 Library General Public License for more details.
18
19 You should have received a copy of the GNU Library General Public
20 License along with the GNU C Library; see the file COPYING.LIB.  If
21 not, write to the Free Software Foundation, Inc., 675 Mass Ave,
22 Cambridge, MA 02139, USA.  */
23
24 #ifdef HAVE_CONFIG_H
25 # include <config.h>
26 #endif
27
28 #ifdef _LIBC
29 # define HAVE_LIMITS_H 1
30 # define HAVE_MBLEN 1
31 # define HAVE_TM_ZONE 1
32 # define STDC_HEADERS 1
33 # include <ansidecl.h>
34 # include "../locale/localeinfo.h"
35 #endif
36
37 #include <stdio.h>
38 #include <sys/types.h>          /* Some systems define `time_t' here.  */
39 #include <time.h>
40
41 #if HAVE_MBLEN
42 # include <ctype.h>
43 #endif
44
45 #if HAVE_LIMITS_H
46 # include <limits.h>
47 #endif
48
49 #if STDC_HEADERS
50 # include <stddef.h>
51 # include <stdlib.h>
52 # include <string.h>
53 #else
54 # define memcpy(d, s, n) bcopy (s, d, n)
55 #endif
56
57 #ifndef __P
58 #if defined (__GNUC__) || (defined (__STDC__) && __STDC__)
59 #define __P(args) args
60 #else
61 #define __P(args) ()
62 #endif  /* GCC.  */
63 #endif  /* Not __P.  */
64
65 #ifndef PTR
66 #ifdef __STDC__
67 #define PTR void *
68 #else
69 #define PTR char *
70 #endif
71 #endif
72
73 static unsigned int week __P((const struct tm *const, int));
74
75
76 #define add(n, f)                                                             \
77   do                                                                          \
78     {                                                                         \
79       i += (n);                                                               \
80       if (i >= maxsize)                                                       \
81         return 0;                                                             \
82       else                                                                    \
83         if (p)                                                                \
84           {                                                                   \
85             f;                                                                \
86             p += (n);                                                         \
87           }                                                                   \
88     } while (0)
89 #define cpy(n, s)       add((n), memcpy((PTR) p, (PTR) (s), (n)))
90
91 #ifdef _LIBC
92 #define fmt(n, args)    add((n), if (sprintf args != (n)) return 0)
93 #else
94 #define fmt(n, args)    add((n), sprintf args; if (strlen (p) != (n)) return 0)
95 #endif
96
97 /* Return the week in the year specified by TP,
98    with weeks starting on STARTING_DAY.  */
99 #ifdef  __GNUC__
100 inline
101 #endif
102 static unsigned int
103 week (tp, starting_day)
104       const struct tm *const tp;
105       int starting_day;
106 {
107   int wday, dl;
108
109   wday = tp->tm_wday - starting_day;
110   if (wday < 0)
111     wday += 7;
112
113   /* Set DL to the day in the year of the last day of the week previous to the
114      one containing the day specified in TP.  If DL is negative or zero, the
115      day specified in TP is in the first week of the year.  Otherwise,
116      calculate the number of complete weeks before our week (DL / 7) and
117      add any partial week at the start of the year (DL % 7).  */
118   dl = tp->tm_yday - wday;
119   return dl <= 0 ? 0 : ((dl / 7) + ((dl % 7) == 0 ? 0 : 1));
120 }
121
122 #ifndef _NL_CURRENT
123 static char const weekday_name[][10] =
124   {
125     "Sunday", "Monday", "Tuesday", "Wednesday",
126     "Thursday", "Friday", "Saturday"
127   };
128 static char const month_name[][10] =
129   {
130     "January", "February", "March", "April", "May", "June",
131     "July", "August", "September", "October", "November", "December"
132   };
133 #endif
134
135 /* Write information from TP into S according to the format
136    string FORMAT, writing no more that MAXSIZE characters
137    (including the terminating '\0') and returning number of
138    characters written.  If S is NULL, nothing will be written
139    anywhere, so to determine how many characters would be
140    written, use NULL for S and (size_t) UINT_MAX for MAXSIZE.  */
141 size_t
142 strftime (s, maxsize, format, tp)
143       char *s;
144       size_t maxsize;
145       const char *format;
146       register const struct tm *tp;
147 {
148   int hour12 = tp->tm_hour;
149 #ifdef _NL_CURRENT
150   const char *const a_wkday = _NL_CURRENT (LC_TIME, ABDAY_1 + tp->tm_wday);
151   const char *const f_wkday = _NL_CURRENT (LC_TIME, DAY_1 + tp->tm_wday);
152   const char *const a_month = _NL_CURRENT (LC_TIME, ABMON_1 + tp->tm_mon);
153   const char *const f_month = _NL_CURRENT (LC_TIME, MON_1 + tp->tm_mon);
154   const char *const ampm = _NL_CURRENT (LC_TIME,
155                                         hour12 > 12 ? PM_STR : AM_STR);
156   size_t aw_len = strlen(a_wkday);
157   size_t am_len = strlen(a_month);
158   size_t ap_len = strlen (ampm);
159 #else
160   const char *const f_wkday = weekday_name[tp->tm_wday];
161   const char *const f_month = month_name[tp->tm_mon];
162   const char *const a_wkday = f_wkday;
163   const char *const a_month = f_month;
164   const char *const ampm = "AMPM" + 2 * (hour12 > 12);
165   size_t aw_len = 3;
166   size_t am_len = 3;
167   size_t ap_len = 2;
168 #endif
169   size_t wkday_len = strlen(f_wkday);
170   size_t month_len = strlen(f_month);
171   const unsigned int y_week0 = week (tp, 0);
172   const unsigned int y_week1 = week (tp, 1);
173   const char *zone;
174   size_t zonelen;
175   register size_t i = 0;
176   register char *p = s;
177   register const char *f;
178
179   zone = 0;
180 #if HAVE_TM_ZONE
181   zone = (const char *) tp->tm_zone;
182 #endif
183 #if HAVE_TZNAME
184   if (!(zone && *zone) && tp->tm_isdst >= 0)
185     zone = tzname[tp->tm_isdst];
186 #endif
187   if (!(zone && *zone))
188     zone = "???";
189
190   zonelen = strlen (zone);
191
192   if (hour12 > 12)
193     hour12 -= 12;
194   else
195     if (hour12 == 0) hour12 = 12;
196
197   for (f = format; *f != '\0'; ++f)
198     {
199       const char *subfmt;
200
201 #if HAVE_MBLEN
202       if (!isascii(*f))
203         {
204           /* Non-ASCII, may be a multibyte.  */
205           int len = mblen(f, strlen(f));
206           if (len > 0)
207             {
208               cpy(len, f);
209               continue;
210             }
211         }
212 #endif
213
214       if (*f != '%')
215         {
216           add(1, *p = *f);
217           continue;
218         }
219
220       ++f;
221       switch (*f)
222         {
223         case '\0':
224         case '%':
225           add(1, *p = *f);
226           break;
227
228         case 'a':
229           cpy(aw_len, a_wkday);
230           break;
231
232         case 'A':
233           cpy(wkday_len, f_wkday);
234           break;
235
236         case 'b':
237         case 'h':               /* GNU extension.  */
238           cpy(am_len, a_month);
239           break;
240
241         case 'B':
242           cpy(month_len, f_month);
243           break;
244
245         case 'c':
246 #ifdef _NL_CURRENT
247           subfmt = _NL_CURRENT (LC_TIME, D_T_FMT);
248 #else
249           subfmt = "%a %b %d %H:%M:%S %Z %Y";
250 #endif
251         subformat:
252           {
253             size_t len = strftime (p, maxsize - i, subfmt, tp);
254             if (len == 0 && *subfmt)
255               return 0;
256             add(len, );
257           }
258           break;
259
260         case 'C':
261           fmt (2, (p, "%02d", (1900 + tp->tm_year) / 100));
262           break;
263
264         case 'x':
265 #ifdef _NL_CURRENT
266           subfmt = _NL_CURRENT (LC_TIME, D_FMT);
267           goto subformat;
268 #endif
269           /* Fall through.  */
270         case 'D':               /* GNU extension.  */
271           subfmt = "%m/%d/%y";
272           goto subformat;
273
274         case 'd':
275           fmt(2, (p, "%02d", tp->tm_mday));
276           break;
277
278         case 'e':               /* GNU extension: %d, but blank-padded.  */
279           fmt(2, (p, "%2d", tp->tm_mday));
280           break;
281
282         case 'H':
283           fmt(2, (p, "%02d", tp->tm_hour));
284           break;
285
286         case 'I':
287           fmt(2, (p, "%02d", hour12));
288           break;
289
290         case 'k':               /* GNU extension.  */
291           fmt(2, (p, "%2d", tp->tm_hour));
292           break;
293
294         case 'l':               /* GNU extension.  */
295           fmt(2, (p, "%2d", hour12));
296           break;
297
298         case 'j':
299           fmt(3, (p, "%03d", 1 + tp->tm_yday));
300           break;
301
302         case 'M':
303           fmt(2, (p, "%02d", tp->tm_min));
304           break;
305
306         case 'm':
307           fmt(2, (p, "%02d", tp->tm_mon + 1));
308           break;
309
310         case 'n':               /* GNU extension.  */
311           add (1, *p = '\n');
312           break;
313
314         case 'p':
315           cpy(ap_len, ampm);
316           break;
317
318         case 'R':               /* GNU extension.  */
319           subfmt = "%H:%M";
320           goto subformat;
321
322         case 'r':               /* GNU extension.  */
323           subfmt = "%I:%M:%S %p";
324           goto subformat;
325
326         case 'S':
327           fmt(2, (p, "%02d", tp->tm_sec));
328           break;
329
330         case 'X':
331 #ifdef _NL_CURRENT
332           subfmt = _NL_CURRENT (LC_TIME, T_FMT);
333           goto subformat;
334 #endif
335           /* Fall through.  */
336         case 'T':               /* GNU extenstion.  */
337           subfmt = "%H:%M:%S";
338           goto subformat;
339
340         case 't':               /* GNU extenstion.  */
341           add (1, *p = '\t');
342           break;
343
344         case 'U':
345           fmt(2, (p, "%02u", y_week0));
346           break;
347
348         case 'W':
349           fmt(2, (p, "%02u", y_week1));
350           break;
351
352         case 'w':
353           fmt(2, (p, "%02d", tp->tm_wday));
354           break;
355
356         case 'Y':
357           fmt(4, (p, "%04d", 1900 + tp->tm_year));
358           break;
359
360         case 'y':
361           fmt(2, (p, "%02d", tp->tm_year % 100));
362           break;
363
364         case 'Z':
365           cpy(zonelen, zone);
366           break;
367
368         default:
369           /* Bad format.  */
370           break;
371         }
372     }
373
374   if (p)
375     *p = '\0';
376   return i;
377 }