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