Update for 960809.
[glibc.git] / time / strftime.c
blob38acc6478dc1de31016563fe54a16c4352fa7806
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. */
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
46 #if HAVE_MBLEN
47 # include <ctype.h>
48 #endif
50 #if HAVE_LIMITS_H
51 # include <limits.h>
52 #endif
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
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. */
70 #ifndef PTR
71 #ifdef __STDC__
72 #define PTR void *
73 #else
74 #define PTR char *
75 #endif
76 #endif
78 static unsigned int week __P((const struct tm *const, int, int));
81 #define add(n, f) \
82 do \
83 { \
84 i += (n); \
85 if (i >= maxsize) \
86 return 0; \
87 else \
88 if (p) \
89 { \
90 f; \
91 p += (n); \
92 } \
93 } while (0)
94 #define cpy(n, s) add((n), memcpy((PTR) p, (PTR) (s), (n)))
96 #ifdef _LIBC
97 #define fmt(n, args) add((n), if (sprintf args != (n)) return 0)
98 #else
99 #define fmt(n, args) add((n), sprintf args; if (strlen (p) != (n)) return 0)
100 #endif
104 /* Return the week in the year specified by TP,
105 with weeks starting on STARTING_DAY. */
106 #ifdef __GNUC__
107 inline
108 #endif
109 static unsigned int
110 week (tp, starting_day, max_preceding)
111 const struct tm *const tp;
112 int starting_day;
113 int max_preceding;
115 int wday, dl, base;
117 wday = tp->tm_wday - starting_day;
118 if (wday < 0)
119 wday += 7;
121 /* Set DL to the day in the year of the first day of the week
122 containing the day specified in TP. */
123 dl = tp->tm_yday - wday;
125 /* For the computation following ISO 8601:1988 we set the number of
126 the week containing January 1st to 1 if this week has more than
127 MAX_PRECEDING days in the new year. For ISO 8601 this number is
128 3, for the other representation it is 7 (i.e., not to be
129 fulfilled). */
130 base = ((dl + 7) % 7) > max_preceding ? 1 : 0;
132 /* If DL is negative we compute the result as 0 unless we have to
133 compute it according ISO 8601. In this case we have to return 53
134 or 1 if the week containing January 1st has less than 4 days in
135 the new year or not. If DL is not negative we calculate the
136 number of complete weeks for our week (DL / 7) plus 1 (because
137 only for DL < 0 we are in week 0/53 and plus the number of the
138 first week computed in the last step. */
139 return dl < 0 ? (dl < -max_preceding ? 53 : base)
140 : base + 1 + dl / 7;
143 #ifndef _NL_CURRENT
144 static char const weekday_name[][10] =
146 "Sunday", "Monday", "Tuesday", "Wednesday",
147 "Thursday", "Friday", "Saturday"
149 static char const month_name[][10] =
151 "January", "February", "March", "April", "May", "June",
152 "July", "August", "September", "October", "November", "December"
154 #endif
156 /* Write information from TP into S according to the format
157 string FORMAT, writing no more that MAXSIZE characters
158 (including the terminating '\0') and returning number of
159 characters written. If S is NULL, nothing will be written
160 anywhere, so to determine how many characters would be
161 written, use NULL for S and (size_t) UINT_MAX for MAXSIZE. */
162 size_t
163 strftime (s, maxsize, format, tp)
164 char *s;
165 size_t maxsize;
166 const char *format;
167 register const struct tm *tp;
169 int hour12 = tp->tm_hour;
170 #ifdef _NL_CURRENT
171 const char *const a_wkday = _NL_CURRENT (LC_TIME, ABDAY_1 + tp->tm_wday);
172 const char *const f_wkday = _NL_CURRENT (LC_TIME, DAY_1 + tp->tm_wday);
173 const char *const a_month = _NL_CURRENT (LC_TIME, ABMON_1 + tp->tm_mon);
174 const char *const f_month = _NL_CURRENT (LC_TIME, MON_1 + tp->tm_mon);
175 const char *const ampm = _NL_CURRENT (LC_TIME,
176 hour12 > 11 ? PM_STR : AM_STR);
177 size_t aw_len = strlen(a_wkday);
178 size_t am_len = strlen(a_month);
179 size_t ap_len = strlen (ampm);
180 #else
181 const char *const f_wkday = weekday_name[tp->tm_wday];
182 const char *const f_month = month_name[tp->tm_mon];
183 const char *const a_wkday = f_wkday;
184 const char *const a_month = f_month;
185 const char *const ampm = "AMPM" + 2 * (hour12 > 11);
186 size_t aw_len = 3;
187 size_t am_len = 3;
188 size_t ap_len = 2;
189 #endif
190 size_t wkday_len = strlen(f_wkday);
191 size_t month_len = strlen(f_month);
192 const unsigned int y_week0 = week (tp, 0, 7);
193 const unsigned int y_week1 = week (tp, 1, 7);
194 const unsigned int y_week2 = week (tp, 1, 3);
195 const char *zone;
196 size_t zonelen;
197 register size_t i = 0;
198 register char *p = s;
199 register const char *f;
200 char number_fmt[5];
202 /* Initialize the buffer we will use for the sprintf format for numbers. */
203 number_fmt[0] = '%';
205 zone = 0;
206 #if HAVE_TM_ZONE
207 zone = (const char *) tp->tm_zone;
208 #endif
209 #if HAVE_TZNAME
210 if (!(zone && *zone) && tp->tm_isdst >= 0)
211 zone = tzname[tp->tm_isdst];
212 #endif
213 if (!(zone && *zone))
214 zone = "???";
216 zonelen = strlen (zone);
218 if (hour12 > 12)
219 hour12 -= 12;
220 else
221 if (hour12 == 0) hour12 = 12;
223 for (f = format; *f != '\0'; ++f)
225 enum { pad_zero, pad_space, pad_none } pad; /* Padding for number. */
226 unsigned int maxdigits; /* Max digits for numeric format. */
227 unsigned int number_value; /* Numeric value to be printed. */
228 const char *subfmt;
230 #if HAVE_MBLEN
231 if (!isascii(*f))
233 /* Non-ASCII, may be a multibyte. */
234 int len = mblen(f, strlen(f));
235 if (len > 0)
237 cpy(len, f);
238 continue;
241 #endif
243 if (*f != '%')
245 add(1, *p = *f);
246 continue;
249 /* Check for flags that can modify a number format. */
250 ++f;
251 switch (*f)
253 case '_':
254 pad = pad_space;
255 ++f;
256 break;
257 case '-':
258 pad = pad_none;
259 ++f;
260 break;
261 default:
262 pad = pad_zero;
263 break;
266 /* Now do the specified format. */
267 switch (*f)
269 case '\0':
270 case '%':
271 add(1, *p = *f);
272 break;
274 case 'a':
275 cpy(aw_len, a_wkday);
276 break;
278 case 'A':
279 cpy(wkday_len, f_wkday);
280 break;
282 case 'b':
283 case 'h': /* GNU extension. */
284 cpy(am_len, a_month);
285 break;
287 case 'B':
288 cpy(month_len, f_month);
289 break;
291 case 'c':
292 #ifdef _NL_CURRENT
293 subfmt = _NL_CURRENT (LC_TIME, D_T_FMT);
294 #else
295 subfmt = "%a %b %d %H:%M:%S %Z %Y";
296 #endif
297 subformat:
299 size_t len = strftime (p, maxsize - i, subfmt, tp);
300 if (len == 0 && *subfmt)
301 return 0;
302 add (len, ;);
304 break;
306 #define DO_NUMBER(digits, value) \
307 maxdigits = digits; number_value = value; goto do_number
308 #define DO_NUMBER_SPACEPAD(digits, value) \
309 maxdigits = digits; number_value = value; goto do_number_spacepad
311 case 'C':
312 DO_NUMBER (2, (1900 + tp->tm_year) / 100);
314 case 'x':
315 #ifdef _NL_CURRENT
316 subfmt = _NL_CURRENT (LC_TIME, D_FMT);
317 goto subformat;
318 #endif
319 /* Fall through. */
320 case 'D': /* GNU extension. */
321 subfmt = "%m/%d/%y";
322 goto subformat;
324 case 'd':
325 DO_NUMBER (2, tp->tm_mday);
327 case 'e': /* GNU extension: %d, but blank-padded. */
328 DO_NUMBER_SPACEPAD (2, tp->tm_mday);
330 /* All numeric formats set MAXDIGITS and NUMBER_VALUE and then
331 jump to one of these two labels. */
333 do_number_spacepad:
334 /* Force `_' flag. */
335 pad = pad_space;
337 do_number:
339 /* Format the number according to the PAD flag. */
341 register char *nf = &number_fmt[1];
342 int printed = maxdigits;
344 switch (pad)
346 case pad_zero:
347 *nf++ = '0';
348 case pad_space:
349 *nf++ = '0' + maxdigits;
350 case pad_none:
351 *nf++ = 'u';
352 *nf = '\0';
355 #ifdef _LIBC
356 add (maxdigits, printed = sprintf (p, number_fmt, number_value));
357 #else
358 add (maxdigits, sprintf (p, number_fmt, number_value);
359 printed = strlen (p));
360 #endif
361 /* Back up if fewer than MAXDIGITS chars written for pad_none. */
362 p -= maxdigits - printed;
363 i -= maxdigits - printed;
365 break;
369 case 'H':
370 DO_NUMBER (2, tp->tm_hour);
372 case 'I':
373 DO_NUMBER (2, hour12);
375 case 'k': /* GNU extension. */
376 DO_NUMBER_SPACEPAD (2, tp->tm_hour);
378 case 'l': /* GNU extension. */
379 DO_NUMBER_SPACEPAD (2, hour12);
381 case 'j':
382 DO_NUMBER (3, 1 + tp->tm_yday);
384 case 'M':
385 DO_NUMBER (2, tp->tm_min);
387 case 'm':
388 DO_NUMBER (2, tp->tm_mon + 1);
390 case 'n': /* GNU extension. */
391 add (1, *p = '\n');
392 break;
394 case 'p':
395 cpy(ap_len, ampm);
396 break;
398 case 'R': /* GNU extension. */
399 subfmt = "%H:%M";
400 goto subformat;
402 case 'r': /* GNU extension. */
403 subfmt = "%I:%M:%S %p";
404 goto subformat;
406 case 'S':
407 DO_NUMBER (2, tp->tm_sec);
409 case 'X':
410 #ifdef _NL_CURRENT
411 subfmt = _NL_CURRENT (LC_TIME, T_FMT);
412 goto subformat;
413 #endif
414 /* Fall through. */
415 case 'T': /* GNU extenstion. */
416 subfmt = "%H:%M:%S";
417 goto subformat;
419 case 't': /* GNU extenstion. */
420 add (1, *p = '\t');
421 break;
423 case 'U':
424 DO_NUMBER (2, y_week0);
426 case 'V':
427 DO_NUMBER (2, y_week2);
429 case 'W':
430 DO_NUMBER (2, y_week1);
432 case 'w':
433 DO_NUMBER (2, tp->tm_wday);
435 case 'Y':
436 DO_NUMBER (4, 1900 + tp->tm_year);
438 case 'y':
439 DO_NUMBER (2, tp->tm_year % 100);
441 case 'Z':
442 cpy(zonelen, zone);
443 break;
445 default:
446 /* Bad format. */
447 break;
451 if (p)
452 *p = '\0';
453 return i;