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 #ifndef lint 19 #ifndef NOID 20 static const char elsieid[] = "@(#)strftime.c 7.64"; 21 /* 22 ** Based on the UCB version with the ID appearing below. 23 ** This is ANSIish only when "multibyte character == plain character". 24 */ 25 #endif /* !defined NOID */ 26 #endif /* !defined lint */ 27 28 #include "namespace.h" 29 #include "private.h" 30 31 #if defined(LIBC_SCCS) && !defined(lint) 32 static const char sccsid[] = "@(#)strftime.c 5.4 (Berkeley) 3/14/89"; 33 #endif /* LIBC_SCCS and not lint */ 34 #include <sys/cdefs.h> 35 __FBSDID("$FreeBSD$"); 36 37 #include "tzfile.h" 38 #include <fcntl.h> 39 #include <sys/stat.h> 40 #include "un-namespace.h" 41 #include "timelocal.h" 42 43 static char * _add(const char *, char *, const char *); 44 static char * _conv(int, const char *, char *, const char *); 45 static char * _fmt(const char *, const struct tm *, char *, const char *, int *); 46 47 size_t strftime(char * __restrict, size_t, const char * __restrict, 48 const struct tm * __restrict); 49 50 extern char * tzname[]; 51 52 #ifndef YEAR_2000_NAME 53 #define YEAR_2000_NAME "CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS" 54 #endif /* !defined YEAR_2000_NAME */ 55 56 57 #define IN_NONE 0 58 #define IN_SOME 1 59 #define IN_THIS 2 60 #define IN_ALL 3 61 62 size_t 63 strftime(char * __restrict s, size_t maxsize, const char * __restrict format, 64 const struct tm * __restrict t) 65 { 66 char * p; 67 int warn; 68 69 tzset(); 70 warn = IN_NONE; 71 p = _fmt(((format == NULL) ? "%c" : format), t, s, s + maxsize, &warn); 72 #ifndef NO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU 73 if (warn != IN_NONE && getenv(YEAR_2000_NAME) != NULL) { 74 (void) fprintf(stderr, "\n"); 75 if (format == NULL) 76 (void) fprintf(stderr, "NULL strftime format "); 77 else (void) fprintf(stderr, "strftime format \"%s\" ", 78 format); 79 (void) fprintf(stderr, "yields only two digits of years in "); 80 if (warn == IN_SOME) 81 (void) fprintf(stderr, "some locales"); 82 else if (warn == IN_THIS) 83 (void) fprintf(stderr, "the current locale"); 84 else (void) fprintf(stderr, "all locales"); 85 (void) fprintf(stderr, "\n"); 86 } 87 #endif /* !defined NO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU */ 88 if (p == s + maxsize) 89 return 0; 90 *p = '\0'; 91 return p - s; 92 } 93 94 static char * 95 _fmt(format, t, pt, ptlim, warnp) 96 const char * format; 97 const struct tm * const t; 98 char * pt; 99 const char * const ptlim; 100 int * warnp; 101 { 102 int Ealternative, Oalternative; 103 struct lc_time_T *tptr = __get_current_time_locale(); 104 105 for ( ; *format; ++format) { 106 if (*format == '%') { 107 Ealternative = 0; 108 Oalternative = 0; 109 label: 110 switch (*++format) { 111 case '\0': 112 --format; 113 break; 114 case 'A': 115 pt = _add((t->tm_wday < 0 || 116 t->tm_wday >= DAYSPERWEEK) ? 117 "?" : tptr->weekday[t->tm_wday], 118 pt, ptlim); 119 continue; 120 case 'a': 121 pt = _add((t->tm_wday < 0 || 122 t->tm_wday >= DAYSPERWEEK) ? 123 "?" : tptr->wday[t->tm_wday], 124 pt, ptlim); 125 continue; 126 case 'B': 127 pt = _add((t->tm_mon < 0 || 128 t->tm_mon >= MONSPERYEAR) ? 129 "?" : (Oalternative ? tptr->alt_month : 130 tptr->month)[t->tm_mon], 131 pt, ptlim); 132 continue; 133 case 'b': 134 case 'h': 135 pt = _add((t->tm_mon < 0 || 136 t->tm_mon >= MONSPERYEAR) ? 137 "?" : tptr->mon[t->tm_mon], 138 pt, ptlim); 139 continue; 140 case 'C': 141 /* 142 ** %C used to do a... 143 ** _fmt("%a %b %e %X %Y", t); 144 ** ...whereas now POSIX 1003.2 calls for 145 ** something completely different. 146 ** (ado, 1993-05-24) 147 */ 148 pt = _conv((t->tm_year + TM_YEAR_BASE) / 100, 149 "%02d", pt, ptlim); 150 continue; 151 case 'c': 152 { 153 int warn2 = IN_SOME; 154 155 pt = _fmt(tptr->c_fmt, t, pt, ptlim, warnp); 156 if (warn2 == IN_ALL) 157 warn2 = IN_THIS; 158 if (warn2 > *warnp) 159 *warnp = warn2; 160 } 161 continue; 162 case 'D': 163 pt = _fmt("%m/%d/%y", t, pt, ptlim, warnp); 164 continue; 165 case 'd': 166 pt = _conv(t->tm_mday, "%02d", pt, ptlim); 167 continue; 168 case 'E': 169 if (Ealternative || Oalternative) 170 break; 171 Ealternative++; 172 goto label; 173 case 'O': 174 /* 175 ** C99 locale modifiers. 176 ** The sequences 177 ** %Ec %EC %Ex %EX %Ey %EY 178 ** %Od %oe %OH %OI %Om %OM 179 ** %OS %Ou %OU %OV %Ow %OW %Oy 180 ** are supposed to provide alternate 181 ** representations. 182 ** 183 ** FreeBSD extension 184 ** %OB 185 */ 186 if (Ealternative || Oalternative) 187 break; 188 Oalternative++; 189 goto label; 190 case 'e': 191 pt = _conv(t->tm_mday, "%2d", pt, ptlim); 192 continue; 193 case 'F': 194 pt = _fmt("%Y-%m-%d", t, pt, ptlim, warnp); 195 continue; 196 case 'H': 197 pt = _conv(t->tm_hour, "%02d", pt, ptlim); 198 continue; 199 case 'I': 200 pt = _conv((t->tm_hour % 12) ? 201 (t->tm_hour % 12) : 12, 202 "%02d", pt, ptlim); 203 continue; 204 case 'j': 205 pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim); 206 continue; 207 case 'k': 208 /* 209 ** This used to be... 210 ** _conv(t->tm_hour % 12 ? 211 ** t->tm_hour % 12 : 12, 2, ' '); 212 ** ...and has been changed to the below to 213 ** match SunOS 4.1.1 and Arnold Robbins' 214 ** strftime version 3.0. That is, "%k" and 215 ** "%l" have been swapped. 216 ** (ado, 1993-05-24) 217 */ 218 pt = _conv(t->tm_hour, "%2d", pt, ptlim); 219 continue; 220 #ifdef KITCHEN_SINK 221 case 'K': 222 /* 223 ** After all this time, still unclaimed! 224 */ 225 pt = _add("kitchen sink", pt, ptlim); 226 continue; 227 #endif /* defined KITCHEN_SINK */ 228 case 'l': 229 /* 230 ** This used to be... 231 ** _conv(t->tm_hour, 2, ' '); 232 ** ...and has been changed to the below to 233 ** match SunOS 4.1.1 and Arnold Robbin's 234 ** strftime version 3.0. That is, "%k" and 235 ** "%l" have been swapped. 236 ** (ado, 1993-05-24) 237 */ 238 pt = _conv((t->tm_hour % 12) ? 239 (t->tm_hour % 12) : 12, 240 "%2d", pt, ptlim); 241 continue; 242 case 'M': 243 pt = _conv(t->tm_min, "%02d", pt, ptlim); 244 continue; 245 case 'm': 246 pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim); 247 continue; 248 case 'n': 249 pt = _add("\n", pt, ptlim); 250 continue; 251 case 'p': 252 pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ? 253 tptr->pm : 254 tptr->am, 255 pt, ptlim); 256 continue; 257 case 'R': 258 pt = _fmt("%H:%M", t, pt, ptlim, warnp); 259 continue; 260 case 'r': 261 pt = _fmt(tptr->ampm_fmt, t, pt, ptlim, 262 warnp); 263 continue; 264 case 'S': 265 pt = _conv(t->tm_sec, "%02d", pt, ptlim); 266 continue; 267 case 's': 268 { 269 struct tm tm; 270 char buf[INT_STRLEN_MAXIMUM( 271 time_t) + 1]; 272 time_t mkt; 273 274 tm = *t; 275 mkt = mktime(&tm); 276 if (TYPE_SIGNED(time_t)) 277 (void) sprintf(buf, "%ld", 278 (long) mkt); 279 else (void) sprintf(buf, "%lu", 280 (unsigned long) mkt); 281 pt = _add(buf, pt, ptlim); 282 } 283 continue; 284 case 'T': 285 pt = _fmt("%H:%M:%S", t, pt, ptlim, warnp); 286 continue; 287 case 't': 288 pt = _add("\t", pt, ptlim); 289 continue; 290 case 'U': 291 pt = _conv((t->tm_yday + DAYSPERWEEK - 292 t->tm_wday) / DAYSPERWEEK, 293 "%02d", pt, ptlim); 294 continue; 295 case 'u': 296 /* 297 ** From Arnold Robbins' strftime version 3.0: 298 ** "ISO 8601: Weekday as a decimal number 299 ** [1 (Monday) - 7]" 300 ** (ado, 1993-05-24) 301 */ 302 pt = _conv((t->tm_wday == 0) ? 303 DAYSPERWEEK : t->tm_wday, 304 "%d", pt, ptlim); 305 continue; 306 case 'V': /* ISO 8601 week number */ 307 case 'G': /* ISO 8601 year (four digits) */ 308 case 'g': /* ISO 8601 year (two digits) */ 309 /* 310 ** From Arnold Robbins' strftime version 3.0: "the week number of the 311 ** year (the first Monday as the first day of week 1) as a decimal number 312 ** (01-53)." 313 ** (ado, 1993-05-24) 314 ** 315 ** From "http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html" by Markus Kuhn: 316 ** "Week 01 of a year is per definition the first week which has the 317 ** Thursday in this year, which is equivalent to the week which contains 318 ** the fourth day of January. In other words, the first week of a new year 319 ** is the week which has the majority of its days in the new year. Week 01 320 ** might also contain days from the previous year and the week before week 321 ** 01 of a year is the last week (52 or 53) of the previous year even if 322 ** it contains days from the new year. A week starts with Monday (day 1) 323 ** and ends with Sunday (day 7). For example, the first week of the year 324 ** 1997 lasts from 1996-12-30 to 1997-01-05..." 325 ** (ado, 1996-01-02) 326 */ 327 { 328 int year; 329 int yday; 330 int wday; 331 int w; 332 333 year = t->tm_year + TM_YEAR_BASE; 334 yday = t->tm_yday; 335 wday = t->tm_wday; 336 for ( ; ; ) { 337 int len; 338 int bot; 339 int top; 340 341 len = isleap(year) ? 342 DAYSPERLYEAR : 343 DAYSPERNYEAR; 344 /* 345 ** What yday (-3 ... 3) does 346 ** the ISO year begin on? 347 */ 348 bot = ((yday + 11 - wday) % 349 DAYSPERWEEK) - 3; 350 /* 351 ** What yday does the NEXT 352 ** ISO year begin on? 353 */ 354 top = bot - 355 (len % DAYSPERWEEK); 356 if (top < -3) 357 top += DAYSPERWEEK; 358 top += len; 359 if (yday >= top) { 360 ++year; 361 w = 1; 362 break; 363 } 364 if (yday >= bot) { 365 w = 1 + ((yday - bot) / 366 DAYSPERWEEK); 367 break; 368 } 369 --year; 370 yday += isleap(year) ? 371 DAYSPERLYEAR : 372 DAYSPERNYEAR; 373 } 374 #ifdef XPG4_1994_04_09 375 if ((w == 52 376 && t->tm_mon == TM_JANUARY) 377 || (w == 1 378 && t->tm_mon == TM_DECEMBER)) 379 w = 53; 380 #endif /* defined XPG4_1994_04_09 */ 381 if (*format == 'V') 382 pt = _conv(w, "%02d", 383 pt, ptlim); 384 else if (*format == 'g') { 385 *warnp = IN_ALL; 386 pt = _conv(year % 100, "%02d", 387 pt, ptlim); 388 } else pt = _conv(year, "%04d", 389 pt, ptlim); 390 } 391 continue; 392 case 'v': 393 /* 394 ** From Arnold Robbins' strftime version 3.0: 395 ** "date as dd-bbb-YYYY" 396 ** (ado, 1993-05-24) 397 */ 398 pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp); 399 continue; 400 case 'W': 401 pt = _conv((t->tm_yday + DAYSPERWEEK - 402 (t->tm_wday ? 403 (t->tm_wday - 1) : 404 (DAYSPERWEEK - 1))) / DAYSPERWEEK, 405 "%02d", pt, ptlim); 406 continue; 407 case 'w': 408 pt = _conv(t->tm_wday, "%d", pt, ptlim); 409 continue; 410 case 'X': 411 pt = _fmt(tptr->X_fmt, t, pt, ptlim, warnp); 412 continue; 413 case 'x': 414 { 415 int warn2 = IN_SOME; 416 417 pt = _fmt(tptr->x_fmt, t, pt, ptlim, &warn2); 418 if (warn2 == IN_ALL) 419 warn2 = IN_THIS; 420 if (warn2 > *warnp) 421 *warnp = warn2; 422 } 423 continue; 424 case 'y': 425 *warnp = IN_ALL; 426 pt = _conv((t->tm_year + TM_YEAR_BASE) % 100, 427 "%02d", pt, ptlim); 428 continue; 429 case 'Y': 430 pt = _conv(t->tm_year + TM_YEAR_BASE, "%04d", 431 pt, ptlim); 432 continue; 433 case 'Z': 434 #ifdef TM_ZONE 435 if (t->TM_ZONE != NULL) 436 pt = _add(t->TM_ZONE, pt, ptlim); 437 else 438 #endif /* defined TM_ZONE */ 439 if (t->tm_isdst >= 0) 440 pt = _add(tzname[t->tm_isdst != 0], 441 pt, ptlim); 442 /* 443 ** C99 says that %Z must be replaced by the 444 ** empty string if the time zone is not 445 ** determinable. 446 */ 447 continue; 448 case 'z': 449 { 450 int diff; 451 char const * sign; 452 453 if (t->tm_isdst < 0) 454 continue; 455 #ifdef TM_GMTOFF 456 diff = t->TM_GMTOFF; 457 #else /* !defined TM_GMTOFF */ 458 /* 459 ** C99 says that the UTC offset must 460 ** be computed by looking only at 461 ** tm_isdst. This requirement is 462 ** incorrect, since it means the code 463 ** must rely on magic (in this case 464 ** altzone and timezone), and the 465 ** magic might not have the correct 466 ** offset. Doing things correctly is 467 ** tricky and requires disobeying C99; 468 ** see GNU C strftime for details. 469 ** For now, punt and conform to the 470 ** standard, even though it's incorrect. 471 ** 472 ** C99 says that %z must be replaced by the 473 ** empty string if the time zone is not 474 ** determinable, so output nothing if the 475 ** appropriate variables are not available. 476 */ 477 if (t->tm_isdst == 0) 478 #ifdef USG_COMPAT 479 diff = -timezone; 480 #else /* !defined USG_COMPAT */ 481 continue; 482 #endif /* !defined USG_COMPAT */ 483 else 484 #ifdef ALTZONE 485 diff = -altzone; 486 #else /* !defined ALTZONE */ 487 continue; 488 #endif /* !defined ALTZONE */ 489 #endif /* !defined TM_GMTOFF */ 490 if (diff < 0) { 491 sign = "-"; 492 diff = -diff; 493 } else sign = "+"; 494 pt = _add(sign, pt, ptlim); 495 diff /= 60; 496 pt = _conv((diff/60)*100 + diff%60, 497 "%04d", pt, ptlim); 498 } 499 continue; 500 case '+': 501 pt = _fmt(tptr->date_fmt, t, pt, ptlim, 502 warnp); 503 continue; 504 case '%': 505 /* 506 ** X311J/88-090 (4.12.3.5): if conversion char is 507 ** undefined, behavior is undefined. Print out the 508 ** character itself as printf(3) also does. 509 */ 510 default: 511 break; 512 } 513 } 514 if (pt == ptlim) 515 break; 516 *pt++ = *format; 517 } 518 return pt; 519 } 520 521 static char * 522 _conv(n, format, pt, ptlim) 523 const int n; 524 const char * const format; 525 char * const pt; 526 const char * const ptlim; 527 { 528 char buf[INT_STRLEN_MAXIMUM(int) + 1]; 529 530 (void) sprintf(buf, format, n); 531 return _add(buf, pt, ptlim); 532 } 533 534 static char * 535 _add(str, pt, ptlim) 536 const char * str; 537 char * pt; 538 const char * const ptlim; 539 { 540 while (pt < ptlim && (*pt = *str++) != '\0') 541 ++pt; 542 return pt; 543 } 544