1 /*
2 * Copyright (c) 1989, 1993
3 * The Regents of the University of California. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 * must display the following acknowledgement:
15 * This product includes software developed by the University of
16 * California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34 #if defined(LIBC_SCCS) && !defined(lint)
35 static char sccsid[] = "@(#)strftime.c 8.1 (Berkeley) 6/4/93";
36 #endif /* LIBC_SCCS and not lint */
37
38 #include <sys/types.h>
39 #include <sys/time.h>
40 #include <tzfile.h>
41 #include <limits.h>
42 #include <stdio.h>
43
44 /*
45 ** 302 / 1000 is log10(2.0) rounded up.
46 ** Subtract one for the sign bit;
47 ** add one for integer division truncation;
48 ** add one more for a minus sign.
49 */
50 #define INT_STRLEN_MAXIMUM(type) \
51 ((sizeof(type) * CHAR_BIT - 1) * 302 / 1000 + 2)
52
53 /*
54 ** Based on elsieid[] = "@(#)strftime.c 7.15"
55 **
56 ** This is ANSIish only when time is treated identically in all locales and
57 ** when "multibyte character == plain character".
58 */
59
60 static const char afmt[][4] = {
61 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
62 };
63 static const char Afmt[][10] = {
64 "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday",
65 "Saturday"
66 };
67 static const char bfmt[][4] = {
68 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep",
69 "Oct", "Nov", "Dec"
70 };
71 static const char Bfmt[][10] = {
72 "January", "February", "March", "April", "May", "June", "July",
73 "August", "September", "October", "November", "December"
74 };
75
76 static char *_add __P((const char *, char *, const char *));
77 static char *_conv __P((int, const char *, char *, const char *));
78 static char *_secs __P((const struct tm *, char *, const char *));
79 static char *_fmt __P((const char *, const struct tm *, char *, const char *));
80
81 extern char *tzname[];
82
83 size_t
strftime(s,maxsize,format,t)84 strftime(s, maxsize, format, t)
85 char *s;
86 size_t maxsize;
87 const char *format;
88 const struct tm *t;
89 {
90 char *p;
91
92 p = _fmt(format, t, s, s + maxsize);
93 if (p == s + maxsize)
94 return (0);
95 *p = '\0';
96 return (p - s);
97 }
98
99 static char *
_fmt(format,t,pt,ptlim)100 _fmt(format, t, pt, ptlim)
101 const char *format;
102 const struct tm *t;
103 char *pt;
104 const char *ptlim;
105 {
106 for (; *format; ++format) {
107 if (*format == '%') {
108 label:
109 switch(*++format) {
110 case '\0':
111 --format;
112 break;
113 case 'A':
114 pt = _add((t->tm_wday < 0 || t->tm_wday > 6) ?
115 "?" : Afmt[t->tm_wday], pt, ptlim);
116 continue;
117 case 'a':
118 pt = _add((t->tm_wday < 0 || t->tm_wday > 6) ?
119 "?" : afmt[t->tm_wday], pt, ptlim);
120 continue;
121 case 'B':
122 pt = _add((t->tm_mon < 0 || t->tm_mon > 11) ?
123 "?" : Bfmt[t->tm_mon], pt, ptlim);
124 continue;
125 case 'b':
126 case 'h':
127 pt = _add((t->tm_mon < 0 || t->tm_mon > 11) ?
128 "?" : bfmt[t->tm_mon], pt, ptlim);
129 continue;
130 case 'c':
131 pt = _fmt("%D %X", t, pt, ptlim);
132 continue;
133 case 'C':
134 /*
135 ** %C used to do a...
136 ** _fmt("%a %b %e %X %Y", t);
137 ** ...whereas now POSIX 1003.2 calls for
138 ** something completely different.
139 ** (ado, 5/24/93)
140 */
141 pt = _conv((t->tm_year + TM_YEAR_BASE) / 100,
142 "%02d", pt, ptlim);
143 continue;
144 case 'D':
145 pt = _fmt("%m/%d/%y", t, pt, ptlim);
146 continue;
147 case 'x':
148 /*
149 ** Version 3.0 of strftime from Arnold Robbins
150 ** (arnold@skeeve.atl.ga.us) does the
151 ** equivalent of...
152 ** _fmt("%a %b %e %Y");
153 ** ...for %x; since the X3J11 C language
154 ** standard calls for "date, using locale's
155 ** date format," anything goes. Using just
156 ** numbers (as here) makes Quakers happier.
157 ** Word from Paul Eggert (eggert@twinsun.com)
158 ** is that %Y-%m-%d is the ISO standard date
159 ** format, specified in ISO 2014 and later
160 ** ISO 8601:1988, with a summary available in
161 ** pub/doc/ISO/english/ISO8601.ps.Z on
162 ** ftp.uni-erlangen.de.
163 ** (ado, 5/30/93)
164 */
165 pt = _fmt("%m/%d/%y", t, pt, ptlim);
166 continue;
167 case 'd':
168 pt = _conv(t->tm_mday, "%02d", pt, ptlim);
169 continue;
170 case 'E':
171 case 'O':
172 /*
173 ** POSIX locale extensions, a la
174 ** Arnold Robbins' strftime version 3.0.
175 ** The sequences
176 ** %Ec %EC %Ex %Ey %EY
177 ** %Od %oe %OH %OI %Om %OM
178 ** %OS %Ou %OU %OV %Ow %OW %Oy
179 ** are supposed to provide alternate
180 ** representations.
181 ** (ado, 5/24/93)
182 */
183 goto label;
184 case 'e':
185 pt = _conv(t->tm_mday, "%2d", pt, ptlim);
186 continue;
187 case 'H':
188 pt = _conv(t->tm_hour, "%02d", pt, ptlim);
189 continue;
190 case 'I':
191 pt = _conv((t->tm_hour % 12) ?
192 (t->tm_hour % 12) : 12,
193 "%02d", pt, ptlim);
194 continue;
195 case 'j':
196 pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim);
197 continue;
198 case 'k':
199 /*
200 ** This used to be...
201 ** _conv(t->tm_hour % 12 ?
202 ** t->tm_hour % 12 : 12, 2, ' ');
203 ** ...and has been changed to the below to
204 ** match SunOS 4.1.1 and Arnold Robbins'
205 ** strftime version 3.0. That is, "%k" and
206 ** "%l" have been swapped.
207 ** (ado, 5/24/93)
208 */
209 pt = _conv(t->tm_hour, "%2d", pt, ptlim);
210 continue;
211 #ifdef KITCHEN_SINK
212 case 'K':
213 /*
214 ** After all this time, still unclaimed!
215 */
216 pt = _add("kitchen sink", pt, ptlim);
217 continue;
218 #endif /* defined KITCHEN_SINK */
219 case 'l':
220 /*
221 ** This used to be...
222 ** _conv(t->tm_hour, 2, ' ');
223 ** ...and has been changed to the below to
224 ** match SunOS 4.1.1 and Arnold Robbin's
225 ** strftime version 3.0. That is, "%k" and
226 ** "%l" have been swapped.
227 ** (ado, 5/24/93)
228 */
229 pt = _conv((t->tm_hour % 12) ?
230 (t->tm_hour % 12) : 12,
231 "%2d", pt, ptlim);
232 continue;
233 case 'M':
234 pt = _conv(t->tm_min, "%02d", pt, ptlim);
235 continue;
236 case 'm':
237 pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim);
238 continue;
239 case 'n':
240 pt = _add("\n", pt, ptlim);
241 continue;
242 case 'p':
243 pt = _add(t->tm_hour >= 12 ? "PM" : "AM",
244 pt, ptlim);
245 continue;
246 case 'R':
247 pt = _fmt("%H:%M", t, pt, ptlim);
248 continue;
249 case 'r':
250 pt = _fmt("%I:%M:%S %p", t, pt, ptlim);
251 continue;
252 case 'S':
253 pt = _conv(t->tm_sec, "%02d", pt, ptlim);
254 continue;
255 case 's':
256 pt = _secs(t, pt, ptlim);
257 continue;
258 case 'T':
259 case 'X':
260 pt = _fmt("%H:%M:%S", t, pt, ptlim);
261 continue;
262 case 't':
263 pt = _add("\t", pt, ptlim);
264 continue;
265 case 'U':
266 pt = _conv((t->tm_yday + 7 - t->tm_wday) / 7,
267 "%02d", pt, ptlim);
268 continue;
269 case 'u':
270 /*
271 ** From Arnold Robbins' strftime version 3.0:
272 ** "ISO 8601: Weekday as a decimal number
273 ** [1 (Monday) - 7]"
274 ** (ado, 5/24/93)
275 */
276 pt = _conv((t->tm_wday == 0) ? 7 : t->tm_wday,
277 "%d", pt, ptlim);
278 continue;
279 case 'V':
280 /*
281 ** From Arnold Robbins' strftime version 3.0:
282 ** "the week number of the year (the first
283 ** Monday as the first day of week 1) as a
284 ** decimal number (01-53). The method for
285 ** determining the week number is as specified
286 ** by ISO 8601 (to wit: if the week containing
287 ** January 1 has four or more days in the new
288 ** year, then it is week 1, otherwise it is
289 ** week 53 of the previous year and the next
290 ** week is week 1)."
291 ** (ado, 5/24/93)
292 */
293 /*
294 ** XXX--If January 1 falls on a Friday,
295 ** January 1-3 are part of week 53 of the
296 ** previous year. By analogy, if January
297 ** 1 falls on a Thursday, are December 29-31
298 ** of the PREVIOUS year part of week 1???
299 ** (ado 5/24/93)
300 **
301 ** You are understood not to expect this.
302 */
303 {
304 int i;
305
306 i = (t->tm_yday + 10 - (t->tm_wday ?
307 (t->tm_wday - 1) : 6)) / 7;
308 pt = _conv((i == 0) ? 53 : i,
309 "%02d", pt, ptlim);
310 }
311 continue;
312 #ifdef notdef
313 /* Not in POSIX date(1), System V or ANSI C. */
314 case 'v':
315 /*
316 ** From Arnold Robbins' strftime version 3.0:
317 ** "date as dd-bbb-YYYY"
318 ** (ado, 5/24/93)
319 */
320 pt = _fmt("%e-%b-%Y", t, pt, ptlim);
321 continue;
322 #endif
323 case 'W':
324 pt = _conv((t->tm_yday + 7 -
325 (t->tm_wday ?
326 (t->tm_wday - 1) : 6)) / 7,
327 "%02d", pt, ptlim);
328 continue;
329 case 'w':
330 pt = _conv(t->tm_wday, "%d", pt, ptlim);
331 continue;
332 case 'y':
333 pt = _conv((t->tm_year + TM_YEAR_BASE) % 100,
334 "%02d", pt, ptlim);
335 continue;
336 case 'Y':
337 pt = _conv(t->tm_year + TM_YEAR_BASE, "%04d",
338 pt, ptlim);
339 continue;
340 case 'Z':
341 #ifdef TM_ZONE
342 if (t->TM_ZONE)
343 pt = _add(t->TM_ZONE, pt, ptlim);
344 else
345 #endif /* defined TM_ZONE */
346 if (t->tm_isdst == 0 || t->tm_isdst == 1) {
347 pt = _add(tzname[t->tm_isdst],
348 pt, ptlim);
349 } else pt = _add("?", pt, ptlim);
350 continue;
351 case '%':
352 /*
353 * X311J/88-090 (4.12.3.5): if conversion char is
354 * undefined, behavior is undefined. Print out the
355 * character itself as printf(3) does.
356 */
357 default:
358 break;
359 }
360 }
361 if (pt == ptlim)
362 break;
363 *pt++ = *format;
364 }
365 return (pt);
366 }
367
368 static char *
_secs(t,pt,ptlim)369 _secs(t, pt, ptlim)
370 const struct tm *t;
371 char *pt;
372 const char *ptlim;
373 {
374 struct tm tmp;
375 time_t s;
376
377 tmp = *t;
378 s = mktime(&tmp);
379 return (_conv((int)s, "%d", pt, ptlim));
380 }
381
382 static char *
_conv(n,format,pt,ptlim)383 _conv(n, format, pt, ptlim)
384 int n;
385 const char *format;
386 char *pt;
387 const char *ptlim;
388 {
389 char buf[INT_STRLEN_MAXIMUM(int) + 1];
390
391 (void) sprintf(buf, format, n);
392 return (_add(buf, pt, ptlim));
393 }
394
395 static char *
_add(str,pt,ptlim)396 _add(str, pt, ptlim)
397 const char *str;
398 char *pt;
399 const char *ptlim;
400 {
401
402 while (pt < ptlim && (*pt = *str++) != '\0')
403 ++pt;
404 return (pt);
405 }
406