1 /*********************************************************************** 2 * * 3 * This software is part of the ast package * 4 * Copyright (c) 1985-2009 AT&T Intellectual Property * 5 * and is licensed under the * 6 * Common Public License, Version 1.0 * 7 * by AT&T Intellectual Property * 8 * * 9 * A copy of the License is available at * 10 * http://www.opensource.org/licenses/cpl1.0.txt * 11 * (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * 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': /* OBSOLETE no pad day of month */ 272 cp = number(cp, ep, (long)tm->tm_mday, 0, width, pad); 273 continue; 274 case 'e': /* blank padded day of month */ 275 cp = number(cp, ep, (long)tm->tm_mday, -2, width, pad); 276 continue; 277 case 'f': /* TM_DEFAULT override */ 278 p = tm_info.deformat; 279 goto push; 280 case 'F': /* ISO 8601:2000 standard date format */ 281 p = "%Y-%m-%d"; 282 goto push; 283 case 'g': /* %V 2 digit year */ 284 case 'G': /* %V 4 digit year */ 285 n = tm->tm_year + 1900; 286 if (tm->tm_yday < 7) 287 { 288 if (tmweek(tm, 2, -1, -1) >= 52) 289 n--; 290 } 291 else if (tm->tm_yday > 358) 292 { 293 if (tmweek(tm, 2, -1, -1) <= 1) 294 n++; 295 } 296 if (c == 'g') 297 { 298 n %= 100; 299 c = 2; 300 } 301 else 302 c = 4; 303 cp = number(cp, ep, (long)n, c, width, pad); 304 continue; 305 case 'H': /* hour (0 - 23) */ 306 cp = number(cp, ep, (long)tm->tm_hour, 2, width, pad); 307 continue; 308 case 'i': /* international `date(1)' date */ 309 p = tm_info.format[TM_INTERNATIONAL]; 310 goto push; 311 case 'I': /* hour (0 - 12) */ 312 if ((n = tm->tm_hour) > 12) n -= 12; 313 else if (n == 0) n = 12; 314 cp = number(cp, ep, (long)n, 2, 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 'j': /* Julian date (1 offset) */ 320 cp = number(cp, ep, (long)(tm->tm_yday + 1), 3, width, pad); 321 continue; 322 case 'k': /* `date(1)' date */ 323 p = tm_info.format[TM_DATE_1]; 324 goto push; 325 case 'K': 326 switch (alt) 327 { 328 case 'E': 329 p = (pad == '_') ? "%Y-%m-%d %H:%M:%S.%N %z" : "%Y-%m-%d+%H:%M:%S.%N%z"; 330 break; 331 case 'O': 332 p = (pad == '_') ? "%Y-%m-%d %H:%M:%S.%N" : "%Y-%m-%d+%H:%M:%S.%N"; 333 break; 334 default: 335 p = (pad == '_') ? "%Y-%m-%d %H:%M:%S" : "%Y-%m-%d+%H:%M:%S"; 336 break; 337 } 338 goto push; 339 case 'l': /* `ls -l' date */ 340 if (t) 341 { 342 now = tmxgettime(); 343 if (warped(t, now)) 344 { 345 p = tm_info.format[TM_DISTANT]; 346 goto push; 347 } 348 } 349 p = tm_info.format[TM_RECENT]; 350 goto push; 351 case 'L': /* TM_DEFAULT */ 352 case 'O': /* OBSOLETE */ 353 p = tm_info.format[TM_DEFAULT]; 354 goto push; 355 case 'm': /* month number */ 356 cp = number(cp, ep, (long)(tm->tm_mon + 1), 2, width, pad); 357 continue; 358 case 'M': /* minutes */ 359 cp = number(cp, ep, (long)tm->tm_min, 2, width, pad); 360 continue; 361 case 'n': 362 if (cp < ep) 363 *cp++ = '\n'; 364 continue; 365 case 'N': /* nanosecond part */ 366 cp = number(cp, ep, (long)tm->tm_nsec, 9, width, pad); 367 continue; 368 case 'o': /* set options */ 369 if (arg) 370 goto options; 371 /*OBSOLETE*/ 372 p = tm_info.deformat; 373 goto push; 374 case 'p': /* meridian */ 375 n = TM_MERIDIAN + (tm->tm_hour >= 12); 376 goto index; 377 case 'q': /* time zone type (nation code) */ 378 if (!(flags & TM_UTC)) 379 { 380 if ((zp = tm->tm_zone) != tm_info.local) 381 for (; zp >= tm_data.zone; zp--) 382 if (p = zp->type) 383 goto string; 384 else if (p = zp->type) 385 goto string; 386 } 387 continue; 388 case 'Q': /* %Q<alpha> or %Q<delim>recent<delim>distant<delim> */ 389 if (c = *format) 390 { 391 format++; 392 if (isalpha(c)) 393 { 394 switch (c) 395 { 396 case 'd': /* `ls -l' distant date */ 397 p = tm_info.format[TM_DISTANT]; 398 goto push; 399 case 'r': /* `ls -l' recent date */ 400 p = tm_info.format[TM_RECENT]; 401 goto push; 402 default: 403 format--; 404 break; 405 } 406 } 407 else 408 { 409 if (t) 410 { 411 now = tmxgettime(); 412 p = warped(t, now) ? (char*)0 : (char*)format; 413 } 414 else 415 p = (char*)format; 416 i = 0; 417 while (n = *format) 418 { 419 format++; 420 if (n == c) 421 { 422 if (!p) 423 p = (char*)format; 424 if (++i == 2) 425 goto push_delimiter; 426 } 427 } 428 } 429 } 430 continue; 431 case 'r': 432 p = tm_info.format[TM_MERIDIAN_TIME]; 433 goto push; 434 case 'R': 435 p = "%H:%M"; 436 goto push; 437 case 's': /* seconds[.nanoseconds] since the epoch */ 438 case '#': 439 now = t; 440 f = fmt; 441 *f++ = '%'; 442 if (pad == '0') 443 *f++ = pad; 444 if (width) 445 f += sfsprintf(f, &fmt[sizeof(fmt)] - f, "%d", width); 446 f += sfsprintf(f, &fmt[sizeof(fmt)] - f, "I%du", sizeof(Tmxsec_t)); 447 cp += sfsprintf(cp, ep - cp, fmt, tmxsec(now)); 448 if (parts > 1) 449 { 450 n = sfsprintf(cp, ep - cp, ".%09I*u", sizeof(Tmxnsec_t), tmxnsec(now)); 451 if (prec && n >= prec) 452 n = prec + 1; 453 cp += n; 454 } 455 continue; 456 case 'S': /* seconds */ 457 cp = number(cp, ep, (long)tm->tm_sec, 2, width, pad); 458 if ((flags & TM_SUBSECOND) && (format - 2) != oformat) 459 { 460 p = ".%N"; 461 goto push; 462 } 463 continue; 464 case 't': 465 if (cp < ep) 466 *cp++ = '\t'; 467 continue; 468 case 'T': 469 p = tm_info.format[TM_TIME]; 470 goto push; 471 case 'u': /* weekday number [1(Monday)-7] */ 472 if (!(i = tm->tm_wday)) 473 i = 7; 474 cp = number(cp, ep, (long)i, 0, width, pad); 475 continue; 476 case 'U': /* week number, Sunday as first day */ 477 cp = number(cp, ep, (long)tmweek(tm, 0, -1, -1), 2, width, pad); 478 continue; 479 case 'V': /* ISO week number */ 480 cp = number(cp, ep, (long)tmweek(tm, 2, -1, -1), 2, width, pad); 481 continue; 482 case 'W': /* week number, Monday as first day */ 483 cp = number(cp, ep, (long)tmweek(tm, 1, -1, -1), 2, width, pad); 484 continue; 485 case 'w': /* weekday number [0(Sunday)-6] */ 486 cp = number(cp, ep, (long)tm->tm_wday, 0, width, pad); 487 continue; 488 case 'x': 489 p = tm_info.format[TM_DATE]; 490 goto push; 491 case 'X': 492 p = tm_info.format[TM_TIME]; 493 goto push; 494 case 'y': /* year in the form yy */ 495 cp = number(cp, ep, (long)(tm->tm_year % 100), 2, width, pad); 496 continue; 497 case 'Y': /* year in the form ccyy */ 498 cp = number(cp, ep, (long)(1900 + tm->tm_year), 4, width, pad); 499 continue; 500 case 'z': /* time zone west offset */ 501 if (arg) 502 { 503 if ((zp = tmzone(arg, &f, 0, 0)) && !*f && tm->tm_zone != zp) 504 tm = tmxtm(tm, tmxtime(tm, tm->tm_zone->west + (tm->tm_isdst ? tm->tm_zone->dst : 0)), zp); 505 continue; 506 } 507 if ((ep - cp) >= 16) 508 cp = tmpoff(cp, ep - cp, "", (flags & TM_UTC) ? 0 : tm->tm_zone->west + (tm->tm_isdst ? tm->tm_zone->dst : 0), 24 * 60); 509 continue; 510 case 'Z': /* time zone */ 511 if (arg) 512 { 513 if ((zp = tmzone(arg, &f, 0, 0)) && !*f && tm->tm_zone != zp) 514 tm = tmxtm(tm, tmxtime(tm, tm->tm_zone->west + (tm->tm_isdst ? tm->tm_zone->dst : 0)), zp); 515 continue; 516 } 517 p = (flags & TM_UTC) ? tm_info.format[TM_UT] : tm->tm_isdst && tm->tm_zone->daylight ? tm->tm_zone->daylight : tm->tm_zone->standard; 518 goto string; 519 case '+': /* old %+flag */ 520 case '!': /* old %!flag */ 521 format--; 522 /*FALLTHROUGH*/ 523 case '=': /* old %=[=][+-]flag */ 524 for (arg = argbuf; *format == '=' || *format == '-' || *format == '+' || *format == '!'; format++) 525 if (arg < &argbuf[sizeof(argbuf) - 2]) 526 *arg++ = *format; 527 if (*arg++ = *format) 528 format++; 529 *arg = 0; 530 arg = argbuf; 531 goto options; 532 default: 533 if (cp < ep) 534 *cp++ = '%'; 535 if (cp < ep) 536 *cp++ = c; 537 continue; 538 } 539 index: 540 p = tm_info.format[n]; 541 string: 542 while (cp < ep && (*cp = *p++)) 543 cp++; 544 continue; 545 options: 546 c = '+'; 547 i = 0; 548 for (;;) 549 { 550 switch (*arg++) 551 { 552 case 0: 553 n = 0; 554 break; 555 case '=': 556 i = !i; 557 continue; 558 case '+': 559 case '-': 560 case '!': 561 c = *(arg - 1); 562 continue; 563 case 'l': 564 n = TM_LEAP; 565 break; 566 case 'n': 567 case 's': 568 n = TM_SUBSECOND; 569 break; 570 case 'u': 571 n = TM_UTC; 572 break; 573 default: 574 continue; 575 } 576 if (!n) 577 break; 578 579 /* 580 * right, the global state stinks 581 * but we respect its locale-like status 582 */ 583 584 if (c == '+') 585 { 586 if (!(flags & n)) 587 { 588 flags |= n; 589 tm_info.flags |= n; 590 tm = tmxtm(tm, t, (flags & TM_UTC) ? &tm_data.zone[2] : tm->tm_zone); 591 if (!i) 592 tm_info.flags &= ~n; 593 } 594 } 595 else if (flags & n) 596 { 597 flags &= ~n; 598 tm_info.flags &= ~n; 599 tm = tmxtm(tm, t, (flags & TM_UTC) ? &tm_data.zone[2] : tm->tm_zone); 600 if (!i) 601 tm_info.flags |= n; 602 } 603 } 604 continue; 605 push: 606 c = 0; 607 push_delimiter: 608 if (sp < &stack[elementsof(stack)]) 609 { 610 sp->format = (char*)format; 611 format = p; 612 sp->delimiter = delimiter; 613 delimiter = c; 614 sp++; 615 } 616 continue; 617 } 618 tm_info.flags = flags; 619 if (cp >= ep) 620 cp = ep - 1; 621 *cp = 0; 622 return cp; 623 } 624