Thu Mar 28 18:32:34 1996 Roland McGrath <roland@whiz-bang.gnu.ai.mit.edu>
[glibc.git] / time / strftime.c
blob02f72b3164a17e75d26e7e132d214c062430cf64
1 /* Copyright (C) 1991, 92, 93, 94, 95, 96 Free Software Foundation, Inc.
2 This file is part of the GNU C Library.
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.
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.
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. */
19 #ifdef HAVE_CONFIG_H
20 # include <config.h>
21 #endif
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
32 #include <stdio.h>
33 #include <sys/types.h> /* Some systems define `time_t' here. */
34 #include <time.h>
36 #if HAVE_MBLEN
37 # include <ctype.h>
38 #endif
40 #if HAVE_LIMITS_H
41 # include <limits.h>
42 #endif
44 #if STDC_HEADERS
45 # include <stddef.h>
46 # include <stdlib.h>
47 # include <string.h>
48 #else
49 # define memcpy(d, s, n) bcopy (s, d, n)
50 #endif
52 #ifndef __P
53 #if defined (__GNUC__) || (defined (__STDC__) && __STDC__)
54 #define __P(args) args
55 #else
56 #define __P(args) ()
57 #endif /* GCC. */
58 #endif /* Not __P. */
60 #ifndef PTR
61 #ifdef __STDC__
62 #define PTR void *
63 #else
64 #define PTR char *
65 #endif
66 #endif
68 static unsigned int week __P((const struct tm *const, int));
71 #define add(n, f) \
72 do \
73 { \
74 i += (n); \
75 if (i >= maxsize) \
76 return 0; \
77 else \
78 if (p) \
79 { \
80 f; \
81 p += (n); \
82 } \
83 } while (0)
84 #define cpy(n, s) add((n), memcpy((PTR) p, (PTR) (s), (n)))
86 #ifdef _LIBC
87 #define fmt(n, args) add((n), if (sprintf args != (n)) return 0)
88 #else
89 #define fmt(n, args) add((n), sprintf args; if (strlen (p) != (n)) return 0)
90 #endif
94 /* Return the week in the year specified by TP,
95 with weeks starting on STARTING_DAY. */
96 #ifdef __GNUC__
97 inline
98 #endif
99 static unsigned int
100 week (tp, starting_day)
101 const struct tm *const tp;
102 int starting_day;
104 int wday, dl;
106 wday = tp->tm_wday - starting_day;
107 if (wday < 0)
108 wday += 7;
110 /* Set DL to the day in the year of the last day of the week previous to the
111 one containing the day specified in TP. If DL is negative or zero, the
112 day specified in TP is in the first week of the year. Otherwise,
113 calculate the number of complete weeks before our week (DL / 7) and
114 add any partial week at the start of the year (DL % 7). */
115 dl = tp->tm_yday - wday;
116 return dl <= 0 ? 0 : ((dl / 7) + ((dl % 7) == 0 ? 0 : 1));
119 #ifndef _NL_CURRENT
120 static char const weekday_name[][10] =
122 "Sunday", "Monday", "Tuesday", "Wednesday",
123 "Thursday", "Friday", "Saturday"
125 static char const month_name[][10] =
127 "January", "February", "March", "April", "May", "June",
128 "July", "August", "September", "October", "November", "December"
130 #endif
132 /* Write information from TP into S according to the format
133 string FORMAT, writing no more that MAXSIZE characters
134 (including the terminating '\0') and returning number of
135 characters written. If S is NULL, nothing will be written
136 anywhere, so to determine how many characters would be
137 written, use NULL for S and (size_t) UINT_MAX for MAXSIZE. */
138 size_t
139 strftime (s, maxsize, format, tp)
140 char *s;
141 size_t maxsize;
142 const char *format;
143 register const struct tm *tp;
145 int hour12 = tp->tm_hour;
146 #ifdef _NL_CURRENT
147 const char *const a_wkday = _NL_CURRENT (LC_TIME, ABDAY_1 + tp->tm_wday);
148 const char *const f_wkday = _NL_CURRENT (LC_TIME, DAY_1 + tp->tm_wday);
149 const char *const a_month = _NL_CURRENT (LC_TIME, ABMON_1 + tp->tm_mon);
150 const char *const f_month = _NL_CURRENT (LC_TIME, MON_1 + tp->tm_mon);
151 const char *const ampm = _NL_CURRENT (LC_TIME,
152 hour12 > 11 ? PM_STR : AM_STR);
153 size_t aw_len = strlen(a_wkday);
154 size_t am_len = strlen(a_month);
155 size_t ap_len = strlen (ampm);
156 #else
157 const char *const f_wkday = weekday_name[tp->tm_wday];
158 const char *const f_month = month_name[tp->tm_mon];
159 const char *const a_wkday = f_wkday;
160 const char *const a_month = f_month;
161 const char *const ampm = "AMPM" + 2 * (hour12 > 11);
162 size_t aw_len = 3;
163 size_t am_len = 3;
164 size_t ap_len = 2;
165 #endif
166 size_t wkday_len = strlen(f_wkday);
167 size_t month_len = strlen(f_month);
168 const unsigned int y_week0 = week (tp, 0);
169 const unsigned int y_week1 = week (tp, 1);
170 const char *zone;
171 size_t zonelen;
172 register size_t i = 0;
173 register char *p = s;
174 register const char *f;
175 char number_fmt[5];
177 /* Initialize the buffer we will use for the sprintf format for numbers. */
178 number_fmt[0] = '%';
180 zone = 0;
181 #if HAVE_TM_ZONE
182 zone = (const char *) tp->tm_zone;
183 #endif
184 #if HAVE_TZNAME
185 if (!(zone && *zone) && tp->tm_isdst >= 0)
186 zone = tzname[tp->tm_isdst];
187 #endif
188 if (!(zone && *zone))
189 zone = "???";
191 zonelen = strlen (zone);
193 if (hour12 > 12)
194 hour12 -= 12;
195 else
196 if (hour12 == 0) hour12 = 12;
198 for (f = format; *f != '\0'; ++f)
200 enum { pad_zero, pad_space, pad_none } pad; /* Padding for number. */
201 unsigned int maxdigits; /* Max digits for numeric format. */
202 unsigned int number_value; /* Numeric value to be printed. */
203 const char *subfmt;
205 #if HAVE_MBLEN
206 if (!isascii(*f))
208 /* Non-ASCII, may be a multibyte. */
209 int len = mblen(f, strlen(f));
210 if (len > 0)
212 cpy(len, f);
213 continue;
216 #endif
218 if (*f != '%')
220 add(1, *p = *f);
221 continue;
224 /* Check for flags that can modify a number format. */
225 ++f;
226 switch (*f)
228 case '_':
229 pad = pad_space;
230 ++f;
231 break;
232 case '-':
233 pad = pad_none;
234 ++f;
235 break;
236 default:
237 pad = pad_zero;
238 break;
241 /* Now do the specified format. */
242 switch (*f)
244 case '\0':
245 case '%':
246 add(1, *p = *f);
247 break;
249 case 'a':
250 cpy(aw_len, a_wkday);
251 break;
253 case 'A':
254 cpy(wkday_len, f_wkday);
255 break;
257 case 'b':
258 case 'h': /* GNU extension. */
259 cpy(am_len, a_month);
260 break;
262 case 'B':
263 cpy(month_len, f_month);
264 break;
266 case 'c':
267 #ifdef _NL_CURRENT
268 subfmt = _NL_CURRENT (LC_TIME, D_T_FMT);
269 #else
270 subfmt = "%a %b %d %H:%M:%S %Z %Y";
271 #endif
272 subformat:
274 size_t len = strftime (p, maxsize - i, subfmt, tp);
275 if (len == 0 && *subfmt)
276 return 0;
277 add(len, );
279 break;
281 #define DO_NUMBER(digits, value) \
282 maxdigits = digits; number_value = value; goto do_number
283 #define DO_NUMBER_NOPAD(digits, value) \
284 maxdigits = digits; number_value = value; goto do_number_nopad
286 case 'C':
287 DO_NUMBER (2, (1900 + tp->tm_year) / 100);
289 case 'x':
290 #ifdef _NL_CURRENT
291 subfmt = _NL_CURRENT (LC_TIME, D_FMT);
292 goto subformat;
293 #endif
294 /* Fall through. */
295 case 'D': /* GNU extension. */
296 subfmt = "%m/%d/%y";
297 goto subformat;
299 case 'd':
300 DO_NUMBER (2, tp->tm_mday);
302 case 'e': /* GNU extension: %d, but blank-padded. */
303 DO_NUMBER_NOPAD (2, tp->tm_mday);
305 /* All numeric formats set MAXDIGITS and NUMBER_VALUE and then
306 jump to one of these two labels. */
308 do_number_nopad:
309 /* Force `-' flag. */
310 pad = pad_none;
312 do_number:
314 /* Format the number according to the PAD flag. */
316 register char *nf = &number_fmt[1];
317 int printed;
319 switch (pad)
321 case pad_zero:
322 *nf++ = '0';
323 case pad_space:
324 *nf++ = '0' + maxdigits;
325 case pad_none:
326 *nf++ = 'u';
327 *nf = '\0';
330 #ifdef _LIBC
331 add (maxdigits, printed = sprintf (p, number_fmt, number_value));
332 #else
333 add (sprintf (p, number_fmt, number_value);
334 printed = strlen (p));
335 #endif
337 break;
341 case 'H':
342 DO_NUMBER (2, tp->tm_hour);
344 case 'I':
345 DO_NUMBER (2, hour12);
347 case 'k': /* GNU extension. */
348 DO_NUMBER_NOPAD (2, tp->tm_hour);
350 case 'l': /* GNU extension. */
351 DO_NUMBER_NOPAD (2, hour12);
353 case 'j':
354 DO_NUMBER (3, 1 + tp->tm_yday);
356 case 'M':
357 DO_NUMBER (2, tp->tm_min);
359 case 'm':
360 DO_NUMBER (2, tp->tm_mon + 1);
362 case 'n': /* GNU extension. */
363 add (1, *p = '\n');
364 break;
366 case 'p':
367 cpy(ap_len, ampm);
368 break;
370 case 'R': /* GNU extension. */
371 subfmt = "%H:%M";
372 goto subformat;
374 case 'r': /* GNU extension. */
375 subfmt = "%I:%M:%S %p";
376 goto subformat;
378 case 'S':
379 DO_NUMBER (2, tp->tm_sec);
381 case 'X':
382 #ifdef _NL_CURRENT
383 subfmt = _NL_CURRENT (LC_TIME, T_FMT);
384 goto subformat;
385 #endif
386 /* Fall through. */
387 case 'T': /* GNU extenstion. */
388 subfmt = "%H:%M:%S";
389 goto subformat;
391 case 't': /* GNU extenstion. */
392 add (1, *p = '\t');
393 break;
395 case 'U':
396 DO_NUMBER (2, y_week0);
398 case 'W':
399 DO_NUMBER (2, y_week1);
401 case 'w':
402 DO_NUMBER (2, tp->tm_wday);
404 case 'Y':
405 DO_NUMBER (4, 1900 + tp->tm_year);
407 case 'y':
408 DO_NUMBER (2, tp->tm_year % 100);
410 case 'Z':
411 cpy(zonelen, zone);
412 break;
414 default:
415 /* Bad format. */
416 break;
420 if (p)
421 *p = '\0';
422 return i;