1 /* 2 * Copyright (c) 1989 The Regents of the University of California. 3 * All rights reserved. 4 * 5 * Copyright (c) 2011 The FreeBSD Foundation 6 * 7 * Portions of this software were developed by David Chisnall 8 * under sponsorship from the FreeBSD Foundation. 9 * 10 * Redistribution and use in source and binary forms are permitted 11 * provided that the above copyright notice and this paragraph are 12 * duplicated in all such forms and that any documentation, 13 * advertising materials, and other materials related to such 14 * distribution and use acknowledge that the software was developed 15 * by the University of California, Berkeley. The name of the 16 * University may not be used to endorse or promote products derived 17 * from this software without specific prior written permission. 18 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR 19 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. 21 */ 22 23 #ifndef lint 24 #ifndef NOID 25 static const char elsieid[] = "@(#)strftime.3 8.3"; 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 "namespace.h" 34 #include "private.h" 35 36 #if defined(LIBC_SCCS) && !defined(lint) 37 static const char sccsid[] = "@(#)strftime.c 5.4 (Berkeley) 3/14/89"; 38 #endif /* LIBC_SCCS and not lint */ 39 #include <sys/cdefs.h> 40 #include "tzfile.h" 41 #include <fcntl.h> 42 #include <sys/stat.h> 43 #include <stdio.h> 44 #include "un-namespace.h" 45 #include "timelocal.h" 46 47 static char * _add(const char *, char *, const char *); 48 static char * _conv(int, const char *, char *, const char *, locale_t); 49 static char * _fmt(const char *, const struct tm *, char *, const char *, 50 int *, locale_t); 51 static char * _yconv(int, int, int, int, char *, const char *, locale_t); 52 53 extern char * tzname[]; 54 55 #ifndef YEAR_2000_NAME 56 #define YEAR_2000_NAME "CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS" 57 #endif /* !defined YEAR_2000_NAME */ 58 59 #define IN_NONE 0 60 #define IN_SOME 1 61 #define IN_THIS 2 62 #define IN_ALL 3 63 64 #define PAD_DEFAULT 0 65 #define PAD_LESS 1 66 #define PAD_SPACE 2 67 #define PAD_ZERO 3 68 69 static const char fmt_padding[][4][5] = { 70 /* DEFAULT, LESS, SPACE, ZERO */ 71 #define PAD_FMT_MONTHDAY 0 72 #define PAD_FMT_HMS 0 73 #define PAD_FMT_CENTURY 0 74 #define PAD_FMT_SHORTYEAR 0 75 #define PAD_FMT_MONTH 0 76 #define PAD_FMT_WEEKOFYEAR 0 77 #define PAD_FMT_DAYOFMONTH 0 78 { "%02d", "%d", "%2d", "%02d" }, 79 #define PAD_FMT_SDAYOFMONTH 1 80 #define PAD_FMT_SHMS 1 81 { "%2d", "%d", "%2d", "%02d" }, 82 #define PAD_FMT_DAYOFYEAR 2 83 { "%03d", "%d", "%3d", "%03d" }, 84 #define PAD_FMT_YEAR 3 85 { "%04d", "%d", "%4d", "%04d" } 86 }; 87 88 size_t 89 strftime_l(char * __restrict s, size_t maxsize, const char * __restrict format, 90 const struct tm * __restrict t, locale_t loc) 91 { 92 char * p; 93 int warn; 94 FIX_LOCALE(loc); 95 96 tzset(); 97 warn = IN_NONE; 98 p = _fmt(((format == NULL) ? "%c" : format), t, s, s + maxsize, &warn, loc); 99 #ifndef NO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU 100 if (warn != IN_NONE && getenv(YEAR_2000_NAME) != NULL) { 101 (void) fprintf_l(stderr, loc, "\n"); 102 if (format == NULL) 103 (void) fputs("NULL strftime format ", stderr); 104 else (void) fprintf_l(stderr, loc, "strftime format \"%s\" ", 105 format); 106 (void) fputs("yields only two digits of years in ", stderr); 107 if (warn == IN_SOME) 108 (void) fputs("some locales", stderr); 109 else if (warn == IN_THIS) 110 (void) fputs("the current locale", stderr); 111 else (void) fputs("all locales", stderr); 112 (void) fputs("\n", stderr); 113 } 114 #endif /* !defined NO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU */ 115 if (p == s + maxsize) 116 return (0); 117 *p = '\0'; 118 return p - s; 119 } 120 121 size_t 122 strftime(char * __restrict s, size_t maxsize, const char * __restrict format, 123 const struct tm * __restrict t) 124 { 125 return strftime_l(s, maxsize, format, t, __get_locale()); 126 } 127 128 static char * 129 _fmt(const char *format, const struct tm * const t, char *pt, 130 const char * const ptlim, int *warnp, locale_t loc) 131 { 132 int Ealternative, Oalternative, PadIndex; 133 struct lc_time_T *tptr = __get_current_time_locale(loc); 134 135 for ( ; *format; ++format) { 136 if (*format == '%') { 137 Ealternative = 0; 138 Oalternative = 0; 139 PadIndex = PAD_DEFAULT; 140 label: 141 switch (*++format) { 142 case '\0': 143 --format; 144 break; 145 case 'A': 146 pt = _add((t->tm_wday < 0 || 147 t->tm_wday >= DAYSPERWEEK) ? 148 "?" : tptr->weekday[t->tm_wday], 149 pt, ptlim); 150 continue; 151 case 'a': 152 pt = _add((t->tm_wday < 0 || 153 t->tm_wday >= DAYSPERWEEK) ? 154 "?" : tptr->wday[t->tm_wday], 155 pt, ptlim); 156 continue; 157 case 'B': 158 pt = _add((t->tm_mon < 0 || 159 t->tm_mon >= MONSPERYEAR) ? 160 "?" : (Oalternative ? tptr->alt_month : 161 tptr->month)[t->tm_mon], 162 pt, ptlim); 163 continue; 164 case 'b': 165 case 'h': 166 pt = _add((t->tm_mon < 0 || 167 t->tm_mon >= MONSPERYEAR) ? 168 "?" : tptr->mon[t->tm_mon], 169 pt, ptlim); 170 continue; 171 case 'C': 172 /* 173 * %C used to do a... 174 * _fmt("%a %b %e %X %Y", t); 175 * ...whereas now POSIX 1003.2 calls for 176 * something completely different. 177 * (ado, 1993-05-24) 178 */ 179 pt = _yconv(t->tm_year, TM_YEAR_BASE, 1, 0, 180 pt, ptlim, loc); 181 continue; 182 case 'c': 183 { 184 int warn2 = IN_SOME; 185 186 pt = _fmt(tptr->c_fmt, t, pt, ptlim, &warn2, loc); 187 if (warn2 == IN_ALL) 188 warn2 = IN_THIS; 189 if (warn2 > *warnp) 190 *warnp = warn2; 191 } 192 continue; 193 case 'D': 194 pt = _fmt("%m/%d/%y", t, pt, ptlim, warnp, loc); 195 continue; 196 case 'd': 197 pt = _conv(t->tm_mday, 198 fmt_padding[PAD_FMT_DAYOFMONTH][PadIndex], 199 pt, ptlim, loc); 200 continue; 201 case 'E': 202 if (Ealternative || Oalternative) 203 break; 204 Ealternative++; 205 goto label; 206 case 'O': 207 /* 208 * C99 locale modifiers. 209 * The sequences 210 * %Ec %EC %Ex %EX %Ey %EY 211 * %Od %oe %OH %OI %Om %OM 212 * %OS %Ou %OU %OV %Ow %OW %Oy 213 * are supposed to provide alternate 214 * representations. 215 * 216 * FreeBSD extension 217 * %OB 218 */ 219 if (Ealternative || Oalternative) 220 break; 221 Oalternative++; 222 goto label; 223 case 'e': 224 pt = _conv(t->tm_mday, 225 fmt_padding[PAD_FMT_SDAYOFMONTH][PadIndex], 226 pt, ptlim, loc); 227 continue; 228 case 'F': 229 pt = _fmt("%Y-%m-%d", t, pt, ptlim, warnp, loc); 230 continue; 231 case 'H': 232 pt = _conv(t->tm_hour, fmt_padding[PAD_FMT_HMS][PadIndex], 233 pt, ptlim, loc); 234 continue; 235 case 'I': 236 pt = _conv((t->tm_hour % 12) ? 237 (t->tm_hour % 12) : 12, 238 fmt_padding[PAD_FMT_HMS][PadIndex], 239 pt, ptlim, loc); 240 continue; 241 case 'j': 242 pt = _conv(t->tm_yday + 1, 243 fmt_padding[PAD_FMT_DAYOFYEAR][PadIndex], 244 pt, ptlim, loc); 245 continue; 246 case 'k': 247 /* 248 * This used to be... 249 * _conv(t->tm_hour % 12 ? 250 * t->tm_hour % 12 : 12, 2, ' '); 251 * ...and has been changed to the below to 252 * match SunOS 4.1.1 and Arnold Robbins' 253 * strftime version 3.0. That is, "%k" and 254 * "%l" have been swapped. 255 * (ado, 1993-05-24) 256 */ 257 pt = _conv(t->tm_hour, fmt_padding[PAD_FMT_SHMS][PadIndex], 258 pt, ptlim, loc); 259 continue; 260 #ifdef KITCHEN_SINK 261 case 'K': 262 /* 263 ** After all this time, still unclaimed! 264 */ 265 pt = _add("kitchen sink", pt, ptlim); 266 continue; 267 #endif /* defined KITCHEN_SINK */ 268 case 'l': 269 /* 270 * This used to be... 271 * _conv(t->tm_hour, 2, ' '); 272 * ...and has been changed to the below to 273 * match SunOS 4.1.1 and Arnold Robbin's 274 * strftime version 3.0. That is, "%k" and 275 * "%l" have been swapped. 276 * (ado, 1993-05-24) 277 */ 278 pt = _conv((t->tm_hour % 12) ? 279 (t->tm_hour % 12) : 12, 280 fmt_padding[PAD_FMT_SHMS][PadIndex], 281 pt, ptlim, loc); 282 continue; 283 case 'M': 284 pt = _conv(t->tm_min, fmt_padding[PAD_FMT_HMS][PadIndex], 285 pt, ptlim, loc); 286 continue; 287 case 'm': 288 pt = _conv(t->tm_mon + 1, 289 fmt_padding[PAD_FMT_MONTH][PadIndex], 290 pt, ptlim, loc); 291 continue; 292 case 'n': 293 pt = _add("\n", pt, ptlim); 294 continue; 295 case 'p': 296 pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ? 297 tptr->pm : tptr->am, 298 pt, ptlim); 299 continue; 300 case 'R': 301 pt = _fmt("%H:%M", t, pt, ptlim, warnp, loc); 302 continue; 303 case 'r': 304 pt = _fmt(tptr->ampm_fmt, t, pt, ptlim, 305 warnp, loc); 306 continue; 307 case 'S': 308 pt = _conv(t->tm_sec, fmt_padding[PAD_FMT_HMS][PadIndex], 309 pt, ptlim, loc); 310 continue; 311 case 's': 312 { 313 struct tm tm; 314 char buf[INT_STRLEN_MAXIMUM( 315 time_t) + 1]; 316 time_t mkt; 317 318 tm = *t; 319 mkt = mktime(&tm); 320 if (TYPE_SIGNED(time_t)) 321 (void) sprintf_l(buf, loc, "%ld", 322 (long) mkt); 323 else (void) sprintf_l(buf, loc, "%lu", 324 (unsigned long) mkt); 325 pt = _add(buf, pt, ptlim); 326 } 327 continue; 328 case 'T': 329 pt = _fmt("%H:%M:%S", t, pt, ptlim, warnp, loc); 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 fmt_padding[PAD_FMT_WEEKOFYEAR][PadIndex], 338 pt, ptlim, loc); 339 continue; 340 case 'u': 341 /* 342 * From Arnold Robbins' strftime version 3.0: 343 * "ISO 8601: Weekday as a decimal number 344 * [1 (Monday) - 7]" 345 * (ado, 1993-05-24) 346 */ 347 pt = _conv((t->tm_wday == 0) ? 348 DAYSPERWEEK : t->tm_wday, 349 "%d", pt, ptlim, loc); 350 continue; 351 case 'V': /* ISO 8601 week number */ 352 case 'G': /* ISO 8601 year (four digits) */ 353 case 'g': /* ISO 8601 year (two digits) */ 354 /* 355 * From Arnold Robbins' strftime version 3.0: "the week number of the 356 * year (the first Monday as the first day of week 1) as a decimal number 357 * (01-53)." 358 * (ado, 1993-05-24) 359 * 360 * From "http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html" by Markus Kuhn: 361 * "Week 01 of a year is per definition the first week which has the 362 * Thursday in this year, which is equivalent to the week which contains 363 * the fourth day of January. In other words, the first week of a new year 364 * is the week which has the majority of its days in the new year. Week 01 365 * might also contain days from the previous year and the week before week 366 * 01 of a year is the last week (52 or 53) of the previous year even if 367 * it contains days from the new year. A week starts with Monday (day 1) 368 * and ends with Sunday (day 7). For example, the first week of the year 369 * 1997 lasts from 1996-12-30 to 1997-01-05..." 370 * (ado, 1996-01-02) 371 */ 372 { 373 int year; 374 int base; 375 int yday; 376 int wday; 377 int w; 378 379 year = t->tm_year; 380 base = TM_YEAR_BASE; 381 yday = t->tm_yday; 382 wday = t->tm_wday; 383 for ( ; ; ) { 384 int len; 385 int bot; 386 int top; 387 388 len = isleap_sum(year, base) ? 389 DAYSPERLYEAR : 390 DAYSPERNYEAR; 391 /* 392 * What yday (-3 ... 3) does 393 * the ISO year begin on? 394 */ 395 bot = ((yday + 11 - wday) % 396 DAYSPERWEEK) - 3; 397 /* 398 * What yday does the NEXT 399 * ISO year begin on? 400 */ 401 top = bot - 402 (len % DAYSPERWEEK); 403 if (top < -3) 404 top += DAYSPERWEEK; 405 top += len; 406 if (yday >= top) { 407 ++base; 408 w = 1; 409 break; 410 } 411 if (yday >= bot) { 412 w = 1 + ((yday - bot) / 413 DAYSPERWEEK); 414 break; 415 } 416 --base; 417 yday += isleap_sum(year, base) ? 418 DAYSPERLYEAR : 419 DAYSPERNYEAR; 420 } 421 #ifdef XPG4_1994_04_09 422 if ((w == 52 && 423 t->tm_mon == TM_JANUARY) || 424 (w == 1 && 425 t->tm_mon == TM_DECEMBER)) 426 w = 53; 427 #endif /* defined XPG4_1994_04_09 */ 428 if (*format == 'V') 429 pt = _conv(w, fmt_padding[PAD_FMT_WEEKOFYEAR][PadIndex], 430 pt, ptlim, loc); 431 else if (*format == 'g') { 432 *warnp = IN_ALL; 433 pt = _yconv(year, base, 0, 1, 434 pt, ptlim, loc); 435 } else pt = _yconv(year, base, 1, 1, 436 pt, ptlim, loc); 437 } 438 continue; 439 case 'v': 440 /* 441 * From Arnold Robbins' strftime version 3.0: 442 * "date as dd-bbb-YYYY" 443 * (ado, 1993-05-24) 444 */ 445 pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp, loc); 446 continue; 447 case 'W': 448 pt = _conv((t->tm_yday + DAYSPERWEEK - 449 (t->tm_wday ? 450 (t->tm_wday - 1) : 451 (DAYSPERWEEK - 1))) / DAYSPERWEEK, 452 fmt_padding[PAD_FMT_WEEKOFYEAR][PadIndex], 453 pt, ptlim, loc); 454 continue; 455 case 'w': 456 pt = _conv(t->tm_wday, "%d", pt, ptlim, loc); 457 continue; 458 case 'X': 459 pt = _fmt(tptr->X_fmt, t, pt, ptlim, warnp, loc); 460 continue; 461 case 'x': 462 { 463 int warn2 = IN_SOME; 464 465 pt = _fmt(tptr->x_fmt, t, pt, ptlim, &warn2, loc); 466 if (warn2 == IN_ALL) 467 warn2 = IN_THIS; 468 if (warn2 > *warnp) 469 *warnp = warn2; 470 } 471 continue; 472 case 'y': 473 *warnp = IN_ALL; 474 pt = _yconv(t->tm_year, TM_YEAR_BASE, 0, 1, 475 pt, ptlim, loc); 476 continue; 477 case 'Y': 478 pt = _yconv(t->tm_year, TM_YEAR_BASE, 1, 1, 479 pt, ptlim, loc); 480 continue; 481 case 'Z': 482 #ifdef TM_ZONE 483 if (t->TM_ZONE != NULL) 484 pt = _add(t->TM_ZONE, pt, ptlim); 485 else 486 #endif /* defined TM_ZONE */ 487 if (t->tm_isdst >= 0) 488 pt = _add(tzname[t->tm_isdst != 0], 489 pt, ptlim); 490 /* 491 * C99 says that %Z must be replaced by the 492 * empty string if the time zone is not 493 * determinable. 494 */ 495 continue; 496 case 'z': 497 { 498 int diff; 499 char const * sign; 500 501 if (t->tm_isdst < 0) 502 continue; 503 #ifdef TM_GMTOFF 504 diff = t->TM_GMTOFF; 505 #else /* !defined TM_GMTOFF */ 506 /* 507 * C99 says that the UTC offset must 508 * be computed by looking only at 509 * tm_isdst. This requirement is 510 * incorrect, since it means the code 511 * must rely on magic (in this case 512 * altzone and timezone), and the 513 * magic might not have the correct 514 * offset. Doing things correctly is 515 * tricky and requires disobeying C99; 516 * see GNU C strftime for details. 517 * For now, punt and conform to the 518 * standard, even though it's incorrect. 519 * 520 * C99 says that %z must be replaced by the 521 * empty string if the time zone is not 522 * determinable, so output nothing if the 523 * appropriate variables are not available. 524 */ 525 if (t->tm_isdst == 0) 526 #ifdef USG_COMPAT 527 diff = -timezone; 528 #else /* !defined USG_COMPAT */ 529 continue; 530 #endif /* !defined USG_COMPAT */ 531 else 532 #ifdef ALTZONE 533 diff = -altzone; 534 #else /* !defined ALTZONE */ 535 continue; 536 #endif /* !defined ALTZONE */ 537 #endif /* !defined TM_GMTOFF */ 538 if (diff < 0) { 539 sign = "-"; 540 diff = -diff; 541 } else 542 sign = "+"; 543 pt = _add(sign, pt, ptlim); 544 diff /= SECSPERMIN; 545 diff = (diff / MINSPERHOUR) * 100 + 546 (diff % MINSPERHOUR); 547 pt = _conv(diff, 548 fmt_padding[PAD_FMT_YEAR][PadIndex], 549 pt, ptlim, loc); 550 } 551 continue; 552 case '+': 553 pt = _fmt(tptr->date_fmt, t, pt, ptlim, 554 warnp, loc); 555 continue; 556 case '-': 557 if (PadIndex != PAD_DEFAULT) 558 break; 559 PadIndex = PAD_LESS; 560 goto label; 561 case '_': 562 if (PadIndex != PAD_DEFAULT) 563 break; 564 PadIndex = PAD_SPACE; 565 goto label; 566 case '0': 567 if (PadIndex != PAD_DEFAULT) 568 break; 569 PadIndex = PAD_ZERO; 570 goto label; 571 case '%': 572 /* 573 * X311J/88-090 (4.12.3.5): if conversion char is 574 * undefined, behavior is undefined. Print out the 575 * character itself as printf(3) also does. 576 */ 577 default: 578 break; 579 } 580 } 581 if (pt == ptlim) 582 break; 583 *pt++ = *format; 584 } 585 return (pt); 586 } 587 588 static char * 589 _conv(const int n, const char * const format, char * const pt, 590 const char * const ptlim, locale_t loc) 591 { 592 char buf[INT_STRLEN_MAXIMUM(int) + 1]; 593 594 (void) sprintf_l(buf, loc, format, n); 595 return _add(buf, pt, ptlim); 596 } 597 598 static char * 599 _add(const char *str, char *pt, const char * const ptlim) 600 { 601 while (pt < ptlim && (*pt = *str++) != '\0') 602 ++pt; 603 return (pt); 604 } 605 606 /* 607 * POSIX and the C Standard are unclear or inconsistent about 608 * what %C and %y do if the year is negative or exceeds 9999. 609 * Use the convention that %C concatenated with %y yields the 610 * same output as %Y, and that %Y contains at least 4 bytes, 611 * with more only if necessary. 612 */ 613 614 static char * 615 _yconv(const int a, const int b, const int convert_top, const int convert_yy, 616 char *pt, const char * const ptlim, locale_t loc) 617 { 618 register int lead; 619 register int trail; 620 621 #define DIVISOR 100 622 trail = a % DIVISOR + b % DIVISOR; 623 lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR; 624 trail %= DIVISOR; 625 if (trail < 0 && lead > 0) { 626 trail += DIVISOR; 627 --lead; 628 } else if (lead < 0 && trail > 0) { 629 trail -= DIVISOR; 630 ++lead; 631 } 632 if (convert_top) { 633 if (lead == 0 && trail < 0) 634 pt = _add("-0", pt, ptlim); 635 else pt = _conv(lead, "%02d", pt, ptlim, loc); 636 } 637 if (convert_yy) 638 pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, 639 ptlim, loc); 640 return (pt); 641 } 642