Formerly ../time/Makefile.~22~
[kopensolaris-gnu/glibc.git] / time / strftime.c
1 /* Extensions for GNU date that are still missing here:
2    -
3    _
4    e
5 */
6
7 /* Copyright (C) 1991, 1992 Free Software Foundation, Inc.
8 This file is part of the GNU C Library.
9
10 The GNU C Library is free software; you can redistribute it and/or
11 modify it under the terms of the GNU Library General Public License as
12 published by the Free Software Foundation; either version 2 of the
13 License, or (at your option) any later version.
14
15 The GNU C Library is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18 Library General Public License for more details.
19
20 You should have received a copy of the GNU Library General Public
21 License along with the GNU C Library; see the file COPYING.LIB.  If
22 not, write to the Free Software Foundation, Inc., 675 Mass Ave,
23 Cambridge, MA 02139, USA.  */
24
25 #include <ansidecl.h>
26 #include <localeinfo.h>
27 #include <ctype.h>
28 #include <limits.h>
29 #include <stddef.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <time.h>
34
35 #ifndef HAVE_GNU_LD
36 #define __tzname        tzname
37 #define __daylight      daylight
38 #define __timezone      timezone
39 #endif
40
41
42 #define add(n, f)                                                             \
43   do                                                                          \
44     {                                                                         \
45       i += (n);                                                               \
46       if (i >= maxsize)                                                       \
47         return 0;                                                             \
48       else                                                                    \
49         if (p != NULL)                                                        \
50           {                                                                   \
51             f;                                                                \
52             p += (n);                                                         \
53           }                                                                   \
54     } while (0)
55 #define cpy(n, s)       add((n), memcpy((PTR) p, (PTR) (s), (n)))
56 #define fmt(n, args)    add((n), if (sprintf args != (n)) return 0)
57
58 /* Return the week in the year specified by TP,
59    with weeks starting on STARTING_DAY.  */
60 #ifdef  __GNUC__
61 inline
62 #endif
63 static unsigned int
64 DEFUN(week, (tp, starting_day),
65       CONST struct tm *CONST tp AND int starting_day)
66 {
67   int wday, dl;
68
69   wday = tp->tm_wday - starting_day;
70   if (wday < 0)
71     wday += 7;
72
73   /* Set DL to the day in the year of the last day of the week previous to the
74      one containing the day specified in TP.  If DL is negative or zero, the
75      day specified in TP is in the first week of the year.  Otherwise,
76      calculate the number of complete weeks before our week (DL / 7) and
77      add any partial week at the start of the year (DL % 7).  */
78   dl = tp->tm_yday - wday;
79   return dl <= 0 ? 0 : ((dl / 7) + ((dl % 7) == 0 ? 0 : 1));
80 }
81
82
83 /* Write information from TP into S according to the format
84    string FORMAT, writing no more that MAXSIZE characters
85    (including the terminating '\0') and returning number of
86    characters written.  If S is NULL, nothing will be written
87    anywhere, so to determine how many characters would be
88    written, use NULL for S and (size_t) UINT_MAX for MAXSIZE.  */
89 size_t
90 DEFUN(strftime, (s, maxsize, format, tp),
91       char *s AND size_t maxsize AND
92       CONST char *format AND register CONST struct tm *tp)
93 {
94   CONST char *CONST a_wkday = _time_info->abbrev_wkday[tp->tm_wday];
95   CONST char *CONST f_wkday = _time_info->full_wkday[tp->tm_wday];
96   CONST char *CONST a_month = _time_info->abbrev_month[tp->tm_mon];
97   CONST char *CONST f_month = _time_info->full_month[tp->tm_mon];
98   size_t aw_len = strlen(a_wkday);
99   size_t am_len = strlen(a_month);
100   size_t wkday_len = strlen(f_wkday);
101   size_t month_len = strlen(f_month);
102   int hour12 = tp->tm_hour;
103   CONST char *CONST ampm = _time_info->ampm[hour12 >= 12];
104   size_t ap_len = strlen(ampm);
105   CONST unsigned int y_week0 = week(tp, 0);
106   CONST unsigned int y_week1 = week(tp, 1);
107   CONST char *zone;
108   size_t zonelen;
109   register size_t i = 0;
110   register char *p = s;
111   register CONST char *f;
112
113   if (tp->tm_isdst < 0)
114     {
115       zone = "";
116       zonelen = 0;
117     }
118   else
119     {
120       zone = __tzname[tp->tm_isdst];
121       zonelen = strlen(zone);
122     }
123
124   if (hour12 > 12)
125     hour12 -= 12;
126   else
127     if (hour12 == 0) hour12 = 12;
128
129   for (f = format; *f != '\0'; ++f)
130     {
131       CONST char *subfmt;
132
133       if (!isascii(*f))
134         {
135           /* Non-ASCII, may be a multibyte.  */
136           int len = mblen(f, strlen(f));
137           if (len > 0)
138             {
139               cpy(len, f);
140               continue;
141             }
142         }
143
144       if (*f != '%')
145         {
146           add(1, *p = *f);
147           continue;
148         }
149
150       ++f;
151       switch (*f)
152         {
153         case '\0':
154         case '%':
155           add(1, *p = *f);
156           break;
157         case 'n':               /* GNU extension.  */
158           add (1, *p = '\n');
159           break;
160         case 't':               /* GNU extenstion.  */
161           add (1, *p = '\t');
162           break;
163
164         case 'a':
165           cpy(aw_len, a_wkday);
166           break;
167         case 'A':
168           cpy(wkday_len, f_wkday);
169           break;
170         case 'b':
171           cpy(am_len, a_month);
172           break;
173         case 'B':
174           cpy(month_len, f_month);
175           break;
176         case 'c':
177           subfmt = _time_info->date_time;
178         subformat:;
179           {
180             size_t len = strftime(p, maxsize - i, subfmt, tp);
181             add(len, );
182           }
183           break;
184         case 'd':
185           fmt(2, (p, "%.2d", tp->tm_mday));
186           break;
187         case 'H':
188           fmt(2, (p, "%.2d", tp->tm_hour));
189           break;
190         case 'I':
191           fmt(2, (p, "%.2d", hour12));
192           break;
193         case 'j':
194           fmt(3, (p, "%.3d", tp->tm_yday));
195           break;
196         case 'm':
197           fmt(2, (p, "%.2d", tp->tm_mon + 1));
198           break;
199         case 'M':
200           fmt(2, (p, "%.2d", tp->tm_min));
201           break;
202         case 'p':
203           cpy(ap_len, ampm);
204           break;
205         case 'S':
206           fmt(2, (p, "%.2d", tp->tm_sec));
207           break;
208         case 'U':
209           fmt(2, (p, "%.2u", y_week0));
210           break;
211         case 'w':
212           fmt(2, (p, "%.2d", tp->tm_wday));
213           break;
214         case 'W':
215           fmt(2, (p, "%.2u", y_week1));
216           break;
217         case 'x':
218           subfmt = _time_info->date;
219           goto subformat;
220         case 'X':
221           subfmt = _time_info->time;
222           goto subformat;
223         case 'y':
224           fmt(2, (p, "%.2d", tp->tm_year));
225           break;
226         case 'Y':
227           fmt(4, (p, "%.4d", 1900 + tp->tm_year));
228           break;
229         case 'Z':
230           cpy(zonelen, zone);
231           break;
232
233           /* GNU extensions.  */
234         case 'r':
235           subfmt = "%I:%M:%S %p";
236           goto subformat;
237         case 'R':
238           subfmt = "%H:%M";
239           goto subformat;
240         case 'T':
241           subfmt = "%H:%M:%S";
242           goto subformat;
243         case 'D':
244           subfmt = "%m/%d/%y";
245           goto subformat;
246
247         case 'e':               /* %d, but blank-padded.  */
248           /* XXX */
249           break;
250
251         default:
252           /* Bad format.  */
253           break;
254         }
255     }
256
257   if (p != NULL)
258     *p = '\0';
259   return(i);
260 }