1 /*********************************************************************** 2 * * 3 * This software is part of the ast package * 4 * Copyright (c) 1985-2011 AT&T Intellectual Property * 5 * and is licensed under the * 6 * Eclipse Public License, Version 1.0 * 7 * by AT&T Intellectual Property * 8 * * 9 * A copy of the License is available at * 10 * http://www.eclipse.org/org/documents/epl-v10.html * 11 * (with md5 checksum b35adb5213ca9657e911e9befb180842) * 12 * * 13 * Information and Software Systems Research * 14 * AT&T Research * 15 * Florham Park NJ * 16 * * 17 * Glenn Fowler <gsf@research.att.com> * 18 * David Korn <dgk@research.att.com> * 19 * Phong Vo <kpv@research.att.com> * 20 * * 21 ***********************************************************************/ 22 #pragma prototyped 23 /* 24 * Glenn Fowler 25 * AT&T Research 26 * 27 * Time_t conversion support 28 */ 29 30 #include <tmx.h> 31 #include <ctype.h> 32 33 #define warped(t,n) ((t)<((n)-tmxsns(6L*30L*24L*60L*60L,0))||(t)>((n)+tmxsns(24L*60L*60L,0))) 34 35 /* 36 * format n with padding p into s 37 * return end of s 38 * 39 * p: <0 blank padding 40 * 0 no padding 41 * >0 0 padding 42 */ 43 44 static char* 45 number(register char* s, register char* e, register long n, register int p, int w, int pad) 46 { 47 char* b; 48 49 if (w) 50 { 51 if (p > 0 && (pad == 0 || pad == '0')) 52 while (w > p) 53 { 54 p++; 55 n *= 10; 56 } 57 else if (w > p) 58 p = w; 59 } 60 switch (pad) 61 { 62 case '-': 63 p = 0; 64 break; 65 case '_': 66 if (p > 0) 67 p = -p; 68 break; 69 case '0': 70 if (p < 0) 71 p = -p; 72 break; 73 } 74 b = s; 75 if (p > 0) 76 s += sfsprintf(s, e - s, "%0*lu", p, n); 77 else if (p < 0) 78 s += sfsprintf(s, e - s, "%*lu", -p, n); 79 else 80 s += sfsprintf(s, e - s, "%lu", n); 81 if (w && (s - b) > w) 82 *(s = b + w) = 0; 83 return s; 84 } 85 86 typedef struct Stack_s 87 { 88 char* format; 89 int delimiter; 90 } Stack_t; 91 92 /* 93 * format t into buf of length len 94 * end of buf is returned 95 */ 96 97 char* 98 tmxfmt(char* buf, size_t len, const char* format, Time_t t) 99 { 100 register char* cp; 101 register char* ep; 102 register char* p; 103 register int n; 104 int c; 105 int i; 106 int flags; 107 int alt; 108 int pad; 109 int delimiter; 110 int width; 111 int prec; 112 int parts; 113 char* arg; 114 char* f; 115 const char* oformat; 116 Tm_t* tm; 117 Tm_zone_t* zp; 118 Time_t now; 119 Stack_t* sp; 120 Stack_t stack[8]; 121 Tm_t ts; 122 char argbuf[256]; 123 char fmt[32]; 124 125 tmlocale(); 126 tm = tmxtm(&ts, t, NiL); 127 if (!format || !*format) 128 format = tm_info.deformat; 129 oformat = format; 130 flags = tm_info.flags; 131 sp = &stack[0]; 132 cp = buf; 133 ep = buf + len; 134 delimiter = 0; 135 for (;;) 136 { 137 if ((c = *format++) == delimiter) 138 { 139 delimiter = 0; 140 if (sp <= &stack[0]) 141 break; 142 sp--; 143 format = sp->format; 144 delimiter = sp->delimiter; 145 continue; 146 } 147 if (c != '%') 148 { 149 if (cp < ep) 150 *cp++ = c; 151 continue; 152 } 153 alt = 0; 154 arg = 0; 155 pad = 0; 156 width = 0; 157 prec = 0; 158 parts = 0; 159 for (;;) 160 { 161 switch (c = *format++) 162 { 163 case '_': 164 case '-': 165 pad = c; 166 continue; 167 case 'E': 168 case 'O': 169 if (!isalpha(*format)) 170 break; 171 alt = c; 172 continue; 173 case '0': 174 if (!parts) 175 { 176 pad = c; 177 continue; 178 } 179 /*FALLTHROUGH*/ 180 case '1': 181 case '2': 182 case '3': 183 case '4': 184 case '5': 185 case '6': 186 case '7': 187 case '8': 188 case '9': 189 switch (parts) 190 { 191 case 0: 192 parts++; 193 /*FALLTHROUGH*/ 194 case 1: 195 width = width * 10 + (c - '0'); 196 break; 197 case 2: 198 prec = prec * 10 + (c - '0'); 199 break; 200 } 201 continue; 202 case '.': 203 if (!parts++) 204 parts++; 205 continue; 206 case '(': 207 i = 1; 208 arg = argbuf; 209 for (;;) 210 { 211 if (!(c = *format++)) 212 { 213 format--; 214 break; 215 } 216 else if (c == '(') 217 i++; 218 else if (c == ')' && !--i) 219 break; 220 else if (arg < &argbuf[sizeof(argbuf) - 1]) 221 *arg++ = c; 222 } 223 *arg = 0; 224 arg = argbuf; 225 continue; 226 default: 227 break; 228 } 229 break; 230 } 231 switch (c) 232 { 233 case 0: 234 format--; 235 continue; 236 case '%': 237 if (cp < ep) 238 *cp++ = '%'; 239 continue; 240 case '?': 241 if (tm_info.deformat != tm_info.format[TM_DEFAULT]) 242 format = tm_info.deformat; 243 else if (!*format) 244 format = tm_info.format[TM_DEFAULT]; 245 continue; 246 case 'a': /* abbreviated day of week name */ 247 n = TM_DAY_ABBREV + tm->tm_wday; 248 goto index; 249 case 'A': /* day of week name */ 250 n = TM_DAY + tm->tm_wday; 251 goto index; 252 case 'b': /* abbreviated month name */ 253 case 'h': 254 n = TM_MONTH_ABBREV + tm->tm_mon; 255 goto index; 256 case 'B': /* month name */ 257 n = TM_MONTH + tm->tm_mon; 258 goto index; 259 case 'c': /* `ctime(3)' date sans newline */ 260 p = tm_info.format[TM_CTIME]; 261 goto push; 262 case 'C': /* 2 digit century */ 263 cp = number(cp, ep, (long)(1900 + tm->tm_year) / 100, 2, width, pad); 264 continue; 265 case 'd': /* day of month */ 266 cp = number(cp, ep, (long)tm->tm_mday, 2, width, pad); 267 continue; 268 case 'D': /* date */ 269 p = tm_info.format[TM_DATE]; 270 goto push; 271 case 'e': /* blank padded day of month */ 272 cp = number(cp, ep, (long)tm->tm_mday, -2, width, pad); 273 continue; 274 case 'f': /* (AST) OBSOLETE use %Qf */ 275 p = "%Qf"; 276 goto push; 277 case 'F': /* ISO 8601:2000 standard date format */ 278 p = "%Y-%m-%d"; 279 goto push; 280 case 'g': /* %V 2 digit year */ 281 case 'G': /* %V 4 digit year */ 282 n = tm->tm_year + 1900; 283 if (tm->tm_yday < 7) 284 { 285 if (tmweek(tm, 2, -1, -1) >= 52) 286 n--; 287 } 288 else if (tm->tm_yday > 358) 289 { 290 if (tmweek(tm, 2, -1, -1) <= 1) 291 n++; 292 } 293 if (c == 'g') 294 { 295 n %= 100; 296 c = 2; 297 } 298 else 299 c = 4; 300 cp = number(cp, ep, (long)n, c, width, pad); 301 continue; 302 case 'H': /* hour (0 - 23) */ 303 cp = number(cp, ep, (long)tm->tm_hour, 2, width, pad); 304 continue; 305 case 'i': /* (AST) OBSOLETE use %QI */ 306 p = "%QI"; 307 goto push; 308 case 'I': /* hour (0 - 12) */ 309 if ((n = tm->tm_hour) > 12) n -= 12; 310 else if (n == 0) n = 12; 311 cp = number(cp, ep, (long)n, 2, width, pad); 312 continue; 313 case 'j': /* Julian date (1 offset) */ 314 cp = number(cp, ep, (long)(tm->tm_yday + 1), 3, width, pad); 315 continue; 316 case 'J': /* Julian date (0 offset) */ 317 cp = number(cp, ep, (long)tm->tm_yday, 3, width, pad); 318 continue; 319 case 'k': /* (AST) OBSOLETE use %QD */ 320 p = "%QD"; 321 goto push; 322 case 'K': /* (AST) largest to smallest */ 323 switch (alt) 324 { 325 case 'E': 326 p = (pad == '_') ? "%Y-%m-%d %H:%M:%S.%N %z" : "%Y-%m-%d+%H:%M:%S.%N%z"; 327 break; 328 case 'O': 329 p = (pad == '_') ? "%Y-%m-%d %H:%M:%S.%N" : "%Y-%m-%d+%H:%M:%S.%N"; 330 break; 331 default: 332 p = (pad == '_') ? "%Y-%m-%d %H:%M:%S" : "%Y-%m-%d+%H:%M:%S"; 333 break; 334 } 335 goto push; 336 case 'l': /* (AST) OBSOLETE use %QL */ 337 p = "%QL"; 338 goto push; 339 case 'L': /* (AST) OBSOLETE use %Ql */ 340 p = "%Ql"; 341 goto push; 342 case 'm': /* month number */ 343 cp = number(cp, ep, (long)(tm->tm_mon + 1), 2, width, pad); 344 continue; 345 case 'M': /* minutes */ 346 cp = number(cp, ep, (long)tm->tm_min, 2, width, pad); 347 continue; 348 case 'n': 349 if (cp < ep) 350 *cp++ = '\n'; 351 continue; 352 case 'N': /* (AST|GNU) nanosecond part */ 353 cp = number(cp, ep, (long)tm->tm_nsec, 9, width, pad); 354 continue; 355 #if 0 356 case 'o': /* (UNUSED) */ 357 continue; 358 #endif 359 case 'p': /* meridian */ 360 n = TM_MERIDIAN + (tm->tm_hour >= 12); 361 goto index; 362 case 'P': /* (AST|GNU) lower case meridian */ 363 p = tm_info.format[TM_MERIDIAN + (tm->tm_hour >= 12)]; 364 while (cp < ep && (n = *p++)) 365 *cp++ = isupper(n) ? tolower(n) : n; 366 continue; 367 case 'q': /* (AST) OBSOLETE use %Qz */ 368 p = "%Qz"; 369 goto push; 370 case 'Q': /* (AST) %Q<alpha> or %Q<delim>recent<delim>distant<delim> */ 371 if (c = *format) 372 { 373 format++; 374 if (isalpha(c)) 375 { 376 switch (c) 377 { 378 case 'd': /* `ls -l' distant date */ 379 p = tm_info.format[TM_DISTANT]; 380 goto push; 381 case 'D': /* `date(1)' date */ 382 p = tm_info.format[TM_DATE_1]; 383 goto push; 384 case 'f': /* TM_DEFAULT override */ 385 p = tm_info.deformat; 386 goto push; 387 case 'I': /* international `date(1)' date */ 388 p = tm_info.format[TM_INTERNATIONAL]; 389 goto push; 390 case 'l': /* TM_DEFAULT */ 391 p = tm_info.format[TM_DEFAULT]; 392 goto push; 393 case 'L': /* `ls -l' date */ 394 if (t) 395 { 396 now = tmxgettime(); 397 if (warped(t, now)) 398 { 399 p = tm_info.format[TM_DISTANT]; 400 goto push; 401 } 402 } 403 p = tm_info.format[TM_RECENT]; 404 goto push; 405 case 'o': /* set options ( %([+-]flag...)o ) */ 406 if (arg) 407 { 408 c = '+'; 409 i = 0; 410 for (;;) 411 { 412 switch (*arg++) 413 { 414 case 0: 415 n = 0; 416 break; 417 case '=': 418 i = !i; 419 continue; 420 case '+': 421 case '-': 422 case '!': 423 c = *(arg - 1); 424 continue; 425 case 'l': 426 n = TM_LEAP; 427 break; 428 case 'n': 429 case 's': 430 n = TM_SUBSECOND; 431 break; 432 case 'u': 433 n = TM_UTC; 434 break; 435 default: 436 continue; 437 } 438 if (!n) 439 break; 440 441 /* 442 * right, the global state stinks 443 * but we respect its locale-like status 444 */ 445 446 if (c == '+') 447 { 448 if (!(flags & n)) 449 { 450 flags |= n; 451 tm_info.flags |= n; 452 tm = tmxtm(tm, t, (flags & TM_UTC) ? &tm_data.zone[2] : tm->tm_zone); 453 if (!i) 454 tm_info.flags &= ~n; 455 } 456 } 457 else if (flags & n) 458 { 459 flags &= ~n; 460 tm_info.flags &= ~n; 461 tm = tmxtm(tm, t, (flags & TM_UTC) ? &tm_data.zone[2] : tm->tm_zone); 462 if (!i) 463 tm_info.flags |= n; 464 } 465 } 466 } 467 break; 468 case 'r': /* `ls -l' recent date */ 469 p = tm_info.format[TM_RECENT]; 470 goto push; 471 case 'z': /* time zone nation code */ 472 if (!(flags & TM_UTC)) 473 { 474 if ((zp = tm->tm_zone) != tm_info.local) 475 for (; zp >= tm_data.zone; zp--) 476 if (p = zp->type) 477 goto string; 478 else if (p = zp->type) 479 goto string; 480 } 481 break; 482 default: 483 format--; 484 break; 485 } 486 } 487 else 488 { 489 if (t) 490 { 491 now = tmxgettime(); 492 p = warped(t, now) ? (char*)0 : (char*)format; 493 } 494 else 495 p = (char*)format; 496 i = 0; 497 while (n = *format) 498 { 499 format++; 500 if (n == c) 501 { 502 if (!p) 503 p = (char*)format; 504 if (++i == 2) 505 goto push_delimiter; 506 } 507 } 508 } 509 } 510 continue; 511 case 'r': 512 p = tm_info.format[TM_MERIDIAN_TIME]; 513 goto push; 514 case 'R': 515 p = "%H:%M"; 516 goto push; 517 case 's': /* (DEFACTO) seconds[.nanoseconds] since the epoch */ 518 case '#': 519 now = t; 520 f = fmt; 521 *f++ = '%'; 522 if (pad == '0') 523 *f++ = pad; 524 if (width) 525 f += sfsprintf(f, &fmt[sizeof(fmt)] - f, "%d", width); 526 f += sfsprintf(f, &fmt[sizeof(fmt)] - f, "I%du", sizeof(Tmxsec_t)); 527 cp += sfsprintf(cp, ep - cp, fmt, tmxsec(now)); 528 if (parts > 1) 529 { 530 n = sfsprintf(cp, ep - cp, ".%09I*u", sizeof(Tmxnsec_t), tmxnsec(now)); 531 if (prec && n >= prec) 532 n = prec + 1; 533 cp += n; 534 } 535 continue; 536 case 'S': /* seconds */ 537 cp = number(cp, ep, (long)tm->tm_sec, 2, width, pad); 538 if ((flags & TM_SUBSECOND) && (format - 2) != oformat) 539 { 540 p = ".%N"; 541 goto push; 542 } 543 continue; 544 case 't': 545 if (cp < ep) 546 *cp++ = '\t'; 547 continue; 548 case 'T': 549 p = tm_info.format[TM_TIME]; 550 goto push; 551 case 'u': /* weekday number [1(Monday)-7] */ 552 if (!(i = tm->tm_wday)) 553 i = 7; 554 cp = number(cp, ep, (long)i, 0, width, pad); 555 continue; 556 case 'U': /* week number, Sunday as first day */ 557 cp = number(cp, ep, (long)tmweek(tm, 0, -1, -1), 2, width, pad); 558 continue; 559 #if 0 560 case 'v': /* (UNUSED) */ 561 continue; 562 #endif 563 case 'V': /* ISO week number */ 564 cp = number(cp, ep, (long)tmweek(tm, 2, -1, -1), 2, width, pad); 565 continue; 566 case 'W': /* week number, Monday as first day */ 567 cp = number(cp, ep, (long)tmweek(tm, 1, -1, -1), 2, width, pad); 568 continue; 569 case 'w': /* weekday number [0(Sunday)-6] */ 570 cp = number(cp, ep, (long)tm->tm_wday, 0, width, pad); 571 continue; 572 case 'x': 573 p = tm_info.format[TM_DATE]; 574 goto push; 575 case 'X': 576 p = tm_info.format[TM_TIME]; 577 goto push; 578 case 'y': /* year in the form yy */ 579 cp = number(cp, ep, (long)(tm->tm_year % 100), 2, width, pad); 580 continue; 581 case 'Y': /* year in the form ccyy */ 582 cp = number(cp, ep, (long)(1900 + tm->tm_year), 4, width, pad); 583 continue; 584 case 'z': /* time zone west offset */ 585 if (arg) 586 { 587 if ((zp = tmzone(arg, &f, 0, 0)) && !*f && tm->tm_zone != zp) 588 tm = tmxtm(tm, tmxtime(tm, tm->tm_zone->west + (tm->tm_isdst ? tm->tm_zone->dst : 0)), zp); 589 continue; 590 } 591 if ((ep - cp) >= 16) 592 cp = tmpoff(cp, ep - cp, "", (flags & TM_UTC) ? 0 : tm->tm_zone->west + (tm->tm_isdst ? tm->tm_zone->dst : 0), pad == '_' ? -24 * 60 : 24 * 60); 593 continue; 594 case 'Z': /* time zone */ 595 if (arg) 596 { 597 if ((zp = tmzone(arg, &f, 0, 0)) && !*f && tm->tm_zone != zp) 598 tm = tmxtm(tm, tmxtime(tm, tm->tm_zone->west + (tm->tm_isdst ? tm->tm_zone->dst : 0)), zp); 599 continue; 600 } 601 p = (flags & TM_UTC) ? tm_info.format[TM_UT] : tm->tm_isdst && tm->tm_zone->daylight ? tm->tm_zone->daylight : tm->tm_zone->standard; 602 goto string; 603 case '=': /* (AST) OBSOLETE use %([+-]flag...)Qo (old %=[=][+-]flag) */ 604 for (arg = argbuf; *format == '=' || *format == '-' || *format == '+' || *format == '!'; format++) 605 if (arg < &argbuf[sizeof(argbuf) - 2]) 606 *arg++ = *format; 607 if (*arg++ = *format) 608 format++; 609 *arg = 0; 610 arg = argbuf; 611 goto options; 612 default: 613 if (cp < ep) 614 *cp++ = '%'; 615 if (cp < ep) 616 *cp++ = c; 617 continue; 618 } 619 index: 620 p = tm_info.format[n]; 621 string: 622 while (cp < ep && (*cp = *p++)) 623 cp++; 624 continue; 625 options: 626 c = '+'; 627 i = 0; 628 for (;;) 629 { 630 switch (*arg++) 631 { 632 case 0: 633 n = 0; 634 break; 635 case '=': 636 i = !i; 637 continue; 638 case '+': 639 case '-': 640 case '!': 641 c = *(arg - 1); 642 continue; 643 case 'l': 644 n = TM_LEAP; 645 break; 646 case 'n': 647 case 's': 648 n = TM_SUBSECOND; 649 break; 650 case 'u': 651 n = TM_UTC; 652 break; 653 default: 654 continue; 655 } 656 if (!n) 657 break; 658 659 /* 660 * right, the global state stinks 661 * but we respect its locale-like status 662 */ 663 664 if (c == '+') 665 { 666 if (!(flags & n)) 667 { 668 flags |= n; 669 tm_info.flags |= n; 670 tm = tmxtm(tm, t, (flags & TM_UTC) ? &tm_data.zone[2] : tm->tm_zone); 671 if (!i) 672 tm_info.flags &= ~n; 673 } 674 } 675 else if (flags & n) 676 { 677 flags &= ~n; 678 tm_info.flags &= ~n; 679 tm = tmxtm(tm, t, (flags & TM_UTC) ? &tm_data.zone[2] : tm->tm_zone); 680 if (!i) 681 tm_info.flags |= n; 682 } 683 } 684 continue; 685 push: 686 c = 0; 687 push_delimiter: 688 if (sp < &stack[elementsof(stack)]) 689 { 690 sp->format = (char*)format; 691 format = p; 692 sp->delimiter = delimiter; 693 delimiter = c; 694 sp++; 695 } 696 continue; 697 } 698 tm_info.flags = flags; 699 if (cp >= ep) 700 cp = ep - 1; 701 *cp = 0; 702 return cp; 703 } 704