* time/tzset.c (__tzname, __daylight, __timezone): Don't check
[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, 1994, 1995 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 "../locale/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 = _NL_CURRENT (LC_TIME, ABDAY_1 + tp->tm_wday);
94   CONST char *CONST f_wkday = _NL_CURRENT (LC_TIME, DAY_1 + tp->tm_wday);
95   CONST char *CONST a_month = _NL_CURRENT (LC_TIME, ABMON_1 + tp->tm_mon);
96   CONST char *CONST f_month = _NL_CURRENT (LC_TIME, MON_1 + 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 = _NL_CURRENT (LC_TIME,
103                                         hour12 > 12 ? PM_STR : AM_STR);
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
158         case 'a':
159           cpy(aw_len, a_wkday);
160           break;
161
162         case 'A':
163           cpy(wkday_len, f_wkday);
164           break;
165
166         case 'b':
167         case 'h':               /* GNU extension.  */
168           cpy(am_len, a_month);
169           break;
170
171         case 'B':
172           cpy(month_len, f_month);
173           break;
174
175         case 'c':
176           subfmt = _NL_CURRENT (LC_TIME, D_T_FMT);
177         subformat:
178           {
179             size_t len = strftime (p, maxsize - i, subfmt, tp);
180             add(len, );
181           }
182           break;
183
184         case 'C':
185           fmt (2, (p, "%.2d", (1900 + tp->tm_year) / 100));
186           break;
187
188         case 'D':               /* GNU extension.  */
189           subfmt = "%m/%d/%y";
190           goto subformat;
191
192         case 'd':
193           fmt(2, (p, "%.2d", tp->tm_mday));
194           break;
195
196         case 'e':               /* GNU extension: %d, but blank-padded.  */
197           fmt(2, (p, "%2d", tp->tm_mday));
198           break;
199
200         case 'H':
201           fmt(2, (p, "%.2d", tp->tm_hour));
202           break;
203
204         case 'I':
205           fmt(2, (p, "%.2d", hour12));
206           break;
207
208         case 'k':               /* GNU extension.  */
209           fmt(2, (p, "%2d", tp->tm_hour));
210           break;
211
212         case 'l':               /* GNU extension.  */
213           fmt(2, (p, "%2d", hour12));
214           break;
215
216         case 'j':
217           fmt(3, (p, "%.3d", 1 + tp->tm_yday));
218           break;
219
220         case 'M':
221           fmt(2, (p, "%.2d", tp->tm_min));
222           break;
223
224         case 'm':
225           fmt(2, (p, "%.2d", tp->tm_mon + 1));
226           break;
227
228         case 'n':               /* GNU extension.  */
229           add (1, *p = '\n');
230           break;
231
232         case 'p':
233           cpy(ap_len, ampm);
234           break;
235
236         case 'R':               /* GNU extension.  */
237           subfmt = "%H:%M";
238           goto subformat;
239
240         case 'r':               /* GNU extension.  */
241           subfmt = "%I:%M:%S %p";
242           goto subformat;
243
244         case 'S':
245           fmt(2, (p, "%.2d", tp->tm_sec));
246           break;
247
248         case 'T':               /* GNU extenstion.  */
249           subfmt = "%H:%M:%S";
250           goto subformat;
251
252         case 't':               /* GNU extenstion.  */
253           add (1, *p = '\t');
254           break;
255
256         case 'U':
257           fmt(2, (p, "%.2u", y_week0));
258           break;
259
260         case 'W':
261           fmt(2, (p, "%.2u", y_week1));
262           break;
263
264         case 'w':
265           fmt(2, (p, "%.2d", tp->tm_wday));
266           break;
267
268         case 'X':
269           subfmt = _NL_CURRENT (LC_TIME, T_FMT);
270           goto subformat;
271
272         case 'x':
273           subfmt = _NL_CURRENT (LC_TIME, D_FMT);
274           goto subformat;
275
276         case 'Y':
277           fmt(4, (p, "%.4d", 1900 + tp->tm_year));
278           break;
279
280         case 'y':
281           fmt(2, (p, "%.2d", tp->tm_year));
282           break;
283
284         case 'Z':
285           cpy(zonelen, zone);
286           break;
287
288         default:
289           /* Bad format.  */
290           break;
291         }
292     }
293
294   if (p != NULL)
295     *p = '\0';
296   return i;
297 }