update from main archive 960911
[glibc/history.git] / time / strftime.c
blob26f4b7f35417f50ad17409a29163ec13560816fa
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_GMTOFF 1
27 # define HAVE_TM_ZONE 1
28 # define STDC_HEADERS 1
29 # include <ansidecl.h>
30 # include "../locale/localeinfo.h"
31 #endif
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 #ifndef CHAR_BIT
79 #define CHAR_BIT 8
80 #endif
82 #define TYPE_SIGNED(t) ((t) -1 < 0)
84 /* Bound on length of the string representing an integer value of type t.
85 Subtract one for the sign bit if t is signed;
86 302 / 1000 is log10 (2) rounded up;
87 add one for integer division truncation;
88 add one more for a minus sign if t is signed. */
89 #define INT_STRLEN_BOUND(t) \
90 ((sizeof (t) * CHAR_BIT - TYPE_SIGNED (t)) * 302 / 100 + 1 + TYPE_SIGNED (t))
92 #define TM_YEAR_BASE 1900
95 static unsigned int week __P ((const struct tm *const, int, int));
98 #define add(n, f) \
99 do \
101 i += (n); \
102 if (i >= maxsize) \
103 return 0; \
104 else \
105 if (p) \
107 f; \
108 p += (n); \
110 } while (0)
111 #define cpy(n, s) add ((n), memcpy((PTR) p, (PTR) (s), (n)))
113 #if ! HAVE_TM_GMTOFF
114 /* Yield the difference between *A and *B,
115 measured in seconds, ignoring leap seconds. */
116 static int tm_diff __P ((const struct tm *, const struct tm *));
117 static int
118 tm_diff (a, b)
119 const struct tm *a;
120 const struct tm *b;
122 int ay = a->tm_year + TM_YEAR_BASE - 1;
123 int by = b->tm_year + TM_YEAR_BASE - 1;
124 /* Divide years by 100, rounding towards minus infinity. */
125 int ac = ay / 100 - (ay % 100 < 0);
126 int bc = by / 100 - (by % 100 < 0);
127 int intervening_leap_days =
128 ((ay >> 2) - (by >> 2)) - (ac - bc) + ((ac >> 2) - (bc >> 2));
129 int years = ay - by;
130 int days = (365 * years + intervening_leap_days
131 + (a->tm_yday - b->tm_yday));
132 return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
133 + (a->tm_min - b->tm_min))
134 + (a->tm_sec - b->tm_sec));
136 #endif /* ! HAVE_TM_GMTOFF */
140 /* Return the week in the year specified by TP,
141 with weeks starting on STARTING_DAY. */
142 #ifdef __GNUC__
143 inline
144 #endif
145 static unsigned int
146 week (tp, starting_day, max_preceding)
147 const struct tm *const tp;
148 int starting_day;
149 int max_preceding;
151 int wday, dl, base;
153 wday = tp->tm_wday - starting_day;
154 if (wday < 0)
155 wday += 7;
157 /* Set DL to the day in the year of the first day of the week
158 containing the day specified in TP. */
159 dl = tp->tm_yday - wday;
161 /* For the computation following ISO 8601:1988 we set the number of
162 the week containing January 1st to 1 if this week has more than
163 MAX_PRECEDING days in the new year. For ISO 8601 this number is
164 3, for the other representation it is 7 (i.e., not to be
165 fulfilled). */
166 base = ((dl + 7) % 7) > max_preceding ? 1 : 0;
168 /* If DL is negative we compute the result as 0 unless we have to
169 compute it according ISO 8601. In this case we have to return 53
170 or 1 if the week containing January 1st has less than 4 days in
171 the new year or not. If DL is not negative we calculate the
172 number of complete weeks for our week (DL / 7) plus 1 (because
173 only for DL < 0 we are in week 0/53 and plus the number of the
174 first week computed in the last step. */
175 return dl < 0 ? (dl < -max_preceding ? 53 : base)
176 : base + 1 + dl / 7;
179 #ifndef _NL_CURRENT
180 static char const weekday_name[][10] =
182 "Sunday", "Monday", "Tuesday", "Wednesday",
183 "Thursday", "Friday", "Saturday"
185 static char const month_name[][10] =
187 "January", "February", "March", "April", "May", "June",
188 "July", "August", "September", "October", "November", "December"
190 #endif
192 /* Write information from TP into S according to the format
193 string FORMAT, writing no more that MAXSIZE characters
194 (including the terminating '\0') and returning number of
195 characters written. If S is NULL, nothing will be written
196 anywhere, so to determine how many characters would be
197 written, use NULL for S and (size_t) UINT_MAX for MAXSIZE. */
198 size_t
199 strftime (s, maxsize, format, tp)
200 char *s;
201 size_t maxsize;
202 const char *format;
203 register const struct tm *tp;
205 int hour12 = tp->tm_hour;
206 #ifdef _NL_CURRENT
207 const char *const a_wkday = _NL_CURRENT (LC_TIME, ABDAY_1 + tp->tm_wday);
208 const char *const f_wkday = _NL_CURRENT (LC_TIME, DAY_1 + tp->tm_wday);
209 const char *const a_month = _NL_CURRENT (LC_TIME, ABMON_1 + tp->tm_mon);
210 const char *const f_month = _NL_CURRENT (LC_TIME, MON_1 + tp->tm_mon);
211 const char *const ampm = _NL_CURRENT (LC_TIME,
212 hour12 > 11 ? PM_STR : AM_STR);
213 size_t aw_len = strlen(a_wkday);
214 size_t am_len = strlen(a_month);
215 size_t ap_len = strlen (ampm);
217 const char * const*alt_digits = &_NL_CURRENT (LC_TIME, ALT_DIGITS);
218 int nr_alt_digits = (_NL_CURRENT (LC_TIME, ALT_DIGITS + 1) - *alt_digits);
219 #else
220 const char *const f_wkday = weekday_name[tp->tm_wday];
221 const char *const f_month = month_name[tp->tm_mon];
222 const char *const a_wkday = f_wkday;
223 const char *const a_month = f_month;
224 const char *const ampm = "AMPM" + 2 * (hour12 > 11);
225 size_t aw_len = 3;
226 size_t am_len = 3;
227 size_t ap_len = 2;
228 #endif
229 size_t wkday_len = strlen (f_wkday);
230 size_t month_len = strlen (f_month);
231 const unsigned int y_week0 = week (tp, 0, 7);
232 const unsigned int y_week1 = week (tp, 1, 7);
233 const unsigned int y_week2 = week (tp, 1, 3);
234 const char *zone;
235 size_t zonelen;
236 register size_t i = 0;
237 register char *p = s;
238 register const char *f;
240 zone = 0;
241 #if HAVE_TM_ZONE
242 zone = (const char *) tp->tm_zone;
243 #endif
244 #if HAVE_TZNAME
245 if (!(zone && *zone) && tp->tm_isdst >= 0)
246 zone = tzname[tp->tm_isdst];
247 #endif
248 if (!(zone && *zone))
249 zone = "???";
251 zonelen = strlen (zone);
253 if (hour12 > 12)
254 hour12 -= 12;
255 else
256 if (hour12 == 0) hour12 = 12;
258 for (f = format; *f != '\0'; ++f)
260 enum { pad_zero, pad_space, pad_none } pad; /* Padding for number. */
261 unsigned int digits; /* Max digits for numeric format. */
262 unsigned int number_value; /* Numeric value to be printed. */
263 int negative_number; /* 1 if the number is negative. */
264 const char *subfmt = "";
265 enum { none, alternate, era } modifier;
266 char *bufp;
267 char buf[1 + (sizeof (int) < sizeof (time_t)
268 ? INT_STRLEN_BOUND (time_t)
269 : INT_STRLEN_BOUND (int))];
271 #if HAVE_MBLEN
272 if (!isascii (*f))
274 /* Non-ASCII, may be a multibyte. */
275 int len = mblen (f, strlen (f));
276 if (len > 0)
278 cpy(len, f);
279 continue;
282 #endif
284 if (*f != '%')
286 add (1, *p = *f);
287 continue;
290 /* Check for flags that can modify a number format. */
291 ++f;
292 switch (*f)
294 case '_':
295 pad = pad_space;
296 ++f;
297 break;
298 case '-':
299 pad = pad_none;
300 ++f;
301 break;
302 default:
303 pad = pad_zero;
304 break;
307 /* Check for modifiers. */
308 switch (*f)
310 case 'E':
311 ++f;
312 modifier = era;
313 break;
314 case 'O':
315 ++f;
316 modifier = alternate;
317 break;
318 default:
319 modifier = none;
320 break;
323 /* Now do the specified format. */
324 switch (*f)
326 #define DO_NUMBER(d, v) \
327 digits = d; number_value = v; goto do_number
328 #define DO_NUMBER_SPACEPAD(d, v) \
329 digits = d; number_value = v; goto do_number_spacepad
331 case '\0': /* GNU extension: % at end of format. */
332 --f;
333 /* Fall through. */
334 case '%':
335 if (modifier != none)
336 goto bad_format;
337 add (1, *p = *f);
338 break;
340 case 'a':
341 if (modifier != none)
342 goto bad_format;
343 cpy (aw_len, a_wkday);
344 break;
346 case 'A':
347 if (modifier != none)
348 goto bad_format;
349 cpy (wkday_len, f_wkday);
350 break;
352 case 'b':
353 case 'h': /* GNU extension. */
354 if (modifier != none)
355 goto bad_format;
356 cpy (am_len, a_month);
357 break;
359 case 'B':
360 if (modifier != none)
361 goto bad_format;
362 cpy (month_len, f_month);
363 break;
365 case 'c':
366 if (modifier == alternate)
367 goto bad_format;
368 #ifdef _NL_CURRENT
369 if (modifier == era)
370 subfmt = _NL_CURRENT (LC_TIME, ERA_D_T_FMT);
371 if (*subfmt == '\0')
372 subfmt = _NL_CURRENT (LC_TIME, D_T_FMT);
373 #else
374 subfmt = "%a %b %e %H:%M:%S %Z %Y";
375 #endif
377 subformat:
379 size_t len = strftime (p, maxsize - i, subfmt, tp);
380 if (len == 0 && *subfmt)
381 return 0;
382 add (len, ;);
384 break;
386 case 'C':
387 if (modifier == alternate)
388 goto bad_format;
389 #ifdef _NL_CURRENT
390 /* XXX I'm not sure about this. --drepper@gnu */
391 if (modifier == era &&
392 *(subfmt = _NL_CURRENT (LC_TIME, ERA)) != '\0')
393 goto subformat;
394 #endif
395 DO_NUMBER (2, (1900 + tp->tm_year) / 100);
397 case 'x':
398 if (modifier == alternate)
399 goto bad_format;
400 #ifdef _NL_CURRENT
401 if (modifier == era)
402 subfmt = _NL_CURRENT (LC_TIME, ERA_D_FMT);
403 if (*subfmt == '\0')
404 subfmt = _NL_CURRENT (LC_TIME, D_FMT);
405 goto subformat;
406 #endif
407 /* Fall through. */
408 case 'D': /* GNU extension. */
409 subfmt = "%m/%d/%y";
410 goto subformat;
412 case 'd':
413 if (modifier == era)
414 goto bad_format;
416 DO_NUMBER (2, tp->tm_mday);
418 case 'e': /* GNU extension: %d, but blank-padded. */
419 if (modifier == era)
420 goto bad_format;
422 DO_NUMBER_SPACEPAD (2, tp->tm_mday);
424 /* All numeric formats set DIGITS and NUMBER_VALUE and then
425 jump to one of these two labels. */
427 do_number_spacepad:
428 /* Force `_' flag. */
429 pad = pad_space;
431 do_number:
432 /* Format the number according to the MODIFIER flag. */
434 #ifdef _NL_CURRENT
435 if (modifier == alternate && 0 <= number_value
436 && number_value < (unsigned int) nr_alt_digits)
438 /* ALT_DIGITS is the first entry in an array with
439 alternative digit symbols. */
440 size_t digitlen = strlen (*(alt_digits + number_value));
441 if (digitlen == 0)
442 break;
443 cpy (digitlen, *(alt_digits + number_value));
444 goto done_with_number;
446 #endif
448 unsigned int u = number_value;
450 bufp = buf + sizeof (buf);
451 negative_number = number_value < 0;
453 if (negative_number)
454 u = -u;
457 *--bufp = u % 10 + '0';
458 while ((u /= 10) != 0);
461 do_number_sign_and_padding:
462 if (negative_number)
463 *--bufp = '-';
465 if (pad != pad_none)
467 int padding = digits - (buf + sizeof (buf) - bufp);
469 if (pad == pad_space)
471 while (0 < padding--)
472 *--bufp = ' ';
474 else
476 bufp += negative_number;
477 while (0 < padding--)
478 *--bufp = '0';
479 if (negative_number)
480 *--bufp = '-';
484 cpy (buf + sizeof (buf) - bufp, bufp);
486 #ifdef _NL_CURRENT
487 done_with_number:
488 #endif
489 break;
492 case 'H':
493 if (modifier == era)
494 goto bad_format;
496 DO_NUMBER (2, tp->tm_hour);
498 case 'I':
499 if (modifier == era)
500 goto bad_format;
502 DO_NUMBER (2, hour12);
504 case 'k': /* GNU extension. */
505 if (modifier == era)
506 goto bad_format;
508 DO_NUMBER_SPACEPAD (2, tp->tm_hour);
510 case 'l': /* GNU extension. */
511 if (modifier == era)
512 goto bad_format;
514 DO_NUMBER_SPACEPAD (2, hour12);
516 case 'j':
517 if (modifier == era)
518 goto bad_format;
520 DO_NUMBER (3, 1 + tp->tm_yday);
522 case 'M':
523 if (modifier == era)
524 goto bad_format;
526 DO_NUMBER (2, tp->tm_min);
528 case 'm':
529 if (modifier == era)
530 goto bad_format;
532 DO_NUMBER (2, tp->tm_mon + 1);
534 case 'n': /* GNU extension. */
535 add (1, *p = '\n');
536 break;
538 case 'p':
539 cpy (ap_len, ampm);
540 break;
542 case 'R': /* GNU extension. */
543 subfmt = "%H:%M";
544 goto subformat;
546 case 'r': /* GNU extension. */
547 subfmt = "%I:%M:%S %p";
548 goto subformat;
550 case 'S':
551 if (modifier == era)
552 return 0;
554 DO_NUMBER (2, tp->tm_sec);
556 case 's': /* GNU extension. */
558 struct tm ltm = *tp;
559 time_t t = mktime (&ltm);
561 /* Generate string value for T using time_t arithmetic;
562 this works even if sizeof (long) < sizeof (time_t). */
564 bufp = buf + sizeof (buf);
565 negative_number = t < 0;
569 int d = t % 10;
570 t /= 10;
572 if (negative_number)
574 d = -d;
576 /* Adjust if division truncates to minus infinity. */
577 if (0 < -1 % 10 && d < 0)
579 t++;
580 d += 10;
584 *--bufp = d + '0';
586 while (t != 0);
588 digits = 1;
589 goto do_number_sign_and_padding;
592 case 'X':
593 if (modifier == alternate)
594 goto bad_format;
595 #ifdef _NL_CURRENT
596 if (modifier == era)
597 subfmt = _NL_CURRENT (LC_TIME, ERA_T_FMT);
598 if (*subfmt == '\0')
599 subfmt = _NL_CURRENT (LC_TIME, T_FMT);
600 goto subformat;
601 #endif
602 /* Fall through. */
603 case 'T': /* GNU extension. */
604 subfmt = "%H:%M:%S";
605 goto subformat;
607 case 't': /* GNU extension. */
608 add (1, *p = '\t');
609 break;
611 case 'U':
612 if (modifier == era)
613 goto bad_format;
615 DO_NUMBER (2, y_week0);
617 case 'V':
618 if (modifier == era)
619 goto bad_format;
621 DO_NUMBER (2, y_week2);
623 case 'W':
624 if (modifier == era)
625 goto bad_format;
627 DO_NUMBER (2, y_week1);
629 case 'w':
630 if (modifier == era)
631 goto bad_format;
633 DO_NUMBER (2, tp->tm_wday);
635 case 'Y':
636 #ifdef _NL_CURRENT
637 if (modifier == era
638 && *(subfmt = _NL_CURRENT (LC_TIME, ERA_YEAR)) != '\0')
639 goto subformat;
640 else
641 #endif
642 if (modifier == alternate)
643 goto bad_format;
644 else
645 DO_NUMBER (4, 1900 + tp->tm_year);
647 case 'y':
648 #ifdef _NL_CURRENT
649 if (modifier == era
650 && *(subfmt = _NL_CURRENT (LC_TIME, ERA_YEAR)) != '\0')
651 goto subformat;
652 #endif
653 DO_NUMBER (2, tp->tm_year % 100);
655 case 'Z':
656 cpy(zonelen, zone);
657 break;
659 case 'z': /* GNU extension. */
660 if (tp->tm_isdst < 0)
661 break;
664 int diff;
665 #if HAVE_TM_GMTOFF
666 diff = tp->tm_gmtoff;
667 #else
668 struct tm gtm;
669 struct tm ltm = *tp;
670 time_t lt = mktime (&ltm);
672 if (lt == (time_t) -1)
674 /* mktime returns -1 for errors, but -1 is also a
675 valid time_t value. Check whether an error really
676 occurred. */
677 struct tm tm;
678 localtime_r (&lt, &tm);
680 if ((ltm.tm_sec ^ tm.tm_sec)
681 | (ltm.tm_min ^ tm.tm_min)
682 | (ltm.tm_hour ^ tm.tm_hour)
683 | (ltm.tm_mday ^ tm.tm_mday)
684 | (ltm.tm_mon ^ tm.tm_mon)
685 | (ltm.tm_year ^ tm.tm_year))
686 break;
689 if (! gmtime_r (&lt, &gtm))
690 break;
692 diff = tm_diff (&ltm, &gtm);
693 #endif
695 if (diff < 0)
697 add (1, *p = '-');
698 diff = -diff;
700 else
701 add (1, *p = '+');
703 pad = pad_zero;
705 diff /= 60;
706 DO_NUMBER (4, (diff / 60) * 100 + diff % 60);
709 default:
710 /* Bad format. */
711 bad_format:
712 if (pad == pad_space)
713 add (1, *p = '_');
714 else if (pad == pad_zero)
715 add (1, *p = '0');
717 if (modifier == era)
718 add (1, *p = 'E');
719 else if (modifier == alternate)
720 add (1, *p = 'O');
722 add (1, *p = *f);
723 break;
727 if (p)
728 *p = '\0';
729 return i;