1/* -*- c-file-style: "linux" -*- */ 2 3/* 4 * strftime.c 5 * 6 * Public-domain implementation of ANSI C library routine. 7 * 8 * It's written in old-style C for maximal portability. 9 * However, since I'm used to prototypes, I've included them too. 10 * 11 * If you want stuff in the System V ascftime routine, add the SYSV_EXT define. 12 * For extensions from SunOS, add SUNOS_EXT. 13 * For stuff needed to implement the P1003.2 date command, add POSIX2_DATE. 14 * For VMS dates, add VMS_EXT. 15 * For a an RFC822 time format, add MAILHEADER_EXT. 16 * For ISO week years, add ISO_DATE_EXT. 17 * For complete POSIX semantics, add POSIX_SEMANTICS. 18 * 19 * The code for %c, %x, and %X now follows the 1003.2 specification for 20 * the POSIX locale. 21 * This version ignores LOCALE information. 22 * It also doesn't worry about multi-byte characters. 23 * So there. 24 * 25 * This file is also shipped with GAWK (GNU Awk), gawk specific bits of 26 * code are included if GAWK is defined. 27 * 28 * Arnold Robbins 29 * January, February, March, 1991 30 * Updated March, April 1992 31 * Updated April, 1993 32 * Updated February, 1994 33 * Updated May, 1994 34 * Updated January, 1995 35 * Updated September, 1995 36 * Updated January, 1996 37 * 38 * Fixes from ado@elsie.nci.nih.gov 39 * February 1991, May 1992 40 * Fixes from Tor Lillqvist tml@tik.vtt.fi 41 * May, 1993 42 * Further fixes from ado@elsie.nci.nih.gov 43 * February 1994 44 * %z code from chip@chinacat.unicom.com 45 * Applied September 1995 46 * %V code fixed (again) and %G, %g added, 47 * January 1996 48 */ 49 50#include "ruby/ruby.h" 51#include "ruby/encoding.h" 52#include "timev.h" 53 54#ifndef GAWK 55#include <stdio.h> 56#include <ctype.h> 57#include <string.h> 58#include <time.h> 59#include <sys/types.h> 60#include <errno.h> 61#endif 62#if defined(TM_IN_SYS_TIME) || !defined(GAWK) 63#include <sys/types.h> 64#if HAVE_SYS_TIME_H 65#include <sys/time.h> 66#endif 67#endif 68#include <math.h> 69 70/* defaults: season to taste */ 71#define SYSV_EXT 1 /* stuff in System V ascftime routine */ 72#define SUNOS_EXT 1 /* stuff in SunOS strftime routine */ 73#define POSIX2_DATE 1 /* stuff in Posix 1003.2 date command */ 74#define VMS_EXT 1 /* include %v for VMS date format */ 75#define MAILHEADER_EXT 1 /* add %z for HHMM format */ 76#define ISO_DATE_EXT 1 /* %G and %g for year of ISO week */ 77 78#if defined(ISO_DATE_EXT) 79#if ! defined(POSIX2_DATE) 80#define POSIX2_DATE 1 81#endif 82#endif 83 84#if defined(POSIX2_DATE) 85#if ! defined(SYSV_EXT) 86#define SYSV_EXT 1 87#endif 88#if ! defined(SUNOS_EXT) 89#define SUNOS_EXT 1 90#endif 91#endif 92 93#if defined(POSIX2_DATE) 94#define adddecl(stuff) stuff 95#else 96#define adddecl(stuff) 97#endif 98 99#undef strchr /* avoid AIX weirdness */ 100 101#if !defined __STDC__ && !defined _WIN32 102#define const /**/ 103static int weeknumber(); 104adddecl(static int iso8601wknum();) 105static int weeknumber_v(); 106adddecl(static int iso8601wknum_v();) 107#else 108static int weeknumber(const struct tm *timeptr, int firstweekday); 109adddecl(static int iso8601wknum(const struct tm *timeptr);) 110static int weeknumber_v(const struct vtm *vtm, int firstweekday); 111adddecl(static int iso8601wknum_v(const struct vtm *vtm);) 112#endif 113 114#ifdef STDC_HEADERS 115#include <stdlib.h> 116#include <string.h> 117#else 118extern void *malloc(); 119extern void *realloc(); 120extern char *getenv(); 121extern char *strchr(); 122#endif 123 124#define range(low, item, hi) max((low), min((item), (hi))) 125 126#undef min /* just in case */ 127 128/* min --- return minimum of two numbers */ 129 130static inline int 131min(int a, int b) 132{ 133 return (a < b ? a : b); 134} 135 136#undef max /* also, just in case */ 137 138/* max --- return maximum of two numbers */ 139 140static inline int 141max(int a, int b) 142{ 143 return (a > b ? a : b); 144} 145 146#ifdef NO_STRING_LITERAL_CONCATENATION 147#error No string literal concatenation 148#endif 149 150#define add(x,y) (rb_funcall((x), '+', 1, (y))) 151#define sub(x,y) (rb_funcall((x), '-', 1, (y))) 152#define mul(x,y) (rb_funcall((x), '*', 1, (y))) 153#define quo(x,y) (rb_funcall((x), rb_intern("quo"), 1, (y))) 154#define div(x,y) (rb_funcall((x), rb_intern("div"), 1, (y))) 155#define mod(x,y) (rb_funcall((x), '%', 1, (y))) 156 157/* strftime --- produce formatted time */ 158 159/* 160 * enc is the encoding of the format. It is used as the encoding of resulted 161 * string, but the name of the month and weekday are always US-ASCII. So it 162 * is only used for the timezone name on Windows. 163 */ 164static size_t 165rb_strftime_with_timespec(char *s, size_t maxsize, const char *format, rb_encoding *enc, const struct vtm *vtm, VALUE timev, struct timespec *ts, int gmt) 166{ 167 const char *const endp = s + maxsize; 168 const char *const start = s; 169 const char *sp, *tp; 170#define TBUFSIZE 100 171 auto char tbuf[TBUFSIZE]; 172 long off; 173 ptrdiff_t i; 174 int w; 175 long y; 176 int precision, flags, colons; 177 char padding; 178 enum {LEFT, CHCASE, LOWER, UPPER}; 179#define BIT_OF(n) (1U<<(n)) 180#ifdef MAILHEADER_EXT 181 int sign; 182#endif 183 184 /* various tables, useful in North America */ 185 static const char days_l[][10] = { 186 "Sunday", "Monday", "Tuesday", "Wednesday", 187 "Thursday", "Friday", "Saturday", 188 }; 189 static const char months_l[][10] = { 190 "January", "February", "March", "April", 191 "May", "June", "July", "August", "September", 192 "October", "November", "December", 193 }; 194 static const char ampm[][3] = { "AM", "PM", }; 195 196 if (s == NULL || format == NULL || vtm == NULL || maxsize == 0) 197 return 0; 198 199 /* quick check if we even need to bother */ 200 if (strchr(format, '%') == NULL && strlen(format) + 1 >= maxsize) { 201 err: 202 errno = ERANGE; 203 return 0; 204 } 205 206 if (enc && (enc == rb_usascii_encoding() || 207 enc == rb_ascii8bit_encoding() || enc == rb_locale_encoding())) { 208 enc = NULL; 209 } 210 211 for (; *format && s < endp - 1; format++) { 212#define FLAG_FOUND() do { \ 213 if (precision > 0) \ 214 goto unknown; \ 215 } while (0) 216#define NEEDS(n) do if (s >= endp || (n) >= endp - s - 1) goto err; while (0) 217#define FILL_PADDING(i) do { \ 218 if (!(flags & BIT_OF(LEFT)) && precision > (i)) { \ 219 NEEDS(precision); \ 220 memset(s, padding ? padding : ' ', precision - (i)); \ 221 s += precision - (i); \ 222 } \ 223 else { \ 224 NEEDS(i); \ 225 } \ 226} while (0); 227#define FMT(def_pad, def_prec, fmt, val) \ 228 do { \ 229 int l; \ 230 if (precision <= 0) precision = (def_prec); \ 231 if (flags & BIT_OF(LEFT)) precision = 1; \ 232 l = snprintf(s, endp - s, \ 233 ((padding == '0' || (!padding && (def_pad) == '0')) ? "%0*"fmt : "%*"fmt), \ 234 precision, (val)); \ 235 if (l < 0) goto err; \ 236 s += l; \ 237 } while (0) 238#define STRFTIME(fmt) \ 239 do { \ 240 i = rb_strftime_with_timespec(s, endp - s, (fmt), enc, vtm, timev, ts, gmt); \ 241 if (!i) return 0; \ 242 if (precision > i) {\ 243 NEEDS(precision); \ 244 memmove(s + precision - i, s, i);\ 245 memset(s, padding ? padding : ' ', precision - i); \ 246 s += precision; \ 247 }\ 248 else s += i; \ 249 } while (0) 250#define FMTV(def_pad, def_prec, fmt, val) \ 251 do { \ 252 VALUE tmp = (val); \ 253 if (FIXNUM_P(tmp)) { \ 254 FMT((def_pad), (def_prec), "l"fmt, FIX2LONG(tmp)); \ 255 } \ 256 else { \ 257 VALUE args[2], result; \ 258 size_t l; \ 259 if (precision <= 0) precision = (def_prec); \ 260 if (flags & BIT_OF(LEFT)) precision = 1; \ 261 args[0] = INT2FIX(precision); \ 262 args[1] = (val); \ 263 if (padding == '0' || (!padding && (def_pad) == '0')) \ 264 result = rb_str_format(2, args, rb_str_new2("%0*"fmt)); \ 265 else \ 266 result = rb_str_format(2, args, rb_str_new2("%*"fmt)); \ 267 l = strlcpy(s, StringValueCStr(result), endp-s); \ 268 if ((size_t)(endp-s) <= l) \ 269 goto err; \ 270 s += l; \ 271 } \ 272 } while (0) 273 274 if (*format != '%') { 275 *s++ = *format; 276 continue; 277 } 278 tp = tbuf; 279 sp = format; 280 precision = -1; 281 flags = 0; 282 padding = 0; 283 colons = 0; 284 again: 285 switch (*++format) { 286 case '\0': 287 format--; 288 goto unknown; 289 290 case '%': 291 FILL_PADDING(1); 292 *s++ = '%'; 293 continue; 294 295 case 'a': /* abbreviated weekday name */ 296 if (flags & BIT_OF(CHCASE)) { 297 flags &= ~(BIT_OF(LOWER)|BIT_OF(CHCASE)); 298 flags |= BIT_OF(UPPER); 299 } 300 if (vtm->wday < 0 || vtm->wday > 6) 301 i = 1, tp = "?"; 302 else 303 i = 3, tp = days_l[vtm->wday]; 304 break; 305 306 case 'A': /* full weekday name */ 307 if (flags & BIT_OF(CHCASE)) { 308 flags &= ~(BIT_OF(LOWER)|BIT_OF(CHCASE)); 309 flags |= BIT_OF(UPPER); 310 } 311 if (vtm->wday < 0 || vtm->wday > 6) 312 i = 1, tp = "?"; 313 else 314 i = strlen(tp = days_l[vtm->wday]); 315 break; 316 317#ifdef SYSV_EXT 318 case 'h': /* abbreviated month name */ 319#endif 320 case 'b': /* abbreviated month name */ 321 if (flags & BIT_OF(CHCASE)) { 322 flags &= ~(BIT_OF(LOWER)|BIT_OF(CHCASE)); 323 flags |= BIT_OF(UPPER); 324 } 325 if (vtm->mon < 1 || vtm->mon > 12) 326 i = 1, tp = "?"; 327 else 328 i = 3, tp = months_l[vtm->mon-1]; 329 break; 330 331 case 'B': /* full month name */ 332 if (flags & BIT_OF(CHCASE)) { 333 flags &= ~(BIT_OF(LOWER)|BIT_OF(CHCASE)); 334 flags |= BIT_OF(UPPER); 335 } 336 if (vtm->mon < 1 || vtm->mon > 12) 337 i = 1, tp = "?"; 338 else 339 i = strlen(tp = months_l[vtm->mon-1]); 340 break; 341 342 case 'c': /* appropriate date and time representation */ 343 STRFTIME("%a %b %e %H:%M:%S %Y"); 344 continue; 345 346 case 'd': /* day of the month, 01 - 31 */ 347 i = range(1, vtm->mday, 31); 348 FMT('0', 2, "d", (int)i); 349 continue; 350 351 case 'H': /* hour, 24-hour clock, 00 - 23 */ 352 i = range(0, vtm->hour, 23); 353 FMT('0', 2, "d", (int)i); 354 continue; 355 356 case 'I': /* hour, 12-hour clock, 01 - 12 */ 357 i = range(0, vtm->hour, 23); 358 if (i == 0) 359 i = 12; 360 else if (i > 12) 361 i -= 12; 362 FMT('0', 2, "d", (int)i); 363 continue; 364 365 case 'j': /* day of the year, 001 - 366 */ 366 FMT('0', 3, "d", vtm->yday); 367 continue; 368 369 case 'm': /* month, 01 - 12 */ 370 i = range(1, vtm->mon, 12); 371 FMT('0', 2, "d", (int)i); 372 continue; 373 374 case 'M': /* minute, 00 - 59 */ 375 i = range(0, vtm->min, 59); 376 FMT('0', 2, "d", (int)i); 377 continue; 378 379 case 'p': /* AM or PM based on 12-hour clock */ 380 case 'P': /* am or pm based on 12-hour clock */ 381 if ((*format == 'p' && (flags & BIT_OF(CHCASE))) || 382 (*format == 'P' && !(flags & (BIT_OF(CHCASE)|BIT_OF(UPPER))))) { 383 flags &= ~(BIT_OF(UPPER)|BIT_OF(CHCASE)); 384 flags |= BIT_OF(LOWER); 385 } 386 i = range(0, vtm->hour, 23); 387 if (i < 12) 388 tp = ampm[0]; 389 else 390 tp = ampm[1]; 391 i = 2; 392 break; 393 394 case 's': 395 if (ts) { 396 time_t sec = ts->tv_sec; 397 if (~(time_t)0 <= 0) 398 FMT('0', 1, PRI_TIMET_PREFIX"d", sec); 399 else 400 FMT('0', 1, PRI_TIMET_PREFIX"u", sec); 401 } 402 else { 403 VALUE sec = div(timev, INT2FIX(1)); 404 FMTV('0', 1, "d", sec); 405 } 406 continue; 407 408 case 'S': /* second, 00 - 60 */ 409 i = range(0, vtm->sec, 60); 410 FMT('0', 2, "d", (int)i); 411 continue; 412 413 case 'U': /* week of year, Sunday is first day of week */ 414 FMT('0', 2, "d", weeknumber_v(vtm, 0)); 415 continue; 416 417 case 'w': /* weekday, Sunday == 0, 0 - 6 */ 418 i = range(0, vtm->wday, 6); 419 FMT('0', 1, "d", (int)i); 420 continue; 421 422 case 'W': /* week of year, Monday is first day of week */ 423 FMT('0', 2, "d", weeknumber_v(vtm, 1)); 424 continue; 425 426 case 'x': /* appropriate date representation */ 427 STRFTIME("%m/%d/%y"); 428 continue; 429 430 case 'X': /* appropriate time representation */ 431 STRFTIME("%H:%M:%S"); 432 continue; 433 434 case 'y': /* year without a century, 00 - 99 */ 435 i = NUM2INT(mod(vtm->year, INT2FIX(100))); 436 FMT('0', 2, "d", (int)i); 437 continue; 438 439 case 'Y': /* year with century */ 440 if (FIXNUM_P(vtm->year)) { 441 long y = FIX2LONG(vtm->year); 442 FMT('0', 0 <= y ? 4 : 5, "ld", y); 443 } 444 else { 445 FMTV('0', 4, "d", vtm->year); 446 } 447 continue; 448 449#ifdef MAILHEADER_EXT 450 case 'z': /* time zone offset east of GMT e.g. -0600 */ 451 if (gmt) { 452 off = 0; 453 } 454 else { 455 off = NUM2LONG(rb_funcall(vtm->utc_offset, rb_intern("round"), 0)); 456 } 457 if (off < 0) { 458 off = -off; 459 sign = -1; 460 } else { 461 sign = +1; 462 } 463 switch (colons) { 464 case 0: /* %z -> +hhmm */ 465 precision = precision <= 5 ? 2 : precision-3; 466 NEEDS(precision + 3); 467 break; 468 469 case 1: /* %:z -> +hh:mm */ 470 precision = precision <= 6 ? 2 : precision-4; 471 NEEDS(precision + 4); 472 break; 473 474 case 2: /* %::z -> +hh:mm:ss */ 475 precision = precision <= 9 ? 2 : precision-7; 476 NEEDS(precision + 7); 477 break; 478 479 case 3: /* %:::z -> +hh[:mm[:ss]] */ 480 if (off % 3600 == 0) { 481 precision = precision <= 3 ? 2 : precision-1; 482 NEEDS(precision + 3); 483 } 484 else if (off % 60 == 0) { 485 precision = precision <= 6 ? 2 : precision-4; 486 NEEDS(precision + 4); 487 } 488 else { 489 precision = precision <= 9 ? 2 : precision-7; 490 NEEDS(precision + 9); 491 } 492 break; 493 494 default: 495 format--; 496 goto unknown; 497 } 498 i = snprintf(s, endp - s, (padding == ' ' ? "%+*ld" : "%+.*ld"), 499 precision + 1, sign * (off / 3600)); 500 if (i < 0) goto err; 501 if (sign < 0 && off < 3600) { 502 *(padding == ' ' ? s + i - 2 : s) = '-'; 503 } 504 s += i; 505 off = off % 3600; 506 if (colons == 3 && off == 0) 507 continue; 508 if (1 <= colons) 509 *s++ = ':'; 510 i = snprintf(s, endp - s, "%02d", (int)(off / 60)); 511 if (i < 0) goto err; 512 s += i; 513 off = off % 60; 514 if (colons == 3 && off == 0) 515 continue; 516 if (2 <= colons) { 517 *s++ = ':'; 518 i = snprintf(s, endp - s, "%02d", (int)off); 519 if (i < 0) goto err; 520 s += i; 521 } 522 continue; 523#endif /* MAILHEADER_EXT */ 524 525 case 'Z': /* time zone name or abbreviation */ 526 if (flags & BIT_OF(CHCASE)) { 527 flags &= ~(BIT_OF(UPPER)|BIT_OF(CHCASE)); 528 flags |= BIT_OF(LOWER); 529 } 530 if (gmt) { 531 i = 3; 532 tp = "UTC"; 533 break; 534 } 535 if (vtm->zone == NULL) { 536 i = 0; 537 } 538 else { 539 tp = vtm->zone; 540 if (enc) { 541 for (i = 0; i < TBUFSIZE && tp[i]; i++) { 542 if ((unsigned char)tp[i] > 0x7F) { 543 VALUE str = rb_str_conv_enc_opts(rb_str_new_cstr(tp), rb_locale_encoding(), enc, ECONV_UNDEF_REPLACE|ECONV_INVALID_REPLACE, Qnil); 544 i = strlcpy(tbuf, RSTRING_PTR(str), TBUFSIZE); 545 tp = tbuf; 546 break; 547 } 548 } 549 } 550 else 551 i = strlen(tp); 552 } 553 break; 554 555#ifdef SYSV_EXT 556 case 'n': /* same as \n */ 557 FILL_PADDING(1); 558 *s++ = '\n'; 559 continue; 560 561 case 't': /* same as \t */ 562 FILL_PADDING(1); 563 *s++ = '\t'; 564 continue; 565 566 case 'D': /* date as %m/%d/%y */ 567 STRFTIME("%m/%d/%y"); 568 continue; 569 570 case 'e': /* day of month, blank padded */ 571 FMT(' ', 2, "d", range(1, vtm->mday, 31)); 572 continue; 573 574 case 'r': /* time as %I:%M:%S %p */ 575 STRFTIME("%I:%M:%S %p"); 576 continue; 577 578 case 'R': /* time as %H:%M */ 579 STRFTIME("%H:%M"); 580 continue; 581 582 case 'T': /* time as %H:%M:%S */ 583 STRFTIME("%H:%M:%S"); 584 continue; 585#endif 586 587#ifdef SUNOS_EXT 588 case 'k': /* hour, 24-hour clock, blank pad */ 589 i = range(0, vtm->hour, 23); 590 FMT(' ', 2, "d", (int)i); 591 continue; 592 593 case 'l': /* hour, 12-hour clock, 1 - 12, blank pad */ 594 i = range(0, vtm->hour, 23); 595 if (i == 0) 596 i = 12; 597 else if (i > 12) 598 i -= 12; 599 FMT(' ', 2, "d", (int)i); 600 continue; 601#endif 602 603 604#ifdef VMS_EXT 605 case 'v': /* date as dd-bbb-YYYY */ 606 STRFTIME("%e-%^b-%4Y"); 607 continue; 608#endif 609 610 611#ifdef POSIX2_DATE 612 case 'C': 613 FMTV('0', 2, "d", div(vtm->year, INT2FIX(100))); 614 continue; 615 616 case 'E': 617 /* POSIX locale extensions, ignored for now */ 618 if (!format[1] || !strchr("cCxXyY", format[1])) 619 goto unknown; 620 goto again; 621 case 'O': 622 /* POSIX locale extensions, ignored for now */ 623 if (!format[1] || !strchr("deHkIlmMSuUVwWy", format[1])) 624 goto unknown; 625 goto again; 626 627 case 'V': /* week of year according ISO 8601 */ 628 FMT('0', 2, "d", iso8601wknum_v(vtm)); 629 continue; 630 631 case 'u': 632 /* ISO 8601: Weekday as a decimal number [1 (Monday) - 7] */ 633 FMT('0', 1, "d", vtm->wday == 0 ? 7 : vtm->wday); 634 continue; 635#endif /* POSIX2_DATE */ 636 637#ifdef ISO_DATE_EXT 638 case 'G': 639 case 'g': 640 /* 641 * Year of ISO week. 642 * 643 * If it's December but the ISO week number is one, 644 * that week is in next year. 645 * If it's January but the ISO week number is 52 or 646 * 53, that week is in last year. 647 * Otherwise, it's this year. 648 */ 649 { 650 VALUE yv = vtm->year; 651 w = iso8601wknum_v(vtm); 652 if (vtm->mon == 12 && w == 1) 653 yv = add(yv, INT2FIX(1)); 654 else if (vtm->mon == 1 && w >= 52) 655 yv = sub(yv, INT2FIX(1)); 656 657 if (*format == 'G') { 658 if (FIXNUM_P(yv)) { 659 const long y = FIX2LONG(yv); 660 FMT('0', 0 <= y ? 4 : 5, "ld", y); 661 } 662 else { 663 FMTV('0', 4, "d", yv); 664 } 665 } 666 else { 667 yv = mod(yv, INT2FIX(100)); 668 y = FIX2LONG(yv); 669 FMT('0', 2, "ld", y); 670 } 671 continue; 672 } 673 674#endif /* ISO_DATE_EXT */ 675 676 677 case 'L': 678 w = 3; 679 goto subsec; 680 681 case 'N': 682 /* 683 * fractional second digits. default is 9 digits 684 * (nanosecond). 685 * 686 * %3N millisecond (3 digits) 687 * %6N microsecond (6 digits) 688 * %9N nanosecond (9 digits) 689 */ 690 w = 9; 691 subsec: 692 if (precision <= 0) { 693 precision = w; 694 } 695 NEEDS(precision); 696 697 if (ts) { 698 long subsec = ts->tv_nsec; 699 if (9 < precision) { 700 snprintf(s, endp - s, "%09ld", subsec); 701 memset(s+9, '0', precision-9); 702 s += precision; 703 } 704 else { 705 int i; 706 for (i = 0; i < 9-precision; i++) 707 subsec /= 10; 708 snprintf(s, endp - s, "%0*ld", precision, subsec); 709 s += precision; 710 } 711 } 712 else { 713 VALUE subsec = mod(timev, INT2FIX(1)); 714 int ww; 715 long n; 716 717 ww = precision; 718 while (9 <= ww) { 719 subsec = mul(subsec, INT2FIX(1000000000)); 720 ww -= 9; 721 } 722 n = 1; 723 for (; 0 < ww; ww--) 724 n *= 10; 725 if (n != 1) 726 subsec = mul(subsec, INT2FIX(n)); 727 subsec = div(subsec, INT2FIX(1)); 728 729 if (FIXNUM_P(subsec)) { 730 (void)snprintf(s, endp - s, "%0*ld", precision, FIX2LONG(subsec)); 731 s += precision; 732 } 733 else { 734 VALUE args[2], result; 735 args[0] = INT2FIX(precision); 736 args[1] = subsec; 737 result = rb_str_format(2, args, rb_str_new2("%0*d")); 738 (void)strlcpy(s, StringValueCStr(result), endp-s); 739 s += precision; 740 } 741 } 742 continue; 743 744 case 'F': /* Equivalent to %Y-%m-%d */ 745 STRFTIME("%Y-%m-%d"); 746 continue; 747 748 case '-': 749 FLAG_FOUND(); 750 flags |= BIT_OF(LEFT); 751 padding = precision = 0; 752 goto again; 753 754 case '^': 755 FLAG_FOUND(); 756 flags |= BIT_OF(UPPER); 757 goto again; 758 759 case '#': 760 FLAG_FOUND(); 761 flags |= BIT_OF(CHCASE); 762 goto again; 763 764 case '_': 765 FLAG_FOUND(); 766 padding = ' '; 767 goto again; 768 769 case ':': 770 { 771 size_t l = strspn(format, ":"); 772 if (l > 3 || format[l] != 'z') goto unknown; 773 colons = (int)l; 774 format += l - 1; 775 } 776 goto again; 777 778 case '0': 779 padding = '0'; 780 case '1': case '2': case '3': case '4': 781 case '5': case '6': case '7': case '8': case '9': 782 { 783 char *e; 784 precision = (int)strtoul(format, &e, 10); 785 format = e - 1; 786 goto again; 787 } 788 789 default: 790 unknown: 791 i = format - sp + 1; 792 tp = sp; 793 precision = -1; 794 flags = 0; 795 padding = 0; 796 colons = 0; 797 break; 798 } 799 if (i) { 800 FILL_PADDING(i); 801 memcpy(s, tp, i); 802 switch (flags & (BIT_OF(UPPER)|BIT_OF(LOWER))) { 803 case BIT_OF(UPPER): 804 do { 805 if (ISLOWER(*s)) *s = TOUPPER(*s); 806 } while (s++, --i); 807 break; 808 case BIT_OF(LOWER): 809 do { 810 if (ISUPPER(*s)) *s = TOLOWER(*s); 811 } while (s++, --i); 812 break; 813 default: 814 s += i; 815 break; 816 } 817 } 818 } 819 if (s >= endp) { 820 goto err; 821 } 822 if (*format == '\0') { 823 *s = '\0'; 824 return (s - start); 825 } else 826 return 0; 827} 828 829size_t 830rb_strftime(char *s, size_t maxsize, const char *format, rb_encoding *enc, const struct vtm *vtm, VALUE timev, int gmt) 831{ 832 return rb_strftime_with_timespec(s, maxsize, format, enc, vtm, timev, NULL, gmt); 833} 834 835size_t 836rb_strftime_timespec(char *s, size_t maxsize, const char *format, rb_encoding *enc, const struct vtm *vtm, struct timespec *ts, int gmt) 837{ 838 return rb_strftime_with_timespec(s, maxsize, format, enc, vtm, Qnil, ts, gmt); 839} 840 841/* isleap --- is a year a leap year? */ 842 843static int 844isleap(long year) 845{ 846 return ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0); 847} 848 849 850static void 851vtm2tm_noyear(const struct vtm *vtm, struct tm *result) 852{ 853 struct tm tm; 854 855 /* for isleap() in iso8601wknum. +100 is -1900 (mod 400). */ 856 tm.tm_year = FIX2INT(mod(vtm->year, INT2FIX(400))) + 100; 857 858 tm.tm_mon = vtm->mon-1; 859 tm.tm_mday = vtm->mday; 860 tm.tm_hour = vtm->hour; 861 tm.tm_min = vtm->min; 862 tm.tm_sec = vtm->sec; 863 tm.tm_wday = vtm->wday; 864 tm.tm_yday = vtm->yday-1; 865 tm.tm_isdst = vtm->isdst; 866#if defined(HAVE_STRUCT_TM_TM_GMTOFF) 867 tm.tm_gmtoff = NUM2LONG(vtm->utc_offset); 868#endif 869#if defined(HAVE_TM_ZONE) 870 tm.tm_zone = (char *)vtm->zone; 871#endif 872 *result = tm; 873} 874 875#ifdef POSIX2_DATE 876/* iso8601wknum --- compute week number according to ISO 8601 */ 877 878static int 879iso8601wknum(const struct tm *timeptr) 880{ 881 /* 882 * From 1003.2: 883 * If the week (Monday to Sunday) containing January 1 884 * has four or more days in the new year, then it is week 1; 885 * otherwise it is the highest numbered week of the previous 886 * year (52 or 53), and the next week is week 1. 887 * 888 * ADR: This means if Jan 1 was Monday through Thursday, 889 * it was week 1, otherwise week 52 or 53. 890 * 891 * XPG4 erroneously included POSIX.2 rationale text in the 892 * main body of the standard. Thus it requires week 53. 893 */ 894 895 int weeknum, jan1day; 896 897 /* get week number, Monday as first day of the week */ 898 weeknum = weeknumber(timeptr, 1); 899 900 /* 901 * With thanks and tip of the hatlo to tml@tik.vtt.fi 902 * 903 * What day of the week does January 1 fall on? 904 * We know that 905 * (timeptr->tm_yday - jan1.tm_yday) MOD 7 == 906 * (timeptr->tm_wday - jan1.tm_wday) MOD 7 907 * and that 908 * jan1.tm_yday == 0 909 * and that 910 * timeptr->tm_wday MOD 7 == timeptr->tm_wday 911 * from which it follows that. . . 912 */ 913 jan1day = timeptr->tm_wday - (timeptr->tm_yday % 7); 914 if (jan1day < 0) 915 jan1day += 7; 916 917 /* 918 * If Jan 1 was a Monday through Thursday, it was in 919 * week 1. Otherwise it was last year's highest week, which is 920 * this year's week 0. 921 * 922 * What does that mean? 923 * If Jan 1 was Monday, the week number is exactly right, it can 924 * never be 0. 925 * If it was Tuesday through Thursday, the weeknumber is one 926 * less than it should be, so we add one. 927 * Otherwise, Friday, Saturday or Sunday, the week number is 928 * OK, but if it is 0, it needs to be 52 or 53. 929 */ 930 switch (jan1day) { 931 case 1: /* Monday */ 932 break; 933 case 2: /* Tuesday */ 934 case 3: /* Wednesday */ 935 case 4: /* Thursday */ 936 weeknum++; 937 break; 938 case 5: /* Friday */ 939 case 6: /* Saturday */ 940 case 0: /* Sunday */ 941 if (weeknum == 0) { 942#ifdef USE_BROKEN_XPG4 943 /* XPG4 (as of March 1994) says 53 unconditionally */ 944 weeknum = 53; 945#else 946 /* get week number of last week of last year */ 947 struct tm dec31ly; /* 12/31 last year */ 948 dec31ly = *timeptr; 949 dec31ly.tm_year--; 950 dec31ly.tm_mon = 11; 951 dec31ly.tm_mday = 31; 952 dec31ly.tm_wday = (jan1day == 0) ? 6 : jan1day - 1; 953 dec31ly.tm_yday = 364 + isleap(dec31ly.tm_year + 1900L); 954 weeknum = iso8601wknum(& dec31ly); 955#endif 956 } 957 break; 958 } 959 960 if (timeptr->tm_mon == 11) { 961 /* 962 * The last week of the year 963 * can be in week 1 of next year. 964 * Sigh. 965 * 966 * This can only happen if 967 * M T W 968 * 29 30 31 969 * 30 31 970 * 31 971 */ 972 int wday, mday; 973 974 wday = timeptr->tm_wday; 975 mday = timeptr->tm_mday; 976 if ( (wday == 1 && (mday >= 29 && mday <= 31)) 977 || (wday == 2 && (mday == 30 || mday == 31)) 978 || (wday == 3 && mday == 31)) 979 weeknum = 1; 980 } 981 982 return weeknum; 983} 984 985static int 986iso8601wknum_v(const struct vtm *vtm) 987{ 988 struct tm tm; 989 vtm2tm_noyear(vtm, &tm); 990 return iso8601wknum(&tm); 991} 992 993#endif 994 995/* weeknumber --- figure how many weeks into the year */ 996 997/* With thanks and tip of the hatlo to ado@elsie.nci.nih.gov */ 998 999static int 1000weeknumber(const struct tm *timeptr, int firstweekday) 1001{ 1002 int wday = timeptr->tm_wday; 1003 int ret; 1004 1005 if (firstweekday == 1) { 1006 if (wday == 0) /* sunday */ 1007 wday = 6; 1008 else 1009 wday--; 1010 } 1011 ret = ((timeptr->tm_yday + 7 - wday) / 7); 1012 if (ret < 0) 1013 ret = 0; 1014 return ret; 1015} 1016 1017static int 1018weeknumber_v(const struct vtm *vtm, int firstweekday) 1019{ 1020 struct tm tm; 1021 vtm2tm_noyear(vtm, &tm); 1022 return weeknumber(&tm, firstweekday); 1023} 1024 1025#if 0 1026/* ADR --- I'm loathe to mess with ado's code ... */ 1027 1028Date: Wed, 24 Apr 91 20:54:08 MDT 1029From: Michal Jaegermann <audfax!emory!vm.ucs.UAlberta.CA!NTOMCZAK> 1030To: arnold@audiofax.com 1031 1032Hi Arnold, 1033in a process of fixing of strftime() in libraries on Atari ST I grabbed 1034some pieces of code from your own strftime. When doing that it came 1035to mind that your weeknumber() function compiles a little bit nicer 1036in the following form: 1037/* 1038 * firstweekday is 0 if starting in Sunday, non-zero if in Monday 1039 */ 1040{ 1041 return (timeptr->tm_yday - timeptr->tm_wday + 1042 (firstweekday ? (timeptr->tm_wday ? 8 : 1) : 7)) / 7; 1043} 1044How nicer it depends on a compiler, of course, but always a tiny bit. 1045 1046 Cheers, 1047 Michal 1048 ntomczak@vm.ucs.ualberta.ca 1049#endif 1050 1051#ifdef TEST_STRFTIME 1052 1053/* 1054 * NAME: 1055 * tst 1056 * 1057 * SYNOPSIS: 1058 * tst 1059 * 1060 * DESCRIPTION: 1061 * "tst" is a test driver for the function "strftime". 1062 * 1063 * OPTIONS: 1064 * None. 1065 * 1066 * AUTHOR: 1067 * Karl Vogel 1068 * Control Data Systems, Inc. 1069 * vogelke@c-17igp.wpafb.af.mil 1070 * 1071 * BUGS: 1072 * None noticed yet. 1073 * 1074 * COMPILE: 1075 * cc -o tst -DTEST_STRFTIME strftime.c 1076 */ 1077 1078/* ADR: I reformatted this to my liking, and deleted some unneeded code. */ 1079 1080#ifndef NULL 1081#include <stdio.h> 1082#endif 1083#include <sys/time.h> 1084#include <string.h> 1085 1086#define MAXTIME 132 1087 1088/* 1089 * Array of time formats. 1090 */ 1091 1092static char *array[] = 1093{ 1094 "(%%A) full weekday name, var length (Sunday..Saturday) %A", 1095 "(%%B) full month name, var length (January..December) %B", 1096 "(%%C) Century %C", 1097 "(%%D) date (%%m/%%d/%%y) %D", 1098 "(%%E) Locale extensions (ignored) %E", 1099 "(%%H) hour (24-hour clock, 00..23) %H", 1100 "(%%I) hour (12-hour clock, 01..12) %I", 1101 "(%%M) minute (00..59) %M", 1102 "(%%O) Locale extensions (ignored) %O", 1103 "(%%R) time, 24-hour (%%H:%%M) %R", 1104 "(%%S) second (00..60) %S", 1105 "(%%T) time, 24-hour (%%H:%%M:%%S) %T", 1106 "(%%U) week of year, Sunday as first day of week (00..53) %U", 1107 "(%%V) week of year according to ISO 8601 %V", 1108 "(%%W) week of year, Monday as first day of week (00..53) %W", 1109 "(%%X) appropriate locale time representation (%H:%M:%S) %X", 1110 "(%%Y) year with century (1970...) %Y", 1111 "(%%Z) timezone (EDT), or blank if timezone not determinable %Z", 1112 "(%%a) locale's abbreviated weekday name (Sun..Sat) %a", 1113 "(%%b) locale's abbreviated month name (Jan..Dec) %b", 1114 "(%%c) full date (Sat Nov 4 12:02:33 1989)%n%t%t%t %c", 1115 "(%%d) day of the month (01..31) %d", 1116 "(%%e) day of the month, blank-padded ( 1..31) %e", 1117 "(%%h) should be same as (%%b) %h", 1118 "(%%j) day of the year (001..366) %j", 1119 "(%%k) hour, 24-hour clock, blank pad ( 0..23) %k", 1120 "(%%l) hour, 12-hour clock, blank pad ( 1..12) %l", 1121 "(%%m) month (01..12) %m", 1122 "(%%p) locale's AM or PM based on 12-hour clock %p", 1123 "(%%r) time, 12-hour (same as %%I:%%M:%%S %%p) %r", 1124 "(%%u) ISO 8601: Weekday as decimal number [1 (Monday) - 7] %u", 1125 "(%%v) VMS date (dd-bbb-YYYY) %v", 1126 "(%%w) day of week (0..6, Sunday == 0) %w", 1127 "(%%x) appropriate locale date representation %x", 1128 "(%%y) last two digits of year (00..99) %y", 1129 "(%%z) timezone offset east of GMT as HHMM (e.g. -0500) %z", 1130 (char *) NULL 1131}; 1132 1133/* main routine. */ 1134 1135int 1136main(int argc, char **argv) 1137{ 1138 long time(); 1139 1140 char *next; 1141 char string[MAXTIME]; 1142 1143 int k; 1144 int length; 1145 1146 struct tm *tm; 1147 1148 long clock; 1149 1150 /* Call the function. */ 1151 1152 clock = time((long *) 0); 1153 tm = localtime(&clock); 1154 1155 for (k = 0; next = array[k]; k++) { 1156 length = strftime(string, MAXTIME, next, tm); 1157 printf("%s\n", string); 1158 } 1159 1160 exit(0); 1161} 1162#endif /* TEST_STRFTIME */ 1163