1/* 2 date_strftime.c: based on a public-domain implementation of ANSI C 3 library routine strftime, which is originally written by Arnold 4 Robbins. 5 */ 6 7#include "ruby/ruby.h" 8#include "date_tmx.h" 9 10#include <stdlib.h> 11#include <string.h> 12#include <ctype.h> 13#include <errno.h> 14 15#if defined(HAVE_SYS_TIME_H) 16#include <sys/time.h> 17#endif 18 19#undef strchr /* avoid AIX weirdness */ 20 21#define range(low, item, hi) (item) 22 23#define add(x,y) (rb_funcall((x), '+', 1, (y))) 24#define sub(x,y) (rb_funcall((x), '-', 1, (y))) 25#define mul(x,y) (rb_funcall((x), '*', 1, (y))) 26#define quo(x,y) (rb_funcall((x), rb_intern("quo"), 1, (y))) 27#define div(x,y) (rb_funcall((x), rb_intern("div"), 1, (y))) 28#define mod(x,y) (rb_funcall((x), '%', 1, (y))) 29 30static void 31upcase(char *s, size_t i) 32{ 33 do { 34 if (ISLOWER(*s)) 35 *s = TOUPPER(*s); 36 } while (s++, --i); 37} 38 39static void 40downcase(char *s, size_t i) 41{ 42 do { 43 if (ISUPPER(*s)) 44 *s = TOLOWER(*s); 45 } while (s++, --i); 46} 47 48/* strftime --- produce formatted time */ 49 50static size_t 51date_strftime_with_tmx(char *s, size_t maxsize, const char *format, 52 const struct tmx *tmx) 53{ 54 char *endp = s + maxsize; 55 char *start = s; 56 const char *sp, *tp; 57 auto char tbuf[100]; 58 ptrdiff_t i; 59 int v, w; 60 size_t colons; 61 int precision, flags; 62 char padding; 63 /* LOCALE_[OE] and COLONS are actually modifiers, not flags */ 64 enum {LEFT, CHCASE, LOWER, UPPER, LOCALE_O, LOCALE_E, COLONS}; 65#define BIT_OF(n) (1U<<(n)) 66 67 /* various tables for locale C */ 68 static const char days_l[][10] = { 69 "Sunday", "Monday", "Tuesday", "Wednesday", 70 "Thursday", "Friday", "Saturday", 71 }; 72 static const char months_l[][10] = { 73 "January", "February", "March", "April", 74 "May", "June", "July", "August", "September", 75 "October", "November", "December", 76 }; 77 static const char ampm[][3] = { "AM", "PM", }; 78 79 if (s == NULL || format == NULL || tmx == NULL || maxsize == 0) 80 return 0; 81 82 /* quick check if we even need to bother */ 83 if (strchr(format, '%') == NULL && strlen(format) + 1 >= maxsize) { 84 err: 85 errno = ERANGE; 86 return 0; 87 } 88 89 for (; *format && s < endp - 1; format++) { 90#define FLAG_FOUND() do { \ 91 if (precision > 0 || flags & (BIT_OF(LOCALE_E) | BIT_OF(LOCALE_O) | BIT_OF(COLONS))) \ 92 goto unknown; \ 93 } while (0) 94#define NEEDS(n) do if (s >= endp || (n) >= endp - s - 1) goto err; while (0) 95#define FILL_PADDING(i) do { \ 96 if (!(flags & BIT_OF(LEFT)) && precision > (i)) { \ 97 NEEDS(precision); \ 98 memset(s, padding ? padding : ' ', precision - (i)); \ 99 s += precision - (i); \ 100 } \ 101 else { \ 102 NEEDS(i); \ 103 } \ 104 } while (0); 105#define FMT(def_pad, def_prec, fmt, val) \ 106 do { \ 107 int l; \ 108 if (precision <= 0) precision = (def_prec); \ 109 if (flags & BIT_OF(LEFT)) precision = 1; \ 110 l = snprintf(s, endp - s, \ 111 ((padding == '0' || (!padding && (def_pad) == '0')) ? \ 112 "%0*"fmt : "%*"fmt), \ 113 precision, (val)); \ 114 if (l < 0) goto err; \ 115 s += l; \ 116 } while (0) 117#define STRFTIME(fmt) \ 118 do { \ 119 i = date_strftime_with_tmx(s, endp - s, (fmt), tmx); \ 120 if (!i) return 0; \ 121 if (flags & BIT_OF(UPPER)) \ 122 upcase(s, i); \ 123 if (!(flags & BIT_OF(LEFT)) && precision > i) { \ 124 if (start + maxsize < s + precision) { \ 125 errno = ERANGE; \ 126 return 0; \ 127 } \ 128 memmove(s + precision - i, s, i); \ 129 memset(s, padding ? padding : ' ', precision - i); \ 130 s += precision; \ 131 } \ 132 else s += i; \ 133 } while (0) 134#define FMTV(def_pad, def_prec, fmt, val) \ 135 do { \ 136 VALUE tmp = (val); \ 137 if (FIXNUM_P(tmp)) { \ 138 FMT((def_pad), (def_prec), "l"fmt, FIX2LONG(tmp)); \ 139 } \ 140 else { \ 141 VALUE args[2], result; \ 142 size_t l; \ 143 if (precision <= 0) precision = (def_prec); \ 144 if (flags & BIT_OF(LEFT)) precision = 1; \ 145 args[0] = INT2FIX(precision); \ 146 args[1] = (val); \ 147 if (padding == '0' || (!padding && (def_pad) == '0')) \ 148 result = rb_str_format(2, args, rb_str_new2("%0*"fmt)); \ 149 else \ 150 result = rb_str_format(2, args, rb_str_new2("%*"fmt)); \ 151 l = strlcpy(s, StringValueCStr(result), endp - s); \ 152 if ((size_t)(endp - s) <= l) \ 153 goto err; \ 154 s += l; \ 155 } \ 156 } while (0) 157 158 if (*format != '%') { 159 *s++ = *format; 160 continue; 161 } 162 tp = tbuf; 163 sp = format; 164 precision = -1; 165 flags = 0; 166 padding = 0; 167 colons = 0; 168 again: 169 switch (*++format) { 170 case '\0': 171 format--; 172 goto unknown; 173 174 case 'A': /* full weekday name */ 175 case 'a': /* abbreviated weekday name */ 176 if (flags & BIT_OF(CHCASE)) { 177 flags &= ~(BIT_OF(LOWER) | BIT_OF(CHCASE)); 178 flags |= BIT_OF(UPPER); 179 } 180 { 181 int wday = tmx_wday; 182 if (wday < 0 || wday > 6) 183 i = 1, tp = "?"; 184 else { 185 if (*format == 'A') 186 i = strlen(tp = days_l[wday]); 187 else 188 i = 3, tp = days_l[wday]; 189 } 190 } 191 break; 192 193 case 'B': /* full month name */ 194 case 'b': /* abbreviated month name */ 195 case 'h': /* same as %b */ 196 if (flags & BIT_OF(CHCASE)) { 197 flags &= ~(BIT_OF(LOWER) | BIT_OF(CHCASE)); 198 flags |= BIT_OF(UPPER); 199 } 200 { 201 int mon = tmx_mon; 202 if (mon < 1 || mon > 12) 203 i = 1, tp = "?"; 204 else { 205 if (*format == 'B') 206 i = strlen(tp = months_l[mon - 1]); 207 else 208 i = 3, tp = months_l[mon - 1]; 209 } 210 } 211 break; 212 213 case 'C': /* century (year/100) */ 214 FMTV('0', 2, "d", div(tmx_year, INT2FIX(100))); 215 continue; 216 217 case 'c': /* appropriate date and time representation */ 218 STRFTIME("%a %b %e %H:%M:%S %Y"); 219 continue; 220 221 case 'D': 222 STRFTIME("%m/%d/%y"); 223 continue; 224 225 case 'd': /* day of the month, 01 - 31 */ 226 case 'e': /* day of month, blank padded */ 227 v = range(1, tmx_mday, 31); 228 FMT((*format == 'd') ? '0' : ' ', 2, "d", v); 229 continue; 230 231 case 'F': 232 STRFTIME("%Y-%m-%d"); 233 continue; 234 235 case 'G': /* year of ISO week with century */ 236 case 'Y': /* year with century */ 237 { 238 VALUE year = (*format == 'G') ? tmx_cwyear : tmx_year; 239 if (FIXNUM_P(year)) { 240 long y = FIX2LONG(year); 241 FMT('0', 0 <= y ? 4 : 5, "ld", y); 242 } 243 else { 244 FMTV('0', 4, "d", year); 245 } 246 } 247 continue; 248 249 case 'g': /* year of ISO week without a century */ 250 case 'y': /* year without a century */ 251 v = NUM2INT(mod((*format == 'g') ? tmx_cwyear : tmx_year, INT2FIX(100))); 252 FMT('0', 2, "d", v); 253 continue; 254 255 case 'H': /* hour, 24-hour clock, 00 - 23 */ 256 case 'k': /* hour, 24-hour clock, blank pad */ 257 v = range(0, tmx_hour, 23); 258 FMT((*format == 'H') ? '0' : ' ', 2, "d", v); 259 continue; 260 261 case 'I': /* hour, 12-hour clock, 01 - 12 */ 262 case 'l': /* hour, 12-hour clock, 1 - 12, blank pad */ 263 v = range(0, tmx_hour, 23); 264 if (v == 0) 265 v = 12; 266 else if (v > 12) 267 v -= 12; 268 FMT((*format == 'I') ? '0' : ' ', 2, "d", v); 269 continue; 270 271 case 'j': /* day of the year, 001 - 366 */ 272 v = range(1, tmx_yday, 366); 273 FMT('0', 3, "d", v); 274 continue; 275 276 case 'L': /* millisecond */ 277 case 'N': /* nanosecond */ 278 if (*format == 'L') 279 w = 3; 280 else 281 w = 9; 282 if (precision <= 0) 283 precision = w; 284 NEEDS(precision); 285 286 { 287 VALUE subsec = tmx_sec_fraction; 288 int ww; 289 long n; 290 291 ww = precision; 292 while (9 <= ww) { 293 subsec = mul(subsec, INT2FIX(1000000000)); 294 ww -= 9; 295 } 296 n = 1; 297 for (; 0 < ww; ww--) 298 n *= 10; 299 if (n != 1) 300 subsec = mul(subsec, INT2FIX(n)); 301 subsec = div(subsec, INT2FIX(1)); 302 303 if (FIXNUM_P(subsec)) { 304 (void)snprintf(s, endp - s, "%0*ld", 305 precision, FIX2LONG(subsec)); 306 s += precision; 307 } 308 else { 309 VALUE args[2], result; 310 args[0] = INT2FIX(precision); 311 args[1] = subsec; 312 result = rb_str_format(2, args, rb_str_new2("%0*d")); 313 (void)strlcpy(s, StringValueCStr(result), endp - s); 314 s += precision; 315 } 316 } 317 continue; 318 319 case 'M': /* minute, 00 - 59 */ 320 v = range(0, tmx_min, 59); 321 FMT('0', 2, "d", v); 322 continue; 323 324 case 'm': /* month, 01 - 12 */ 325 v = range(1, tmx_mon, 12); 326 FMT('0', 2, "d", v); 327 continue; 328 329 case 'n': /* same as \n */ 330 FILL_PADDING(1); 331 *s++ = '\n'; 332 continue; 333 334 case 't': /* same as \t */ 335 FILL_PADDING(1); 336 *s++ = '\t'; 337 continue; 338 339 case 'P': /* am or pm based on 12-hour clock */ 340 case 'p': /* AM or PM based on 12-hour clock */ 341 if ((*format == 'p' && (flags & BIT_OF(CHCASE))) || 342 (*format == 'P' && !(flags & (BIT_OF(CHCASE) | BIT_OF(UPPER))))) { 343 flags &= ~(BIT_OF(UPPER) | BIT_OF(CHCASE)); 344 flags |= BIT_OF(LOWER); 345 } 346 v = range(0, tmx_hour, 23); 347 if (v < 12) 348 tp = ampm[0]; 349 else 350 tp = ampm[1]; 351 i = 2; 352 break; 353 354 case 'Q': /* milliseconds since Unix epoch */ 355 FMTV('0', 1, "d", tmx_msecs); 356 continue; 357 358 case 'R': 359 STRFTIME("%H:%M"); 360 continue; 361 362 case 'r': 363 STRFTIME("%I:%M:%S %p"); 364 continue; 365 366 case 'S': /* second, 00 - 59 */ 367 v = range(0, tmx_sec, 59); 368 FMT('0', 2, "d", v); 369 continue; 370 371 case 's': /* seconds since Unix epoch */ 372 FMTV('0', 1, "d", tmx_secs); 373 continue; 374 375 case 'T': 376 STRFTIME("%H:%M:%S"); 377 continue; 378 379 case 'U': /* week of year, Sunday is first day of week */ 380 case 'W': /* week of year, Monday is first day of week */ 381 v = range(0, (*format == 'U') ? tmx_wnum0 : tmx_wnum1, 53); 382 FMT('0', 2, "d", v); 383 continue; 384 385 case 'u': /* weekday, Monday == 1, 1 - 7 */ 386 v = range(1, tmx_cwday, 7); 387 FMT('0', 1, "d", v); 388 continue; 389 390 case 'V': /* week of year according ISO 8601 */ 391 v = range(1, tmx_cweek, 53); 392 FMT('0', 2, "d", v); 393 continue; 394 395 case 'v': 396 STRFTIME("%e-%b-%Y"); 397 continue; 398 399 case 'w': /* weekday, Sunday == 0, 0 - 6 */ 400 v = range(0, tmx_wday, 6); 401 FMT('0', 1, "d", v); 402 continue; 403 404 case 'X': /* appropriate time representation */ 405 STRFTIME("%H:%M:%S"); 406 continue; 407 408 case 'x': /* appropriate date representation */ 409 STRFTIME("%m/%d/%y"); 410 continue; 411 412 case 'Z': /* time zone name or abbreviation */ 413 if (flags & BIT_OF(CHCASE)) { 414 flags &= ~(BIT_OF(UPPER) | BIT_OF(CHCASE)); 415 flags |= BIT_OF(LOWER); 416 } 417 { 418 char *zone = tmx_zone; 419 if (zone == NULL) 420 tp = ""; 421 else 422 tp = zone; 423 i = strlen(tp); 424 } 425 break; 426 427 case 'z': /* offset from UTC */ 428 { 429 long off, aoff; 430 int hl, hw; 431 432 off = tmx_offset; 433 aoff = off; 434 if (aoff < 0) 435 aoff = -off; 436 437 if ((aoff / 3600) < 10) 438 hl = 1; 439 else 440 hl = 2; 441 hw = 2; 442 if (flags & BIT_OF(LEFT) && hl == 1) 443 hw = 1; 444 445 switch (colons) { 446 case 0: /* %z -> +hhmm */ 447 precision = precision <= (3 + hw) ? hw : precision - 3; 448 NEEDS(precision + 3); 449 break; 450 451 case 1: /* %:z -> +hh:mm */ 452 precision = precision <= (4 + hw) ? hw : precision - 4; 453 NEEDS(precision + 4); 454 break; 455 456 case 2: /* %::z -> +hh:mm:ss */ 457 precision = precision <= (7 + hw) ? hw : precision - 7; 458 NEEDS(precision + 7); 459 break; 460 461 case 3: /* %:::z -> +hh[:mm[:ss]] */ 462 { 463 if (aoff % 3600 == 0) { 464 precision = precision <= (1 + hw) ? 465 hw : precision - 1; 466 NEEDS(precision + 3); 467 } 468 else if (aoff % 60 == 0) { 469 precision = precision <= (4 + hw) ? 470 hw : precision - 4; 471 NEEDS(precision + 4); 472 } 473 else { 474 precision = precision <= (7 + hw) ? 475 hw : precision - 7; 476 NEEDS(precision + 7); 477 } 478 } 479 break; 480 481 default: 482 format--; 483 goto unknown; 484 } 485 if (padding == ' ' && precision > hl) { 486 i = snprintf(s, endp - s, "%*s", precision - hl, ""); 487 precision = hl; 488 if (i < 0) goto err; 489 s += i; 490 } 491 if (off < 0) { 492 off = -off; 493 *s++ = '-'; 494 } else { 495 *s++ = '+'; 496 } 497 i = snprintf(s, endp - s, "%.*ld", precision, off / 3600); 498 if (i < 0) goto err; 499 s += i; 500 off = off % 3600; 501 if (colons == 3 && off == 0) 502 continue; 503 if (1 <= colons) 504 *s++ = ':'; 505 i = snprintf(s, endp - s, "%02d", (int)(off / 60)); 506 if (i < 0) goto err; 507 s += i; 508 off = off % 60; 509 if (colons == 3 && off == 0) 510 continue; 511 if (2 <= colons) { 512 *s++ = ':'; 513 i = snprintf(s, endp - s, "%02d", (int)off); 514 if (i < 0) goto err; 515 s += i; 516 } 517 } 518 continue; 519 520 case '+': 521 STRFTIME("%a %b %e %H:%M:%S %Z %Y"); 522 continue; 523 524 case 'E': 525 /* POSIX locale extensions, ignored for now */ 526 flags |= BIT_OF(LOCALE_E); 527 if (*(format + 1) && strchr("cCxXyY", *(format + 1))) 528 goto again; 529 goto unknown; 530 case 'O': 531 /* POSIX locale extensions, ignored for now */ 532 flags |= BIT_OF(LOCALE_O); 533 if (*(format + 1) && strchr("deHkIlmMSuUVwWy", *(format + 1))) 534 goto again; 535 goto unknown; 536 537 case ':': 538 flags |= BIT_OF(COLONS); 539 { 540 size_t l = strspn(format, ":"); 541 format += l; 542 if (*format == 'z') { 543 colons = l; 544 format--; 545 goto again; 546 } 547 format -= l; 548 } 549 goto unknown; 550 551 case '_': 552 FLAG_FOUND(); 553 padding = ' '; 554 goto again; 555 556 case '-': 557 FLAG_FOUND(); 558 flags |= BIT_OF(LEFT); 559 goto again; 560 561 case '^': 562 FLAG_FOUND(); 563 flags |= BIT_OF(UPPER); 564 goto again; 565 566 case '#': 567 FLAG_FOUND(); 568 flags |= BIT_OF(CHCASE); 569 goto again; 570 571 case '0': 572 FLAG_FOUND(); 573 padding = '0'; 574 case '1': case '2': case '3': case '4': 575 case '5': case '6': case '7': case '8': case '9': 576 { 577 char *e; 578 precision = (int)strtoul(format, &e, 10); 579 format = e - 1; 580 goto again; 581 } 582 583 case '%': 584 FILL_PADDING(1); 585 *s++ = '%'; 586 continue; 587 588 default: 589 unknown: 590 i = format - sp + 1; 591 tp = sp; 592 precision = -1; 593 flags = 0; 594 padding = 0; 595 colons = 0; 596 break; 597 } 598 if (i) { 599 FILL_PADDING(i); 600 memcpy(s, tp, i); 601 switch (flags & (BIT_OF(UPPER) | BIT_OF(LOWER))) { 602 case BIT_OF(UPPER): 603 upcase(s, i); 604 break; 605 case BIT_OF(LOWER): 606 downcase(s, i); 607 break; 608 } 609 s += i; 610 } 611 } 612 if (s >= endp) { 613 goto err; 614 } 615 if (*format == '\0') { 616 *s = '\0'; 617 return (s - start); 618 } 619 return 0; 620} 621 622size_t 623date_strftime(char *s, size_t maxsize, const char *format, 624 const struct tmx *tmx) 625{ 626 return date_strftime_with_tmx(s, maxsize, format, tmx); 627} 628 629/* 630Local variables: 631c-file-style: "ruby" 632End: 633*/ 634