Deleted Added
full compact
strftime.c (15927) strftime.c (17141)
1/*
2 * Copyright (c) 1989 The Regents of the University of California.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms are permitted
6 * provided that the above copyright notice and this paragraph are
7 * duplicated in all such forms and that any documentation,
8 * advertising materials, and other materials related to such
9 * distribution and use acknowledge that the software was developed
10 * by the University of California, Berkeley. The name of the
11 * University may not be used to endorse or promote products derived
12 * from this software without specific prior written permission.
13 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
14 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
15 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
16 */
17
18#ifdef LIBC_RCS
19static const char rcsid[] =
1/*
2 * Copyright (c) 1989 The Regents of the University of California.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms are permitted
6 * provided that the above copyright notice and this paragraph are
7 * duplicated in all such forms and that any documentation,
8 * advertising materials, and other materials related to such
9 * distribution and use acknowledge that the software was developed
10 * by the University of California, Berkeley. The name of the
11 * University may not be used to endorse or promote products derived
12 * from this software without specific prior written permission.
13 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
14 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
15 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
16 */
17
18#ifdef LIBC_RCS
19static const char rcsid[] =
20 "$Id: strftime.c,v 1.7 1996/05/27 04:10:27 scrappy Exp $";
20 "$Id: strftime.c,v 1.8 1996/05/27 06:54:01 scrappy Exp $";
21#endif
22
23#ifndef lint
24#ifndef NOID
25static const char elsieid[] = "@(#)strftime.c 7.38";
26/*
27** Based on the UCB version with the ID appearing below.
28** This is ANSIish only when "multibyte character == plain character".
29*/
30#endif /* !defined NOID */
31#endif /* !defined lint */
32
33#include "private.h"
34
35#ifndef LIBC_SCCS
36#ifndef lint
37static const char sccsid[] = "@(#)strftime.c 5.4 (Berkeley) 3/14/89";
38#endif /* !defined lint */
39#endif /* !defined LIBC_SCCS */
40
41#include "tzfile.h"
42#include <fcntl.h>
43#include <locale.h>
44#include <rune.h> /* for _PATH_LOCALE */
45#include <sys/stat.h>
46
47#define LOCALE_HOME _PATH_LOCALE
48
49struct lc_time_T {
50 const char * mon[12];
51 const char * month[12];
52 const char * wday[7];
53 const char * weekday[7];
54 const char * X_fmt;
55 const char * x_fmt;
56 const char * c_fmt;
57 const char * am;
58 const char * pm;
59 const char * date_fmt;
60};
61
62static struct lc_time_T localebuf;
21#endif
22
23#ifndef lint
24#ifndef NOID
25static const char elsieid[] = "@(#)strftime.c 7.38";
26/*
27** Based on the UCB version with the ID appearing below.
28** This is ANSIish only when "multibyte character == plain character".
29*/
30#endif /* !defined NOID */
31#endif /* !defined lint */
32
33#include "private.h"
34
35#ifndef LIBC_SCCS
36#ifndef lint
37static const char sccsid[] = "@(#)strftime.c 5.4 (Berkeley) 3/14/89";
38#endif /* !defined lint */
39#endif /* !defined LIBC_SCCS */
40
41#include "tzfile.h"
42#include <fcntl.h>
43#include <locale.h>
44#include <rune.h> /* for _PATH_LOCALE */
45#include <sys/stat.h>
46
47#define LOCALE_HOME _PATH_LOCALE
48
49struct lc_time_T {
50 const char * mon[12];
51 const char * month[12];
52 const char * wday[7];
53 const char * weekday[7];
54 const char * X_fmt;
55 const char * x_fmt;
56 const char * c_fmt;
57 const char * am;
58 const char * pm;
59 const char * date_fmt;
60};
61
62static struct lc_time_T localebuf;
63static struct lc_time_T * _loc P((void));
64static int using_locale;
65
66#define Locale (using_locale ? &localebuf : &C_time_locale)
67
68static const struct lc_time_T C_time_locale = {
69 {
70 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
71 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
72 }, {
73 "January", "February", "March", "April", "May", "June",
74 "July", "August", "September", "October", "November", "December"
75 }, {
76 "Sun", "Mon", "Tue", "Wed",
77 "Thu", "Fri", "Sat"
78 }, {
79 "Sunday", "Monday", "Tuesday", "Wednesday",
80 "Thursday", "Friday", "Saturday"
81 },
82
83 /* X_fmt */
84 "%H:%M:%S",
85
86 /*
87 ** x_fmt
88 ** Since the C language standard calls for
89 ** "date, using locale's date format," anything goes.
90 ** Using just numbers (as here) makes Quakers happier;
91 ** it's also compatible with SVR4.
92 */
93 "%m/%d/%y",
94
95 /*
96 ** c_fmt (ctime-compatible)
97 ** Note that
98 ** "%a %b %d %H:%M:%S %Y"
99 ** is used by Solaris 2.3.
100 */
101 "%a %b %e %X %Y",
102
103 /* am */
104 "AM",
105
106 /* pm */
107 "PM",
108
109 /* date_fmt */
110 "%a %b %e %X %Z %Y"
111};
112
113static char * _add P((const char *, char *, const char *));
114static char * _conv P((int, const char *, char *, const char *));
115static char * _fmt P((const char *, const struct tm *, char *, const char *));
116static char * _secs P((const struct tm *, char *, const char *));
117
118size_t strftime P((char *, size_t, const char *, const struct tm *));
119
120extern char * tzname[];
121
122size_t
123strftime(s, maxsize, format, t)
124 char *const s;
125 const size_t maxsize;
126 const char *const format;
127 const struct tm *const t;
128{
129 char *p;
130
131 tzset();
132 p = _fmt(((format == NULL) ? "%c" : format), t, s, s + maxsize);
133 if (p == s + maxsize)
134 return 0;
135 *p = '\0';
136 return p - s;
137}
138
139static char *
140_fmt(format, t, pt, ptlim)
141 const char *format;
142 const struct tm *const t;
143 char *pt;
144 const char *const ptlim;
145{
146 for ( ; *format; ++format) {
147 if (*format == '%') {
148label:
149 switch (*++format) {
150 case '\0':
151 --format;
152 break;
153 case 'A':
154 pt = _add((t->tm_wday < 0 || t->tm_wday > 6) ?
155 "?" : Locale->weekday[t->tm_wday],
156 pt, ptlim);
157 continue;
158 case 'a':
159 pt = _add((t->tm_wday < 0 || t->tm_wday > 6) ?
160 "?" : Locale->wday[t->tm_wday],
161 pt, ptlim);
162 continue;
163 case 'B':
164 pt = _add((t->tm_mon < 0 || t->tm_mon > 11) ?
165 "?" : Locale->month[t->tm_mon],
166 pt, ptlim);
167 continue;
168 case 'b':
169 case 'h':
170 pt = _add((t->tm_mon < 0 || t->tm_mon > 11) ?
171 "?" : Locale->mon[t->tm_mon],
172 pt, ptlim);
173 continue;
174 case 'C':
175 /*
176 ** %C used to do a...
177 ** _fmt("%a %b %e %X %Y", t);
178 ** ...whereas now POSIX 1003.2 calls for
179 ** something completely different.
180 ** (ado, 5/24/93)
181 */
182 pt = _conv((t->tm_year + TM_YEAR_BASE) / 100,
183 "%02d", pt, ptlim);
184 continue;
185 case 'c':
186 pt = _fmt(Locale->c_fmt, t, pt, ptlim);
187 continue;
188 case 'D':
189 pt = _fmt("%m/%d/%y", t, pt, ptlim);
190 continue;
191 case 'd':
192 pt = _conv(t->tm_mday, "%02d", pt, ptlim);
193 continue;
194 case 'E':
195 case 'O':
196 /*
197 ** POSIX locale extensions, a la
198 ** Arnold Robbins' strftime version 3.0.
199 ** The sequences
200 ** %Ec %EC %Ex %Ey %EY
201 ** %Od %oe %OH %OI %Om %OM
202 ** %OS %Ou %OU %OV %Ow %OW %Oy
203 ** are supposed to provide alternate
204 ** representations.
205 ** (ado, 5/24/93)
206 */
207 goto label;
208 case 'e':
209 pt = _conv(t->tm_mday, "%2d", pt, ptlim);
210 continue;
211 case 'H':
212 pt = _conv(t->tm_hour, "%02d", pt, ptlim);
213 continue;
214 case 'I':
215 pt = _conv((t->tm_hour % 12) ?
216 (t->tm_hour % 12) : 12,
217 "%02d", pt, ptlim);
218 continue;
219 case 'j':
220 pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim);
221 continue;
222 case 'k':
223 /*
224 ** This used to be...
225 ** _conv(t->tm_hour % 12 ?
226 ** t->tm_hour % 12 : 12, 2, ' ');
227 ** ...and has been changed to the below to
228 ** match SunOS 4.1.1 and Arnold Robbins'
229 ** strftime version 3.0. That is, "%k" and
230 ** "%l" have been swapped.
231 ** (ado, 5/24/93)
232 */
233 pt = _conv(t->tm_hour, "%2d", pt, ptlim);
234 continue;
235#ifdef KITCHEN_SINK
236 case 'K':
237 /*
238 ** After all this time, still unclaimed!
239 */
240 pt = _add("kitchen sink", pt, ptlim);
241 continue;
242#endif /* defined KITCHEN_SINK */
243 case 'l':
244 /*
245 ** This used to be...
246 ** _conv(t->tm_hour, 2, ' ');
247 ** ...and has been changed to the below to
248 ** match SunOS 4.1.1 and Arnold Robbin's
249 ** strftime version 3.0. That is, "%k" and
250 ** "%l" have been swapped.
251 ** (ado, 5/24/93)
252 */
253 pt = _conv((t->tm_hour % 12) ?
254 (t->tm_hour % 12) : 12,
255 "%2d", pt, ptlim);
256 continue;
257 case 'M':
258 pt = _conv(t->tm_min, "%02d", pt, ptlim);
259 continue;
260 case 'm':
261 pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim);
262 continue;
263 case 'n':
264 pt = _add("\n", pt, ptlim);
265 continue;
266 case 'p':
267 pt = _add((t->tm_hour >= 12) ?
268 Locale->pm :
269 Locale->am,
270 pt, ptlim);
271 continue;
272 case 'R':
273 pt = _fmt("%H:%M", t, pt, ptlim);
274 continue;
275 case 'r':
276 pt = _fmt("%I:%M:%S %p", t, pt, ptlim);
277 continue;
278 case 'S':
279 pt = _conv(t->tm_sec, "%02d", pt, ptlim);
280 continue;
281 case 's':
282 pt = _secs(t, pt, ptlim);
283 continue;
284 case 'T':
285 pt = _fmt("%H:%M:%S", t, pt, ptlim);
286 continue;
287 case 't':
288 pt = _add("\t", pt, ptlim);
289 continue;
290 case 'U':
291 pt = _conv((t->tm_yday + 7 - t->tm_wday) / 7,
292 "%02d", pt, ptlim);
293 continue;
294 case 'u':
295 /*
296 ** From Arnold Robbins' strftime version 3.0:
297 ** "ISO 8601: Weekday as a decimal number
298 ** [1 (Monday) - 7]"
299 ** (ado, 5/24/93)
300 */
301 pt = _conv((t->tm_wday == 0) ? 7 : t->tm_wday,
302 "%d", pt, ptlim);
303 continue;
304 case 'V':
305 /*
306 ** From Arnold Robbins' strftime version 3.0:
307 ** "the week number of the year (the first
308 ** Monday as the first day of week 1) as a
309 ** decimal number (01-53). The method for
310 ** determining the week number is as specified
311 ** by ISO 8601 (to wit: if the week containing
312 ** January 1 has four or more days in the new
313 ** year, then it is week 1, otherwise it is
314 ** week 53 of the previous year and the next
315 ** week is week 1)."
316 ** (ado, 5/24/93)
317 */
318 /*
319 ** XXX--If January 1 falls on a Friday,
320 ** January 1-3 are part of week 53 of the
321 ** previous year. By analogy, if January
322 ** 1 falls on a Thursday, are December 29-31
323 ** of the PREVIOUS year part of week 1???
324 ** (ado 5/24/93)
325 */
326 /*
327 ** You are understood not to expect this.
328 */
329 {
330 int i;
331
332 i = (t->tm_yday + 10 - (t->tm_wday ?
333 (t->tm_wday - 1) : 6)) / 7;
334 if (i == 0) {
335 /*
336 ** What day of the week does
337 ** January 1 fall on?
338 */
339 i = t->tm_wday -
340 (t->tm_yday - 1);
341 /*
342 ** Fri Jan 1: 53
343 ** Sun Jan 1: 52
344 ** Sat Jan 1: 53 if previous
345 ** year a leap
346 ** year, else 52
347 */
348 if (i == TM_FRIDAY)
349 i = 53;
350 else if (i == TM_SUNDAY)
351 i = 52;
352 else i = isleap(t->tm_year +
353 TM_YEAR_BASE) ?
354 53 : 52;
355#ifdef XPG4_1994_04_09
356 /*
357 ** As of 4/9/94, though,
358 ** XPG4 calls for 53
359 ** unconditionally.
360 */
361 i = 53;
362#endif /* defined XPG4_1994_04_09 */
363 }
364 pt = _conv(i, "%02d", pt, ptlim);
365 }
366 continue;
367 case 'v':
368 /*
369 ** From Arnold Robbins' strftime version 3.0:
370 ** "date as dd-bbb-YYYY"
371 ** (ado, 5/24/93)
372 */
373 pt = _fmt("%e-%b-%Y", t, pt, ptlim);
374 continue;
375 case 'W':
376 pt = _conv((t->tm_yday + 7 -
377 (t->tm_wday ?
378 (t->tm_wday - 1) : 6)) / 7,
379 "%02d", pt, ptlim);
380 continue;
381 case 'w':
382 pt = _conv(t->tm_wday, "%d", pt, ptlim);
383 continue;
384 case 'X':
385 pt = _fmt(Locale->X_fmt, t, pt, ptlim);
386 continue;
387 case 'x':
388 pt = _fmt(Locale->x_fmt, t, pt, ptlim);
389 continue;
390 case 'y':
391 pt = _conv((t->tm_year + TM_YEAR_BASE) % 100,
392 "%02d", pt, ptlim);
393 continue;
394 case 'Y':
395 pt = _conv(t->tm_year + TM_YEAR_BASE, "%04d",
396 pt, ptlim);
397 continue;
398 case 'Z':
399 if (t->tm_zone != NULL)
400 pt = _add(t->tm_zone, pt, ptlim);
401 else
402 if (t->tm_isdst == 0 || t->tm_isdst == 1) {
403 pt = _add(tzname[t->tm_isdst],
404 pt, ptlim);
405 } else pt = _add("?", pt, ptlim);
406 continue;
407 case '+':
408 pt = _fmt(Locale->date_fmt, t, pt, ptlim);
409 continue;
410 case '%':
411 /*
412 * X311J/88-090 (4.12.3.5): if conversion char is
413 * undefined, behavior is undefined. Print out the
414 * character itself as printf(3) also does.
415 */
416 default:
417 break;
418 }
419 }
420 if (pt == ptlim)
421 break;
422 *pt++ = *format;
423 }
424 return pt;
425}
426
427static char *
428_conv(n, format, pt, ptlim)
429 const int n;
430 const char *const format;
431 char *const pt;
432 const char *const ptlim;
433{
434 char buf[INT_STRLEN_MAXIMUM(int) + 1];
435
436 (void) sprintf(buf, format, n);
437 return _add(buf, pt, ptlim);
438}
439
440static char *
441_secs(t, pt, ptlim)
442 const struct tm *t;
443 char *pt;
444 const char *ptlim;
445{
446 char buf[INT_STRLEN_MAXIMUM(int) + 1];
447 register time_t s;
448 struct tm tmp;
449
450 /* Make a copy, mktime(3) modifies the tm struct. */
451 tmp = *t;
452 s = mktime(&tmp);
453 (void) sprintf(buf, "%ld", s);
454 return _add(buf, pt, ptlim);
455}
456
457static char *
458_add(str, pt, ptlim)
459 const char *str;
460 char *pt;
461 const char *const ptlim;
462{
463 while (pt < ptlim && (*pt = *str++) != '\0')
464 ++pt;
465 return pt;
466}
467
468extern char *_PathLocale;
469
470int
471__time_load_locale(const char *name)
472{
473 static const char lc_time[] = "LC_TIME";
474 static char * locale_buf;
475 static char locale_buf_C[] = "C";
476
477 int fd;
478 char * lbuf;
479 char * p;
480 const char ** ap;
481 const char * plim;
482 char filename[FILENAME_MAX];
483 struct stat st;
484 size_t namesize;
485 size_t bufsize;
486 int save_using_locale;
487
488 save_using_locale = using_locale;
489 using_locale = 0;
490
491 if (name == NULL)
492 goto no_locale;
493
494 if (!*name || !strcmp(name, "C") || !strcmp(name, "POSIX"))
495 return 0;
496
497 /*
498 ** If the locale name is the same as our cache, use the cache.
499 */
500 lbuf = locale_buf;
501 if (lbuf != NULL && strcmp(name, lbuf) == 0) {
502 p = lbuf;
503 for (ap = (const char **) &localebuf;
504 ap < (const char **) (&localebuf + 1);
505 ++ap)
506 *ap = p += strlen(p) + 1;
507 using_locale = 1;
508 return 0;
509 }
510 /*
511 ** Slurp the locale file into the cache.
512 */
513 namesize = strlen(name) + 1;
514
515 snprintf(filename, sizeof filename,
516 "%s/%s/%s",
517 _PathLocale, name, lc_time);
518 fd = open(filename, O_RDONLY);
519 if (fd < 0)
520 goto no_locale;
521 if (fstat(fd, &st) != 0)
522 goto bad_locale;
523 if (st.st_size <= 0)
524 goto bad_locale;
525 bufsize = namesize + st.st_size;
526 locale_buf = NULL;
527 lbuf = (lbuf == NULL || lbuf == locale_buf_C) ?
528 malloc(bufsize) : realloc(lbuf, bufsize);
529 if (lbuf == NULL)
530 goto bad_locale;
531 (void) strcpy(lbuf, name);
532 p = lbuf + namesize;
533 plim = p + st.st_size;
534 if (read(fd, p, (size_t) st.st_size) != st.st_size)
535 goto bad_lbuf;
536 if (close(fd) != 0)
537 goto bad_lbuf;
538 /*
539 ** Parse the locale file into localebuf.
540 */
541 if (plim[-1] != '\n')
542 goto bad_lbuf;
543 for (ap = (const char **) &localebuf;
544 ap < (const char **) (&localebuf + 1);
545 ++ap) {
546 if (p == plim)
547 goto reset_locale;
548 *ap = p;
549 while (*p != '\n')
550 ++p;
551 *p++ = '\0';
552 }
553 /*
554 ** Record the successful parse in the cache.
555 */
556 locale_buf = lbuf;
557
558 using_locale = 1;
559 return 0;
560
561reset_locale:
562 /*
563 * XXX - This may not be the correct thing to do in this case.
564 * setlocale() assumes that we left the old locale alone.
565 */
566 locale_buf = locale_buf_C;
567 localebuf = C_time_locale;
568 save_using_locale = 0;
569bad_lbuf:
570 free(lbuf);
571bad_locale:
572 (void) close(fd);
573no_locale:
574 using_locale = save_using_locale;
575 return -1;
576}
63static int using_locale;
64
65#define Locale (using_locale ? &localebuf : &C_time_locale)
66
67static const struct lc_time_T C_time_locale = {
68 {
69 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
70 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
71 }, {
72 "January", "February", "March", "April", "May", "June",
73 "July", "August", "September", "October", "November", "December"
74 }, {
75 "Sun", "Mon", "Tue", "Wed",
76 "Thu", "Fri", "Sat"
77 }, {
78 "Sunday", "Monday", "Tuesday", "Wednesday",
79 "Thursday", "Friday", "Saturday"
80 },
81
82 /* X_fmt */
83 "%H:%M:%S",
84
85 /*
86 ** x_fmt
87 ** Since the C language standard calls for
88 ** "date, using locale's date format," anything goes.
89 ** Using just numbers (as here) makes Quakers happier;
90 ** it's also compatible with SVR4.
91 */
92 "%m/%d/%y",
93
94 /*
95 ** c_fmt (ctime-compatible)
96 ** Note that
97 ** "%a %b %d %H:%M:%S %Y"
98 ** is used by Solaris 2.3.
99 */
100 "%a %b %e %X %Y",
101
102 /* am */
103 "AM",
104
105 /* pm */
106 "PM",
107
108 /* date_fmt */
109 "%a %b %e %X %Z %Y"
110};
111
112static char * _add P((const char *, char *, const char *));
113static char * _conv P((int, const char *, char *, const char *));
114static char * _fmt P((const char *, const struct tm *, char *, const char *));
115static char * _secs P((const struct tm *, char *, const char *));
116
117size_t strftime P((char *, size_t, const char *, const struct tm *));
118
119extern char * tzname[];
120
121size_t
122strftime(s, maxsize, format, t)
123 char *const s;
124 const size_t maxsize;
125 const char *const format;
126 const struct tm *const t;
127{
128 char *p;
129
130 tzset();
131 p = _fmt(((format == NULL) ? "%c" : format), t, s, s + maxsize);
132 if (p == s + maxsize)
133 return 0;
134 *p = '\0';
135 return p - s;
136}
137
138static char *
139_fmt(format, t, pt, ptlim)
140 const char *format;
141 const struct tm *const t;
142 char *pt;
143 const char *const ptlim;
144{
145 for ( ; *format; ++format) {
146 if (*format == '%') {
147label:
148 switch (*++format) {
149 case '\0':
150 --format;
151 break;
152 case 'A':
153 pt = _add((t->tm_wday < 0 || t->tm_wday > 6) ?
154 "?" : Locale->weekday[t->tm_wday],
155 pt, ptlim);
156 continue;
157 case 'a':
158 pt = _add((t->tm_wday < 0 || t->tm_wday > 6) ?
159 "?" : Locale->wday[t->tm_wday],
160 pt, ptlim);
161 continue;
162 case 'B':
163 pt = _add((t->tm_mon < 0 || t->tm_mon > 11) ?
164 "?" : Locale->month[t->tm_mon],
165 pt, ptlim);
166 continue;
167 case 'b':
168 case 'h':
169 pt = _add((t->tm_mon < 0 || t->tm_mon > 11) ?
170 "?" : Locale->mon[t->tm_mon],
171 pt, ptlim);
172 continue;
173 case 'C':
174 /*
175 ** %C used to do a...
176 ** _fmt("%a %b %e %X %Y", t);
177 ** ...whereas now POSIX 1003.2 calls for
178 ** something completely different.
179 ** (ado, 5/24/93)
180 */
181 pt = _conv((t->tm_year + TM_YEAR_BASE) / 100,
182 "%02d", pt, ptlim);
183 continue;
184 case 'c':
185 pt = _fmt(Locale->c_fmt, t, pt, ptlim);
186 continue;
187 case 'D':
188 pt = _fmt("%m/%d/%y", t, pt, ptlim);
189 continue;
190 case 'd':
191 pt = _conv(t->tm_mday, "%02d", pt, ptlim);
192 continue;
193 case 'E':
194 case 'O':
195 /*
196 ** POSIX locale extensions, a la
197 ** Arnold Robbins' strftime version 3.0.
198 ** The sequences
199 ** %Ec %EC %Ex %Ey %EY
200 ** %Od %oe %OH %OI %Om %OM
201 ** %OS %Ou %OU %OV %Ow %OW %Oy
202 ** are supposed to provide alternate
203 ** representations.
204 ** (ado, 5/24/93)
205 */
206 goto label;
207 case 'e':
208 pt = _conv(t->tm_mday, "%2d", pt, ptlim);
209 continue;
210 case 'H':
211 pt = _conv(t->tm_hour, "%02d", pt, ptlim);
212 continue;
213 case 'I':
214 pt = _conv((t->tm_hour % 12) ?
215 (t->tm_hour % 12) : 12,
216 "%02d", pt, ptlim);
217 continue;
218 case 'j':
219 pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim);
220 continue;
221 case 'k':
222 /*
223 ** This used to be...
224 ** _conv(t->tm_hour % 12 ?
225 ** t->tm_hour % 12 : 12, 2, ' ');
226 ** ...and has been changed to the below to
227 ** match SunOS 4.1.1 and Arnold Robbins'
228 ** strftime version 3.0. That is, "%k" and
229 ** "%l" have been swapped.
230 ** (ado, 5/24/93)
231 */
232 pt = _conv(t->tm_hour, "%2d", pt, ptlim);
233 continue;
234#ifdef KITCHEN_SINK
235 case 'K':
236 /*
237 ** After all this time, still unclaimed!
238 */
239 pt = _add("kitchen sink", pt, ptlim);
240 continue;
241#endif /* defined KITCHEN_SINK */
242 case 'l':
243 /*
244 ** This used to be...
245 ** _conv(t->tm_hour, 2, ' ');
246 ** ...and has been changed to the below to
247 ** match SunOS 4.1.1 and Arnold Robbin's
248 ** strftime version 3.0. That is, "%k" and
249 ** "%l" have been swapped.
250 ** (ado, 5/24/93)
251 */
252 pt = _conv((t->tm_hour % 12) ?
253 (t->tm_hour % 12) : 12,
254 "%2d", pt, ptlim);
255 continue;
256 case 'M':
257 pt = _conv(t->tm_min, "%02d", pt, ptlim);
258 continue;
259 case 'm':
260 pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim);
261 continue;
262 case 'n':
263 pt = _add("\n", pt, ptlim);
264 continue;
265 case 'p':
266 pt = _add((t->tm_hour >= 12) ?
267 Locale->pm :
268 Locale->am,
269 pt, ptlim);
270 continue;
271 case 'R':
272 pt = _fmt("%H:%M", t, pt, ptlim);
273 continue;
274 case 'r':
275 pt = _fmt("%I:%M:%S %p", t, pt, ptlim);
276 continue;
277 case 'S':
278 pt = _conv(t->tm_sec, "%02d", pt, ptlim);
279 continue;
280 case 's':
281 pt = _secs(t, pt, ptlim);
282 continue;
283 case 'T':
284 pt = _fmt("%H:%M:%S", t, pt, ptlim);
285 continue;
286 case 't':
287 pt = _add("\t", pt, ptlim);
288 continue;
289 case 'U':
290 pt = _conv((t->tm_yday + 7 - t->tm_wday) / 7,
291 "%02d", pt, ptlim);
292 continue;
293 case 'u':
294 /*
295 ** From Arnold Robbins' strftime version 3.0:
296 ** "ISO 8601: Weekday as a decimal number
297 ** [1 (Monday) - 7]"
298 ** (ado, 5/24/93)
299 */
300 pt = _conv((t->tm_wday == 0) ? 7 : t->tm_wday,
301 "%d", pt, ptlim);
302 continue;
303 case 'V':
304 /*
305 ** From Arnold Robbins' strftime version 3.0:
306 ** "the week number of the year (the first
307 ** Monday as the first day of week 1) as a
308 ** decimal number (01-53). The method for
309 ** determining the week number is as specified
310 ** by ISO 8601 (to wit: if the week containing
311 ** January 1 has four or more days in the new
312 ** year, then it is week 1, otherwise it is
313 ** week 53 of the previous year and the next
314 ** week is week 1)."
315 ** (ado, 5/24/93)
316 */
317 /*
318 ** XXX--If January 1 falls on a Friday,
319 ** January 1-3 are part of week 53 of the
320 ** previous year. By analogy, if January
321 ** 1 falls on a Thursday, are December 29-31
322 ** of the PREVIOUS year part of week 1???
323 ** (ado 5/24/93)
324 */
325 /*
326 ** You are understood not to expect this.
327 */
328 {
329 int i;
330
331 i = (t->tm_yday + 10 - (t->tm_wday ?
332 (t->tm_wday - 1) : 6)) / 7;
333 if (i == 0) {
334 /*
335 ** What day of the week does
336 ** January 1 fall on?
337 */
338 i = t->tm_wday -
339 (t->tm_yday - 1);
340 /*
341 ** Fri Jan 1: 53
342 ** Sun Jan 1: 52
343 ** Sat Jan 1: 53 if previous
344 ** year a leap
345 ** year, else 52
346 */
347 if (i == TM_FRIDAY)
348 i = 53;
349 else if (i == TM_SUNDAY)
350 i = 52;
351 else i = isleap(t->tm_year +
352 TM_YEAR_BASE) ?
353 53 : 52;
354#ifdef XPG4_1994_04_09
355 /*
356 ** As of 4/9/94, though,
357 ** XPG4 calls for 53
358 ** unconditionally.
359 */
360 i = 53;
361#endif /* defined XPG4_1994_04_09 */
362 }
363 pt = _conv(i, "%02d", pt, ptlim);
364 }
365 continue;
366 case 'v':
367 /*
368 ** From Arnold Robbins' strftime version 3.0:
369 ** "date as dd-bbb-YYYY"
370 ** (ado, 5/24/93)
371 */
372 pt = _fmt("%e-%b-%Y", t, pt, ptlim);
373 continue;
374 case 'W':
375 pt = _conv((t->tm_yday + 7 -
376 (t->tm_wday ?
377 (t->tm_wday - 1) : 6)) / 7,
378 "%02d", pt, ptlim);
379 continue;
380 case 'w':
381 pt = _conv(t->tm_wday, "%d", pt, ptlim);
382 continue;
383 case 'X':
384 pt = _fmt(Locale->X_fmt, t, pt, ptlim);
385 continue;
386 case 'x':
387 pt = _fmt(Locale->x_fmt, t, pt, ptlim);
388 continue;
389 case 'y':
390 pt = _conv((t->tm_year + TM_YEAR_BASE) % 100,
391 "%02d", pt, ptlim);
392 continue;
393 case 'Y':
394 pt = _conv(t->tm_year + TM_YEAR_BASE, "%04d",
395 pt, ptlim);
396 continue;
397 case 'Z':
398 if (t->tm_zone != NULL)
399 pt = _add(t->tm_zone, pt, ptlim);
400 else
401 if (t->tm_isdst == 0 || t->tm_isdst == 1) {
402 pt = _add(tzname[t->tm_isdst],
403 pt, ptlim);
404 } else pt = _add("?", pt, ptlim);
405 continue;
406 case '+':
407 pt = _fmt(Locale->date_fmt, t, pt, ptlim);
408 continue;
409 case '%':
410 /*
411 * X311J/88-090 (4.12.3.5): if conversion char is
412 * undefined, behavior is undefined. Print out the
413 * character itself as printf(3) also does.
414 */
415 default:
416 break;
417 }
418 }
419 if (pt == ptlim)
420 break;
421 *pt++ = *format;
422 }
423 return pt;
424}
425
426static char *
427_conv(n, format, pt, ptlim)
428 const int n;
429 const char *const format;
430 char *const pt;
431 const char *const ptlim;
432{
433 char buf[INT_STRLEN_MAXIMUM(int) + 1];
434
435 (void) sprintf(buf, format, n);
436 return _add(buf, pt, ptlim);
437}
438
439static char *
440_secs(t, pt, ptlim)
441 const struct tm *t;
442 char *pt;
443 const char *ptlim;
444{
445 char buf[INT_STRLEN_MAXIMUM(int) + 1];
446 register time_t s;
447 struct tm tmp;
448
449 /* Make a copy, mktime(3) modifies the tm struct. */
450 tmp = *t;
451 s = mktime(&tmp);
452 (void) sprintf(buf, "%ld", s);
453 return _add(buf, pt, ptlim);
454}
455
456static char *
457_add(str, pt, ptlim)
458 const char *str;
459 char *pt;
460 const char *const ptlim;
461{
462 while (pt < ptlim && (*pt = *str++) != '\0')
463 ++pt;
464 return pt;
465}
466
467extern char *_PathLocale;
468
469int
470__time_load_locale(const char *name)
471{
472 static const char lc_time[] = "LC_TIME";
473 static char * locale_buf;
474 static char locale_buf_C[] = "C";
475
476 int fd;
477 char * lbuf;
478 char * p;
479 const char ** ap;
480 const char * plim;
481 char filename[FILENAME_MAX];
482 struct stat st;
483 size_t namesize;
484 size_t bufsize;
485 int save_using_locale;
486
487 save_using_locale = using_locale;
488 using_locale = 0;
489
490 if (name == NULL)
491 goto no_locale;
492
493 if (!*name || !strcmp(name, "C") || !strcmp(name, "POSIX"))
494 return 0;
495
496 /*
497 ** If the locale name is the same as our cache, use the cache.
498 */
499 lbuf = locale_buf;
500 if (lbuf != NULL && strcmp(name, lbuf) == 0) {
501 p = lbuf;
502 for (ap = (const char **) &localebuf;
503 ap < (const char **) (&localebuf + 1);
504 ++ap)
505 *ap = p += strlen(p) + 1;
506 using_locale = 1;
507 return 0;
508 }
509 /*
510 ** Slurp the locale file into the cache.
511 */
512 namesize = strlen(name) + 1;
513
514 snprintf(filename, sizeof filename,
515 "%s/%s/%s",
516 _PathLocale, name, lc_time);
517 fd = open(filename, O_RDONLY);
518 if (fd < 0)
519 goto no_locale;
520 if (fstat(fd, &st) != 0)
521 goto bad_locale;
522 if (st.st_size <= 0)
523 goto bad_locale;
524 bufsize = namesize + st.st_size;
525 locale_buf = NULL;
526 lbuf = (lbuf == NULL || lbuf == locale_buf_C) ?
527 malloc(bufsize) : realloc(lbuf, bufsize);
528 if (lbuf == NULL)
529 goto bad_locale;
530 (void) strcpy(lbuf, name);
531 p = lbuf + namesize;
532 plim = p + st.st_size;
533 if (read(fd, p, (size_t) st.st_size) != st.st_size)
534 goto bad_lbuf;
535 if (close(fd) != 0)
536 goto bad_lbuf;
537 /*
538 ** Parse the locale file into localebuf.
539 */
540 if (plim[-1] != '\n')
541 goto bad_lbuf;
542 for (ap = (const char **) &localebuf;
543 ap < (const char **) (&localebuf + 1);
544 ++ap) {
545 if (p == plim)
546 goto reset_locale;
547 *ap = p;
548 while (*p != '\n')
549 ++p;
550 *p++ = '\0';
551 }
552 /*
553 ** Record the successful parse in the cache.
554 */
555 locale_buf = lbuf;
556
557 using_locale = 1;
558 return 0;
559
560reset_locale:
561 /*
562 * XXX - This may not be the correct thing to do in this case.
563 * setlocale() assumes that we left the old locale alone.
564 */
565 locale_buf = locale_buf_C;
566 localebuf = C_time_locale;
567 save_using_locale = 0;
568bad_lbuf:
569 free(lbuf);
570bad_locale:
571 (void) close(fd);
572no_locale:
573 using_locale = save_using_locale;
574 return -1;
575}