Formerly ../time/sys/time.h.~16~
[kopensolaris-gnu/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 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 #include <ansidecl.h>
25 #include <localeinfo.h>
26 #include <ctype.h>
27 #include <limits.h>
28 #include <stddef.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <time.h>
33
34 #ifndef HAVE_GNU_LD
35 #define __tzname        tzname
36 #define __daylight      daylight
37 #define __timezone      timezone
38 #endif
39
40
41 #define add(n, f)                                                             \
42   do                                                                          \
43     {                                                                         \
44       i += (n);                                                               \
45       if (i >= maxsize)                                                       \
46         return 0;                                                             \
47       else                                                                    \
48         if (p != NULL)                                                        \
49           {                                                                   \
50             f;                                                                \
51             p += (n);                                                         \
52           }                                                                   \
53     } while (0)
54 #define cpy(n, s)       add((n), memcpy((PTR) p, (PTR) (s), (n)))
55 #define fmt(n, args)    add((n), if (sprintf args != (n)) return 0)
56
57 /* Return the week in the year specified by TP,
58    with weeks starting on STARTING_DAY.  */
59 #ifdef  __GNUC__
60 inline
61 #endif
62 static unsigned int
63 DEFUN(week, (tp, starting_day),
64       CONST struct tm *CONST tp AND int starting_day)
65 {
66   int wday, dl;
67
68   wday = tp->tm_wday - starting_day;
69   if (wday < 0)
70     wday += 7;
71
72   /* Set DL to the day in the year of the last day of the week previous to the
73      one containing the day specified in TP.  If DL is negative or zero, the
74      day specified in TP is in the first week of the year.  Otherwise,
75      calculate the number of complete weeks before our week (DL / 7) and
76      add any partial week at the start of the year (DL % 7).  */
77   dl = tp->tm_yday - wday;
78   return dl <= 0 ? 0 : ((dl / 7) + ((dl % 7) == 0 ? 0 : 1));
79 }
80
81
82 /* Write information from TP into S according to the format
83    string FORMAT, writing no more that MAXSIZE characters
84    (including the terminating '\0') and returning number of
85    characters written.  If S is NULL, nothing will be written
86    anywhere, so to determine how many characters would be
87    written, use NULL for S and (size_t) UINT_MAX for MAXSIZE.  */
88 size_t
89 DEFUN(strftime, (s, maxsize, format, tp),
90       char *s AND size_t maxsize AND
91       CONST char *format AND register CONST struct tm *tp)
92 {
93   CONST char *CONST a_wkday = _time_info->abbrev_wkday[tp->tm_wday];
94   CONST char *CONST f_wkday = _time_info->full_wkday[tp->tm_wday];
95   CONST char *CONST a_month = _time_info->abbrev_month[tp->tm_mon];
96   CONST char *CONST f_month = _time_info->full_month[tp->tm_mon];
97   size_t aw_len = strlen(a_wkday);
98   size_t am_len = strlen(a_month);
99   size_t wkday_len = strlen(f_wkday);
100   size_t month_len = strlen(f_month);
101   int hour12 = tp->tm_hour;
102   CONST char *CONST ampm = _time_info->ampm[hour12 >= 12];
103   size_t ap_len = strlen(ampm);
104   CONST unsigned int y_week0 = week(tp, 0);
105   CONST unsigned int y_week1 = week(tp, 1);
106   CONST char *zone;
107   size_t zonelen;
108   register size_t i = 0;
109   register char *p = s;
110   register CONST char *f;
111
112   if (tp->tm_isdst < 0)
113     {
114       zone = "";
115       zonelen = 0;
116     }
117   else
118     {
119       zone = __tzname[tp->tm_isdst];
120       zonelen = strlen(zone);
121     }
122
123   if (hour12 > 12)
124     hour12 -= 12;
125   else
126     if (hour12 == 0) hour12 = 12;
127
128   for (f = format; *f != '\0'; ++f)
129     {
130       CONST char *subfmt;
131
132       if (!isascii(*f))
133         {
134           /* Non-ASCII, may be a multibyte.  */
135           int len = mblen(f, strlen(f));
136           if (len > 0)
137             {
138               cpy(len, f);
139               continue;
140             }
141         }
142
143       if (*f != '%')
144         {
145           add(1, *p = *f);
146           continue;
147         }
148
149       ++f;
150       switch (*f)
151         {
152         case '\0':
153         case '%':
154           add(1, *p = *f);
155           break;
156
157         case 'a':
158           cpy(aw_len, a_wkday);
159           break;
160
161         case 'A':
162           cpy(wkday_len, f_wkday);
163           break;
164
165         case 'b':
166         case 'h':               /* GNU extension.  */
167           cpy(am_len, a_month);
168           break;
169
170         case 'B':
171           cpy(month_len, f_month);
172           break;
173
174         case 'c':
175           subfmt = _time_info->date_time;
176         subformat:
177           {
178             size_t len = strftime (p, maxsize - i, subfmt, tp);
179             add(len, );
180           }
181           break;
182
183         case 'C':
184           fmt (2, (p, "%.2d", (1900 + tp->tm_year) / 100));
185           break;
186
187         case 'D':               /* GNU extension.  */
188           subfmt = "%m/%d/%y";
189           goto subformat;
190
191         case 'd':
192           fmt(2, (p, "%.2d", tp->tm_mday));
193           break;
194
195         case 'e':               /* GNU extension: %d, but blank-padded.  */
196           fmt(2, (p, "%2d", tp->tm_mday));
197           break;
198
199         case 'H':
200           fmt(2, (p, "%.2d", tp->tm_hour));
201           break;
202
203         case 'I':
204           fmt(2, (p, "%.2d", hour12));
205           break;
206
207         case 'k':               /* GNU extension.  */
208           fmt(2, (p, "%2d", tp->tm_hour));
209           break;
210
211         case 'l':               /* GNU extension.  */
212           fmt(2, (p, "%2d", hour12));
213           break;
214
215         case 'j':
216           fmt(3, (p, "%.3d", tp->tm_yday));
217           break;
218
219         case 'M':
220           fmt(2, (p, "%.2d", tp->tm_min));
221           break;
222
223         case 'm':
224           fmt(2, (p, "%.2d", tp->tm_mon + 1));
225           break;
226
227         case 'n':               /* GNU extension.  */
228           add (1, *p = '\n');
229           break;
230
231         case 'p':
232           cpy(ap_len, ampm);
233           break;
234
235         case 'R':               /* GNU extension.  */
236           subfmt = "%H:%M";
237           goto subformat;
238
239         case 'r':               /* GNU extension.  */
240           subfmt = "%I:%M:%S %p";
241           goto subformat;
242
243         case 'S':
244           fmt(2, (p, "%.2d", tp->tm_sec));
245           break;
246
247         case 'T':               /* GNU extenstion.  */
248           subfmt = "%H:%M:%S";
249           goto subformat;
250
251         case 't':               /* GNU extenstion.  */
252           add (1, *p = '\t');
253           break;
254
255         case 'U':
256           fmt(2, (p, "%.2u", y_week0));
257           break;
258
259         case 'W':
260           fmt(2, (p, "%.2u", y_week1));
261           break;
262
263         case 'w':
264           fmt(2, (p, "%.2d", tp->tm_wday));
265           break;
266
267         case 'X':
268           subfmt = _time_info->time;
269           goto subformat;
270
271         case 'x':
272           subfmt = _time_info->date;
273           goto subformat;
274
275         case 'Y':
276           fmt(4, (p, "%.4d", 1900 + tp->tm_year));
277           break;
278
279         case 'y':
280           fmt(2, (p, "%.2d", tp->tm_year));
281           break;
282
283         case 'Z':
284           cpy(zonelen, zone);
285           break;
286
287         default:
288           /* Bad format.  */
289           break;
290         }
291     }
292
293   if (p != NULL)
294     *p = '\0';
295   return i;
296 }