mdb: remove assorted sun4[uv] files
[unleashed.git] / contrib / tzcode / strftime.c
blob4b9fd2cb80cd4a05bc502fde68f8e721ac3d5746
1 /* Convert a broken-down timestamp to a string. */
3 /* Copyright 1989 The Regents of the University of California.
4 All rights reserved.
6 Redistribution and use in source and binary forms, with or without
7 modification, are permitted provided that the following conditions
8 are met:
9 1. Redistributions of source code must retain the above copyright
10 notice, this list of conditions and the following disclaimer.
11 2. Redistributions in binary form must reproduce the above copyright
12 notice, this list of conditions and the following disclaimer in the
13 documentation and/or other materials provided with the distribution.
14 3. Neither the name of the University nor the names of its contributors
15 may be used to endorse or promote products derived from this software
16 without specific prior written permission.
18 THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
19 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
22 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 SUCH DAMAGE. */
31 ** Based on the UCB version with the copyright notice appearing above.
33 ** This is ANSIish only when "multibyte character == plain character".
36 #include "private.h"
38 #include <fcntl.h>
39 #include <locale.h>
41 struct lc_time_T {
42 const char * mon[MONSPERYEAR];
43 const char * month[MONSPERYEAR];
44 const char * wday[DAYSPERWEEK];
45 const char * weekday[DAYSPERWEEK];
46 const char * X_fmt;
47 const char * x_fmt;
48 const char * c_fmt;
49 const char * am;
50 const char * pm;
51 const char * date_fmt;
54 #define Locale (&C_time_locale)
56 static const struct lc_time_T C_time_locale = {
58 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
59 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
60 }, {
61 "January", "February", "March", "April", "May", "June",
62 "July", "August", "September", "October", "November", "December"
63 }, {
64 "Sun", "Mon", "Tue", "Wed",
65 "Thu", "Fri", "Sat"
66 }, {
67 "Sunday", "Monday", "Tuesday", "Wednesday",
68 "Thursday", "Friday", "Saturday"
71 /* X_fmt */
72 "%H:%M:%S",
75 ** x_fmt
76 ** C99 requires this format.
77 ** Using just numbers (as here) makes Quakers happier;
78 ** it's also compatible with SVR4.
80 "%m/%d/%y",
83 ** c_fmt
84 ** C99 requires this format.
85 ** Previously this code used "%D %X", but we now conform to C99.
86 ** Note that
87 ** "%a %b %d %H:%M:%S %Y"
88 ** is used by Solaris 2.3.
90 "%a %b %e %T %Y",
92 /* am */
93 "AM",
95 /* pm */
96 "PM",
98 /* date_fmt */
99 "%a %b %e %H:%M:%S %Z %Y"
102 static char * _add(const char *, char *, const char *);
103 static char * _conv(int, const char *, char *, const char *);
104 static char * _fmt(const char *, const struct tm *, char *, const char *,
105 int *);
106 static char * _yconv(int, int, bool, bool, char *, char const *);
108 #if !HAVE_POSIX_DECLS
109 extern char * tzname[];
110 #endif
112 #ifndef YEAR_2000_NAME
113 #define YEAR_2000_NAME "CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS"
114 #endif /* !defined YEAR_2000_NAME */
116 #define IN_NONE 0
117 #define IN_SOME 1
118 #define IN_THIS 2
119 #define IN_ALL 3
121 #if HAVE_STRFTIME_L
122 size_t
123 strftime_l(char *s, size_t maxsize, char const *format, struct tm const *t,
124 locale_t locale)
126 /* Just call strftime, as only the C locale is supported. */
127 return strftime(s, maxsize, format, t);
129 #endif
131 size_t
132 strftime(char *s, size_t maxsize, const char *format, const struct tm *t)
134 char * p;
135 int warn;
137 tzset();
138 warn = IN_NONE;
139 p = _fmt(format, t, s, s + maxsize, &warn);
140 #ifndef NO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU
141 if (warn != IN_NONE && getenv(YEAR_2000_NAME) != NULL) {
142 fprintf(stderr, "\n");
143 fprintf(stderr, "strftime format \"%s\" ", format);
144 fprintf(stderr, "yields only two digits of years in ");
145 if (warn == IN_SOME)
146 fprintf(stderr, "some locales");
147 else if (warn == IN_THIS)
148 fprintf(stderr, "the current locale");
149 else fprintf(stderr, "all locales");
150 fprintf(stderr, "\n");
152 #endif /* !defined NO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU */
153 if (p == s + maxsize)
154 return 0;
155 *p = '\0';
156 return p - s;
159 static char *
160 _fmt(const char *format, const struct tm *t, char *pt,
161 const char *ptlim, int *warnp)
163 for ( ; *format; ++format) {
164 if (*format == '%') {
165 label:
166 switch (*++format) {
167 case '\0':
168 --format;
169 break;
170 case 'A':
171 pt = _add((t->tm_wday < 0 ||
172 t->tm_wday >= DAYSPERWEEK) ?
173 "?" : Locale->weekday[t->tm_wday],
174 pt, ptlim);
175 continue;
176 case 'a':
177 pt = _add((t->tm_wday < 0 ||
178 t->tm_wday >= DAYSPERWEEK) ?
179 "?" : Locale->wday[t->tm_wday],
180 pt, ptlim);
181 continue;
182 case 'B':
183 pt = _add((t->tm_mon < 0 ||
184 t->tm_mon >= MONSPERYEAR) ?
185 "?" : Locale->month[t->tm_mon],
186 pt, ptlim);
187 continue;
188 case 'b':
189 case 'h':
190 pt = _add((t->tm_mon < 0 ||
191 t->tm_mon >= MONSPERYEAR) ?
192 "?" : Locale->mon[t->tm_mon],
193 pt, ptlim);
194 continue;
195 case 'C':
197 ** %C used to do a...
198 ** _fmt("%a %b %e %X %Y", t);
199 ** ...whereas now POSIX 1003.2 calls for
200 ** something completely different.
201 ** (ado, 1993-05-24)
203 pt = _yconv(t->tm_year, TM_YEAR_BASE,
204 true, false, pt, ptlim);
205 continue;
206 case 'c':
208 int warn2 = IN_SOME;
210 pt = _fmt(Locale->c_fmt, t, pt, ptlim, &warn2);
211 if (warn2 == IN_ALL)
212 warn2 = IN_THIS;
213 if (warn2 > *warnp)
214 *warnp = warn2;
216 continue;
217 case 'D':
218 pt = _fmt("%m/%d/%y", t, pt, ptlim, warnp);
219 continue;
220 case 'd':
221 pt = _conv(t->tm_mday, "%02d", pt, ptlim);
222 continue;
223 case 'E':
224 case 'O':
226 ** C99 locale modifiers.
227 ** The sequences
228 ** %Ec %EC %Ex %EX %Ey %EY
229 ** %Od %oe %OH %OI %Om %OM
230 ** %OS %Ou %OU %OV %Ow %OW %Oy
231 ** are supposed to provide alternate
232 ** representations.
234 goto label;
235 case 'e':
236 pt = _conv(t->tm_mday, "%2d", pt, ptlim);
237 continue;
238 case 'F':
239 pt = _fmt("%Y-%m-%d", t, pt, ptlim, warnp);
240 continue;
241 case 'H':
242 pt = _conv(t->tm_hour, "%02d", pt, ptlim);
243 continue;
244 case 'I':
245 pt = _conv((t->tm_hour % 12) ?
246 (t->tm_hour % 12) : 12,
247 "%02d", pt, ptlim);
248 continue;
249 case 'j':
250 pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim);
251 continue;
252 case 'k':
254 ** This used to be...
255 ** _conv(t->tm_hour % 12 ?
256 ** t->tm_hour % 12 : 12, 2, ' ');
257 ** ...and has been changed to the below to
258 ** match SunOS 4.1.1 and Arnold Robbins'
259 ** strftime version 3.0. That is, "%k" and
260 ** "%l" have been swapped.
261 ** (ado, 1993-05-24)
263 pt = _conv(t->tm_hour, "%2d", pt, ptlim);
264 continue;
265 #ifdef KITCHEN_SINK
266 case 'K':
268 ** After all this time, still unclaimed!
270 pt = _add("kitchen sink", pt, ptlim);
271 continue;
272 #endif /* defined KITCHEN_SINK */
273 case 'l':
275 ** This used to be...
276 ** _conv(t->tm_hour, 2, ' ');
277 ** ...and has been changed to the below to
278 ** match SunOS 4.1.1 and Arnold Robbin's
279 ** strftime version 3.0. That is, "%k" and
280 ** "%l" have been swapped.
281 ** (ado, 1993-05-24)
283 pt = _conv((t->tm_hour % 12) ?
284 (t->tm_hour % 12) : 12,
285 "%2d", pt, ptlim);
286 continue;
287 case 'M':
288 pt = _conv(t->tm_min, "%02d", pt, ptlim);
289 continue;
290 case 'm':
291 pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim);
292 continue;
293 case 'n':
294 pt = _add("\n", pt, ptlim);
295 continue;
296 case 'p':
297 pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ?
298 Locale->pm :
299 Locale->am,
300 pt, ptlim);
301 continue;
302 case 'R':
303 pt = _fmt("%H:%M", t, pt, ptlim, warnp);
304 continue;
305 case 'r':
306 pt = _fmt("%I:%M:%S %p", t, pt, ptlim, warnp);
307 continue;
308 case 'S':
309 pt = _conv(t->tm_sec, "%02d", pt, ptlim);
310 continue;
311 case 's':
313 struct tm tm;
314 char buf[INT_STRLEN_MAXIMUM(
315 time_t) + 1];
316 time_t mkt;
318 tm = *t;
319 mkt = mktime(&tm);
320 if (TYPE_SIGNED(time_t))
321 sprintf(buf, "%"PRIdMAX,
322 (intmax_t) mkt);
323 else sprintf(buf, "%"PRIuMAX,
324 (uintmax_t) mkt);
325 pt = _add(buf, pt, ptlim);
327 continue;
328 case 'T':
329 pt = _fmt("%H:%M:%S", t, pt, ptlim, warnp);
330 continue;
331 case 't':
332 pt = _add("\t", pt, ptlim);
333 continue;
334 case 'U':
335 pt = _conv((t->tm_yday + DAYSPERWEEK -
336 t->tm_wday) / DAYSPERWEEK,
337 "%02d", pt, ptlim);
338 continue;
339 case 'u':
341 ** From Arnold Robbins' strftime version 3.0:
342 ** "ISO 8601: Weekday as a decimal number
343 ** [1 (Monday) - 7]"
344 ** (ado, 1993-05-24)
346 pt = _conv((t->tm_wday == 0) ?
347 DAYSPERWEEK : t->tm_wday,
348 "%d", pt, ptlim);
349 continue;
350 case 'V': /* ISO 8601 week number */
351 case 'G': /* ISO 8601 year (four digits) */
352 case 'g': /* ISO 8601 year (two digits) */
354 ** From Arnold Robbins' strftime version 3.0: "the week number of the
355 ** year (the first Monday as the first day of week 1) as a decimal number
356 ** (01-53)."
357 ** (ado, 1993-05-24)
359 ** From <http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html> by Markus Kuhn:
360 ** "Week 01 of a year is per definition the first week which has the
361 ** Thursday in this year, which is equivalent to the week which contains
362 ** the fourth day of January. In other words, the first week of a new year
363 ** is the week which has the majority of its days in the new year. Week 01
364 ** might also contain days from the previous year and the week before week
365 ** 01 of a year is the last week (52 or 53) of the previous year even if
366 ** it contains days from the new year. A week starts with Monday (day 1)
367 ** and ends with Sunday (day 7). For example, the first week of the year
368 ** 1997 lasts from 1996-12-30 to 1997-01-05..."
369 ** (ado, 1996-01-02)
372 int year;
373 int base;
374 int yday;
375 int wday;
376 int w;
378 year = t->tm_year;
379 base = TM_YEAR_BASE;
380 yday = t->tm_yday;
381 wday = t->tm_wday;
382 for ( ; ; ) {
383 int len;
384 int bot;
385 int top;
387 len = isleap_sum(year, base) ?
388 DAYSPERLYEAR :
389 DAYSPERNYEAR;
391 ** What yday (-3 ... 3) does
392 ** the ISO year begin on?
394 bot = ((yday + 11 - wday) %
395 DAYSPERWEEK) - 3;
397 ** What yday does the NEXT
398 ** ISO year begin on?
400 top = bot -
401 (len % DAYSPERWEEK);
402 if (top < -3)
403 top += DAYSPERWEEK;
404 top += len;
405 if (yday >= top) {
406 ++base;
407 w = 1;
408 break;
410 if (yday >= bot) {
411 w = 1 + ((yday - bot) /
412 DAYSPERWEEK);
413 break;
415 --base;
416 yday += isleap_sum(year, base) ?
417 DAYSPERLYEAR :
418 DAYSPERNYEAR;
420 #ifdef XPG4_1994_04_09
421 if ((w == 52 &&
422 t->tm_mon == TM_JANUARY) ||
423 (w == 1 &&
424 t->tm_mon == TM_DECEMBER))
425 w = 53;
426 #endif /* defined XPG4_1994_04_09 */
427 if (*format == 'V')
428 pt = _conv(w, "%02d",
429 pt, ptlim);
430 else if (*format == 'g') {
431 *warnp = IN_ALL;
432 pt = _yconv(year, base,
433 false, true,
434 pt, ptlim);
435 } else pt = _yconv(year, base,
436 true, true,
437 pt, ptlim);
439 continue;
440 case 'v':
442 ** From Arnold Robbins' strftime version 3.0:
443 ** "date as dd-bbb-YYYY"
444 ** (ado, 1993-05-24)
446 pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp);
447 continue;
448 case 'W':
449 pt = _conv((t->tm_yday + DAYSPERWEEK -
450 (t->tm_wday ?
451 (t->tm_wday - 1) :
452 (DAYSPERWEEK - 1))) / DAYSPERWEEK,
453 "%02d", pt, ptlim);
454 continue;
455 case 'w':
456 pt = _conv(t->tm_wday, "%d", pt, ptlim);
457 continue;
458 case 'X':
459 pt = _fmt(Locale->X_fmt, t, pt, ptlim, warnp);
460 continue;
461 case 'x':
463 int warn2 = IN_SOME;
465 pt = _fmt(Locale->x_fmt, t, pt, ptlim, &warn2);
466 if (warn2 == IN_ALL)
467 warn2 = IN_THIS;
468 if (warn2 > *warnp)
469 *warnp = warn2;
471 continue;
472 case 'y':
473 *warnp = IN_ALL;
474 pt = _yconv(t->tm_year, TM_YEAR_BASE,
475 false, true,
476 pt, ptlim);
477 continue;
478 case 'Y':
479 pt = _yconv(t->tm_year, TM_YEAR_BASE,
480 true, true,
481 pt, ptlim);
482 continue;
483 case 'Z':
484 #ifdef TM_ZONE
485 pt = _add(t->TM_ZONE, pt, ptlim);
486 #else
487 if (t->tm_isdst >= 0)
488 pt = _add(tzname[t->tm_isdst != 0],
489 pt, ptlim);
490 #endif
492 ** C99 says that %Z must be replaced by the
493 ** empty string if the time zone is not
494 ** determinable.
496 continue;
497 case 'z':
498 #if defined TM_GMTOFF || defined USG_COMPAT || defined ALTZONE
500 long diff;
501 char const * sign;
502 bool negative;
504 # ifdef TM_GMTOFF
505 diff = t->TM_GMTOFF;
506 # else
508 ** C99 says that the UT offset must
509 ** be computed by looking only at
510 ** tm_isdst. This requirement is
511 ** incorrect, since it means the code
512 ** must rely on magic (in this case
513 ** altzone and timezone), and the
514 ** magic might not have the correct
515 ** offset. Doing things correctly is
516 ** tricky and requires disobeying C99;
517 ** see GNU C strftime for details.
518 ** For now, punt and conform to the
519 ** standard, even though it's incorrect.
521 ** C99 says that %z must be replaced by the
522 ** empty string if the time zone is not
523 ** determinable, so output nothing if the
524 ** appropriate variables are not available.
526 if (t->tm_isdst < 0)
527 continue;
528 if (t->tm_isdst == 0)
529 # ifdef USG_COMPAT
530 diff = -timezone;
531 # else
532 continue;
533 # endif
534 else
535 # ifdef ALTZONE
536 diff = -altzone;
537 # else
538 continue;
539 # endif
540 # endif
541 negative = diff < 0;
542 if (diff == 0) {
543 #ifdef TM_ZONE
544 negative = t->TM_ZONE[0] == '-';
545 #else
546 negative
547 = (t->tm_isdst < 0
548 || tzname[t->tm_isdst != 0][0] == '-');
549 #endif
551 if (negative) {
552 sign = "-";
553 diff = -diff;
554 } else sign = "+";
555 pt = _add(sign, pt, ptlim);
556 diff /= SECSPERMIN;
557 diff = (diff / MINSPERHOUR) * 100 +
558 (diff % MINSPERHOUR);
559 pt = _conv(diff, "%04d", pt, ptlim);
561 #endif
562 continue;
563 case '+':
564 pt = _fmt(Locale->date_fmt, t, pt, ptlim,
565 warnp);
566 continue;
567 case '%':
569 ** X311J/88-090 (4.12.3.5): if conversion char is
570 ** undefined, behavior is undefined. Print out the
571 ** character itself as printf(3) also does.
573 default:
574 break;
577 if (pt == ptlim)
578 break;
579 *pt++ = *format;
581 return pt;
584 static char *
585 _conv(int n, const char *format, char *pt, const char *ptlim)
587 char buf[INT_STRLEN_MAXIMUM(int) + 1];
589 sprintf(buf, format, n);
590 return _add(buf, pt, ptlim);
593 static char *
594 _add(const char *str, char *pt, const char *ptlim)
596 while (pt < ptlim && (*pt = *str++) != '\0')
597 ++pt;
598 return pt;
602 ** POSIX and the C Standard are unclear or inconsistent about
603 ** what %C and %y do if the year is negative or exceeds 9999.
604 ** Use the convention that %C concatenated with %y yields the
605 ** same output as %Y, and that %Y contains at least 4 bytes,
606 ** with more only if necessary.
609 static char *
610 _yconv(int a, int b, bool convert_top, bool convert_yy,
611 char *pt, const char *ptlim)
613 register int lead;
614 register int trail;
616 #define DIVISOR 100
617 trail = a % DIVISOR + b % DIVISOR;
618 lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR;
619 trail %= DIVISOR;
620 if (trail < 0 && lead > 0) {
621 trail += DIVISOR;
622 --lead;
623 } else if (lead < 0 && trail > 0) {
624 trail -= DIVISOR;
625 ++lead;
627 if (convert_top) {
628 if (lead == 0 && trail < 0)
629 pt = _add("-0", pt, ptlim);
630 else pt = _conv(lead, "%02d", pt, ptlim);
632 if (convert_yy)
633 pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim);
634 return pt;