missing/strftime.c


DEFINITIONS

This source file includes following functions.
  1. adddecl
  2. max
  3. strftime
  4. isleap
  5. iso8601wknum
  6. weeknumber
  7. strftime
  8. main


   1  /*
   2   * strftime.c
   3   *
   4   * Public-domain implementation of ANSI C library routine.
   5   *
   6   * It's written in old-style C for maximal portability.
   7   * However, since I'm used to prototypes, I've included them too.
   8   *
   9   * If you want stuff in the System V ascftime routine, add the SYSV_EXT define.
  10   * For extensions from SunOS, add SUNOS_EXT.
  11   * For stuff needed to implement the P1003.2 date command, add POSIX2_DATE.
  12   * For VMS dates, add VMS_EXT.
  13   * For a an RFC822 time format, add MAILHEADER_EXT.
  14   * For ISO week years, add ISO_DATE_EXT.
  15   * For complete POSIX semantics, add POSIX_SEMANTICS.
  16   *
  17   * The code for %c, %x, and %X now follows the 1003.2 specification for
  18   * the POSIX locale.
  19   * This version ignores LOCALE information.
  20   * It also doesn't worry about multi-byte characters.
  21   * So there.
  22   *
  23   * This file is also shipped with GAWK (GNU Awk), gawk specific bits of
  24   * code are included if GAWK is defined.
  25   *
  26   * Arnold Robbins
  27   * January, February, March, 1991
  28   * Updated March, April 1992
  29   * Updated April, 1993
  30   * Updated February, 1994
  31   * Updated May, 1994
  32   * Updated January, 1995
  33   * Updated September, 1995
  34   * Updated January, 1996
  35   *
  36   * Fixes from ado@elsie.nci.nih.gov
  37   * February 1991, May 1992
  38   * Fixes from Tor Lillqvist tml@tik.vtt.fi
  39   * May, 1993
  40   * Further fixes from ado@elsie.nci.nih.gov
  41   * February 1994
  42   * %z code from chip@chinacat.unicom.com
  43   * Applied September 1995
  44   * %V code fixed (again) and %G, %g added,
  45   * January 1996
  46   */
  47  
  48  #include "config.h"
  49  
  50  #ifndef GAWK
  51  #include <stdio.h>
  52  #include <ctype.h>
  53  #include <string.h>
  54  #include <time.h>
  55  #endif
  56  #if defined(TM_IN_SYS_TIME) || ! defined(GAWK)
  57  #include <sys/types.h>
  58  #include <sys/time.h>
  59  #endif
  60  
  61  /* defaults: season to taste */
  62  #define SYSV_EXT        1       /* stuff in System V ascftime routine */
  63  #define SUNOS_EXT       1       /* stuff in SunOS strftime routine */
  64  #define POSIX2_DATE     1       /* stuff in Posix 1003.2 date command */
  65  #define VMS_EXT         1       /* include %v for VMS date format */
  66  #define MAILHEADER_EXT  1       /* add %z for HHMM format */
  67  #define ISO_DATE_EXT    1       /* %G and %g for year of ISO week */
  68  #ifndef GAWK
  69  #define POSIX_SEMANTICS 1       /* call tzset() if TZ changes */
  70  #endif
  71  
  72  #if defined(ISO_DATE_EXT)
  73  #if ! defined(POSIX2_DATE)
  74  #define POSIX2_DATE     1
  75  #endif
  76  #endif
  77  
  78  #if defined(POSIX2_DATE)
  79  #if ! defined(SYSV_EXT)
  80  #define SYSV_EXT        1
  81  #endif
  82  #if ! defined(SUNOS_EXT)
  83  #define SUNOS_EXT       1
  84  #endif
  85  #endif
  86  
  87  #if defined(POSIX2_DATE)
  88  #define adddecl(stuff)  stuff
  89  #else
  90  #define adddecl(stuff)
  91  #endif
  92  
  93  #undef strchr   /* avoid AIX weirdness */
  94  
  95  #ifndef __STDC__
  96  #define const   /**/
  97  extern void tzset();
  98  static int weeknumber();
  99  adddecl(static int iso8601wknum();)
 100  #else
 101  extern void tzset(void);
 102  static int weeknumber(const struct tm *timeptr, int firstweekday);
 103  adddecl(static int iso8601wknum(const struct tm *timeptr);)
 104  #endif
 105  
 106  #ifdef STDC_HEADERS
 107  #include <stdlib.h>
 108  #include <string.h>
 109  #else
 110  extern void *malloc();
 111  extern void *realloc();
 112  extern char *getenv();
 113  extern char *strchr();
 114  #endif
 115  
 116  #define range(low, item, hi)    max(low, min(item, hi))
 117  
 118  #if !defined(OS2) && !defined(MSDOS) && defined(HAVE_TZNAME)
 119  extern char *tzname[2];
 120  extern int daylight;
 121  #ifdef SOLARIS
 122  extern long timezone, altzone;
 123  #else
 124  extern int timezone, altzone;
 125  #endif
 126  #endif
 127  
 128  #undef min      /* just in case */
 129  
 130  /* min --- return minimum of two numbers */
 131  
 132  #ifndef __STDC__
 133  static inline int
 134  min(a, b)
 135  int a, b;
 136  #else
 137  static inline int
 138  min(int a, int b)
 139  #endif
 140  {
 141          return (a < b ? a : b);
 142  }
 143  
 144  #undef max      /* also, just in case */
 145  
 146  /* max --- return maximum of two numbers */
 147  
 148  #ifndef __STDC__
 149  static inline int
 150  max(a, b)
 151  int a, b;
 152  #else
 153  static inline int
 154  max(int a, int b)
 155  #endif
 156  {
 157          return (a > b ? a : b);
 158  }
 159  
 160  /* strftime --- produce formatted time */
 161  
 162  #ifndef __STDC__
 163  size_t
 164  strftime(s, maxsize, format, timeptr)
 165  char *s;
 166  size_t maxsize;
 167  const char *format;
 168  const struct tm *timeptr;
 169  #else
 170  size_t
 171  strftime(char *s, size_t maxsize, const char *format, const struct tm *timeptr)
 172  #endif
 173  {
 174          char *endp = s + maxsize;
 175          char *start = s;
 176          auto char tbuf[100];
 177          long off;
 178          int i, w;
 179          long y;
 180          static short first = 1;
 181  #ifdef POSIX_SEMANTICS
 182          static char *savetz = NULL;
 183          static int savetzlen = 0;
 184          char *tz;
 185  #endif /* POSIX_SEMANTICS */
 186  #ifndef HAVE_TM_ZONE
 187  #ifndef HAVE_TM_NAME
 188  #ifndef HAVE_TZNAME
 189          extern char *timezone();
 190          struct timeval tv;
 191          struct timezone zone;
 192  #endif /* HAVE_TZNAME */
 193  #endif /* HAVE_TM_NAME */
 194  #endif /* HAVE_TM_ZONE */
 195  
 196          /* various tables, useful in North America */
 197          static const char *days_a[] = {
 198                  "Sun", "Mon", "Tue", "Wed",
 199                  "Thu", "Fri", "Sat",
 200          };
 201          static const char *days_l[] = {
 202                  "Sunday", "Monday", "Tuesday", "Wednesday",
 203                  "Thursday", "Friday", "Saturday",
 204          };
 205          static const char *months_a[] = {
 206                  "Jan", "Feb", "Mar", "Apr", "May", "Jun",
 207                  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
 208          };
 209          static const char *months_l[] = {
 210                  "January", "February", "March", "April",
 211                  "May", "June", "July", "August", "September",
 212                  "October", "November", "December",
 213          };
 214          static const char *ampm[] = { "AM", "PM", };
 215  
 216          if (s == NULL || format == NULL || timeptr == NULL || maxsize == 0)
 217                  return 0;
 218  
 219          /* quick check if we even need to bother */
 220          if (strchr(format, '%') == NULL && strlen(format) + 1 >= maxsize)
 221                  return 0;
 222  
 223  #ifndef POSIX_SEMANTICS
 224          if (first) {
 225                  tzset();
 226                  first = 0;
 227          }
 228  #else   /* POSIX_SEMANTICS */
 229          tz = getenv("TZ");
 230          if (first) {
 231                  if (tz != NULL) {
 232                          int tzlen = strlen(tz);
 233  
 234                          savetz = (char *) malloc(tzlen + 1);
 235                          if (savetz != NULL) {
 236                                  savetzlen = tzlen + 1;
 237                                  strcpy(savetz, tz);
 238                          }
 239                  }
 240                  tzset();
 241                  first = 0;
 242          }
 243          /* if we have a saved TZ, and it is different, recapture and reset */
 244          if (tz && savetz && (tz[0] != savetz[0] || strcmp(tz, savetz) != 0)) {
 245                  i = strlen(tz) + 1;
 246                  if (i > savetzlen) {
 247                          savetz = (char *) realloc(savetz, i);
 248                          if (savetz) {
 249                                  savetzlen = i;
 250                                  strcpy(savetz, tz);
 251                          }
 252                  } else
 253                          strcpy(savetz, tz);
 254                  tzset();
 255          }
 256  #endif  /* POSIX_SEMANTICS */
 257  
 258          for (; *format && s < endp - 1; format++) {
 259                  tbuf[0] = '\0';
 260                  if (*format != '%') {
 261                          *s++ = *format;
 262                          continue;
 263                  }
 264          again:
 265                  switch (*++format) {
 266                  case '\0':
 267                          *s++ = '%';
 268                          goto out;
 269  
 270                  case '%':
 271                          *s++ = '%';
 272                          continue;
 273  
 274                  case 'a':       /* abbreviated weekday name */
 275                          if (timeptr->tm_wday < 0 || timeptr->tm_wday > 6)
 276                                  strcpy(tbuf, "?");
 277                          else
 278                                  strcpy(tbuf, days_a[timeptr->tm_wday]);
 279                          break;
 280  
 281                  case 'A':       /* full weekday name */
 282                          if (timeptr->tm_wday < 0 || timeptr->tm_wday > 6)
 283                                  strcpy(tbuf, "?");
 284                          else
 285                                  strcpy(tbuf, days_l[timeptr->tm_wday]);
 286                          break;
 287  
 288  #ifdef SYSV_EXT
 289                  case 'h':       /* abbreviated month name */
 290  #endif
 291                  case 'b':       /* abbreviated month name */
 292                          if (timeptr->tm_mon < 0 || timeptr->tm_mon > 11)
 293                                  strcpy(tbuf, "?");
 294                          else
 295                                  strcpy(tbuf, months_a[timeptr->tm_mon]);
 296                          break;
 297  
 298                  case 'B':       /* full month name */
 299                          if (timeptr->tm_mon < 0 || timeptr->tm_mon > 11)
 300                                  strcpy(tbuf, "?");
 301                          else
 302                                  strcpy(tbuf, months_l[timeptr->tm_mon]);
 303                          break;
 304  
 305                  case 'c':       /* appropriate date and time representation */
 306                          strftime(tbuf, sizeof tbuf, "%a %b %e %H:%M:%S %Y", timeptr);
 307                          break;
 308  
 309                  case 'd':       /* day of the month, 01 - 31 */
 310                          i = range(1, timeptr->tm_mday, 31);
 311                          sprintf(tbuf, "%02d", i);
 312                          break;
 313  
 314                  case 'H':       /* hour, 24-hour clock, 00 - 23 */
 315                          i = range(0, timeptr->tm_hour, 23);
 316                          sprintf(tbuf, "%02d", i);
 317                          break;
 318  
 319                  case 'I':       /* hour, 12-hour clock, 01 - 12 */
 320                          i = range(0, timeptr->tm_hour, 23);
 321                          if (i == 0)
 322                                  i = 12;
 323                          else if (i > 12)
 324                                  i -= 12;
 325                          sprintf(tbuf, "%02d", i);
 326                          break;
 327  
 328                  case 'j':       /* day of the year, 001 - 366 */
 329                          sprintf(tbuf, "%03d", timeptr->tm_yday + 1);
 330                          break;
 331  
 332                  case 'm':       /* month, 01 - 12 */
 333                          i = range(0, timeptr->tm_mon, 11);
 334                          sprintf(tbuf, "%02d", i + 1);
 335                          break;
 336  
 337                  case 'M':       /* minute, 00 - 59 */
 338                          i = range(0, timeptr->tm_min, 59);
 339                          sprintf(tbuf, "%02d", i);
 340                          break;
 341  
 342                  case 'p':       /* am or pm based on 12-hour clock */
 343                          i = range(0, timeptr->tm_hour, 23);
 344                          if (i < 12)
 345                                  strcpy(tbuf, ampm[0]);
 346                          else
 347                                  strcpy(tbuf, ampm[1]);
 348                          break;
 349  
 350                  case 'S':       /* second, 00 - 60 */
 351                          i = range(0, timeptr->tm_sec, 60);
 352                          sprintf(tbuf, "%02d", i);
 353                          break;
 354  
 355                  case 'U':       /* week of year, Sunday is first day of week */
 356                          sprintf(tbuf, "%02d", weeknumber(timeptr, 0));
 357                          break;
 358  
 359                  case 'w':       /* weekday, Sunday == 0, 0 - 6 */
 360                          i = range(0, timeptr->tm_wday, 6);
 361                          sprintf(tbuf, "%d", i);
 362                          break;
 363  
 364                  case 'W':       /* week of year, Monday is first day of week */
 365                          sprintf(tbuf, "%02d", weeknumber(timeptr, 1));
 366                          break;
 367  
 368                  case 'x':       /* appropriate date representation */
 369                          strftime(tbuf, sizeof tbuf, "%m/%d/%y", timeptr);
 370                          break;
 371  
 372                  case 'X':       /* appropriate time representation */
 373                          strftime(tbuf, sizeof tbuf, "%H:%M:%S", timeptr);
 374                          break;
 375  
 376                  case 'y':       /* year without a century, 00 - 99 */
 377                          i = timeptr->tm_year % 100;
 378                          sprintf(tbuf, "%02d", i);
 379                          break;
 380  
 381                  case 'Y':       /* year with century */
 382                          sprintf(tbuf, "%ld", 1900L + timeptr->tm_year);
 383                          break;
 384  
 385  #ifdef MAILHEADER_EXT
 386                  /*
 387                   * From: Chip Rosenthal <chip@chinacat.unicom.com>
 388                   * Date: Sun, 19 Mar 1995 00:33:29 -0600 (CST)
 389                   * 
 390                   * Warning: the %z [code] is implemented by inspecting the
 391                   * timezone name conditional compile settings, and
 392                   * inferring a method to get timezone offsets. I've tried
 393                   * this code on a couple of machines, but I don't doubt
 394                   * there is some system out there that won't like it.
 395                   * Maybe the easiest thing to do would be to bracket this
 396                   * with an #ifdef that can turn it off. The %z feature
 397                   * would be an admittedly obscure one that most folks can
 398                   * live without, but it would be a great help to those of
 399                   * us that muck around with various message processors.
 400                   */
 401                  case 'z':       /* time zone offset east of GMT e.g. -0600 */
 402  #ifdef HAVE_TM_NAME
 403                          /*
 404                           * Systems with tm_name probably have tm_tzadj as
 405                           * secs west of GMT.  Convert to mins east of GMT.
 406                           */
 407                          off = -timeptr->tm_tzadj / 60;
 408  #else /* !HAVE_TM_NAME */
 409  #ifdef HAVE_TM_ZONE
 410                          /*
 411                           * Systems with tm_zone probably have tm_gmtoff as
 412                           * secs east of GMT.  Convert to mins east of GMT.
 413                           */
 414                          off = timeptr->tm_gmtoff / 60;
 415  #else /* !HAVE_TM_ZONE */
 416  #if HAVE_TZNAME
 417                          /*
 418                           * Systems with tzname[] probably have timezone as
 419                           * secs west of GMT.  Convert to mins east of GMT.
 420                           */
 421                          off = -(daylight ? timezone : altzone) / 60;
 422  #else /* !HAVE_TZNAME */
 423                          gettimeofday(&tv, &zone);
 424                          off = -zone.tz_minuteswest;
 425  #endif /* !HAVE_TZNAME */
 426  #endif /* !HAVE_TM_ZONE */
 427  #endif /* !HAVE_TM_NAME */
 428                          if (off < 0) {
 429                                  tbuf[0] = '-';
 430                                  off = -off;
 431                          } else {
 432                                  tbuf[0] = '+';
 433                          }
 434                          sprintf(tbuf+1, "%02d%02d", off/60, off%60);
 435                          break;
 436  #endif /* MAILHEADER_EXT */
 437  
 438                  case 'Z':       /* time zone name or abbrevation */
 439  #ifdef HAVE_TZNAME
 440                          i = (daylight && timeptr->tm_isdst > 0); /* 0 or 1 */
 441                          strcpy(tbuf, tzname[i]);
 442  #else
 443  #ifdef HAVE_TM_ZONE
 444                          strcpy(tbuf, timeptr->tm_zone);
 445  #else
 446  #ifdef HAVE_TM_NAME
 447                          strcpy(tbuf, timeptr->tm_name);
 448  #else
 449                          gettimeofday(& tv, & zone);
 450  #ifdef __CYGWIN__
 451                          strcpy(tbuf, timezone());
 452  #else
 453                          strcpy(tbuf, timezone(zone.tz_minuteswest,
 454                                                  timeptr->tm_isdst > 0));
 455  #endif
 456  #endif /* HAVE_TM_NAME */
 457  #endif /* HAVE_TM_ZONE */
 458  #endif /* HAVE_TZNAME */
 459                          break;
 460  
 461  #ifdef SYSV_EXT
 462                  case 'n':       /* same as \n */
 463                          tbuf[0] = '\n';
 464                          tbuf[1] = '\0';
 465                          break;
 466  
 467                  case 't':       /* same as \t */
 468                          tbuf[0] = '\t';
 469                          tbuf[1] = '\0';
 470                          break;
 471  
 472                  case 'D':       /* date as %m/%d/%y */
 473                          strftime(tbuf, sizeof tbuf, "%m/%d/%y", timeptr);
 474                          break;
 475  
 476                  case 'e':       /* day of month, blank padded */
 477                          sprintf(tbuf, "%2d", range(1, timeptr->tm_mday, 31));
 478                          break;
 479  
 480                  case 'r':       /* time as %I:%M:%S %p */
 481                          strftime(tbuf, sizeof tbuf, "%I:%M:%S %p", timeptr);
 482                          break;
 483  
 484                  case 'R':       /* time as %H:%M */
 485                          strftime(tbuf, sizeof tbuf, "%H:%M", timeptr);
 486                          break;
 487  
 488                  case 'T':       /* time as %H:%M:%S */
 489                          strftime(tbuf, sizeof tbuf, "%H:%M:%S", timeptr);
 490                          break;
 491  #endif
 492  
 493  #ifdef SUNOS_EXT
 494                  case 'k':       /* hour, 24-hour clock, blank pad */
 495                          sprintf(tbuf, "%2d", range(0, timeptr->tm_hour, 23));
 496                          break;
 497  
 498                  case 'l':       /* hour, 12-hour clock, 1 - 12, blank pad */
 499                          i = range(0, timeptr->tm_hour, 23);
 500                          if (i == 0)
 501                                  i = 12;
 502                          else if (i > 12)
 503                                  i -= 12;
 504                          sprintf(tbuf, "%2d", i);
 505                          break;
 506  #endif
 507  
 508  
 509  #ifdef VMS_EXT
 510                  case 'v':       /* date as dd-bbb-YYYY */
 511                          sprintf(tbuf, "%2d-%3.3s-%4ld",
 512                                  range(1, timeptr->tm_mday, 31),
 513                                  months_a[range(0, timeptr->tm_mon, 11)],
 514                                  timeptr->tm_year + 1900L);
 515                          for (i = 3; i < 6; i++)
 516                                  if (islower(tbuf[i]))
 517                                          tbuf[i] = toupper(tbuf[i]);
 518                          break;
 519  #endif
 520  
 521  
 522  #ifdef POSIX2_DATE
 523                  case 'C':
 524                          sprintf(tbuf, "%02ld", (timeptr->tm_year + 1900L) / 100);
 525                          break;
 526  
 527  
 528                  case 'E':
 529                  case 'O':
 530                          /* POSIX locale extensions, ignored for now */
 531                          goto again;
 532  
 533                  case 'V':       /* week of year according ISO 8601 */
 534                          sprintf(tbuf, "%02d", iso8601wknum(timeptr));
 535                          break;
 536  
 537                  case 'u':
 538                  /* ISO 8601: Weekday as a decimal number [1 (Monday) - 7] */
 539                          sprintf(tbuf, "%d", timeptr->tm_wday == 0 ? 7 :
 540                                          timeptr->tm_wday);
 541                          break;
 542  #endif  /* POSIX2_DATE */
 543  
 544  #ifdef ISO_DATE_EXT
 545                  case 'G':
 546                  case 'g':
 547                          /*
 548                           * Year of ISO week.
 549                           *
 550                           * If it's December but the ISO week number is one,
 551                           * that week is in next year.
 552                           * If it's January but the ISO week number is 52 or
 553                           * 53, that week is in last year.
 554                           * Otherwise, it's this year.
 555                           */
 556                          w = iso8601wknum(timeptr);
 557                          if (timeptr->tm_mon == 11 && w == 1)
 558                                  y = 1900L + timeptr->tm_year + 1;
 559                          else if (timeptr->tm_mon == 0 && w >= 52)
 560                                  y = 1900L + timeptr->tm_year - 1;
 561                          else
 562                                  y = 1900L + timeptr->tm_year;
 563  
 564                          if (*format == 'G')
 565                                  sprintf(tbuf, "%ld", y);
 566                          else
 567                                  sprintf(tbuf, "%02ld", y % 100);
 568                          break;
 569  #endif /* ISO_DATE_EXT */
 570                  default:
 571                          tbuf[0] = '%';
 572                          tbuf[1] = *format;
 573                          tbuf[2] = '\0';
 574                          break;
 575                  }
 576                  i = strlen(tbuf);
 577                  if (i) {
 578                          if (s + i < endp - 1) {
 579                                  strcpy(s, tbuf);
 580                                  s += i;
 581                          } else
 582                                  return 0;
 583                  }
 584          }
 585  out:
 586          if (s < endp && *format == '\0') {
 587                  *s = '\0';
 588                  return (s - start);
 589          } else
 590                  return 0;
 591  }
 592  
 593  /* isleap --- is a year a leap year? */
 594  
 595  #ifndef __STDC__
 596  static int
 597  isleap(year)
 598  long year;
 599  #else
 600  static int
 601  isleap(long year)
 602  #endif
 603  {
 604          return ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0);
 605  }
 606  
 607  
 608  #ifdef POSIX2_DATE
 609  /* iso8601wknum --- compute week number according to ISO 8601 */
 610  
 611  #ifndef __STDC__
 612  static int
 613  iso8601wknum(timeptr)
 614  const struct tm *timeptr;
 615  #else
 616  static int
 617  iso8601wknum(const struct tm *timeptr)
 618  #endif
 619  {
 620          /*
 621           * From 1003.2:
 622           *      If the week (Monday to Sunday) containing January 1
 623           *      has four or more days in the new year, then it is week 1;
 624           *      otherwise it is the highest numbered week of the previous
 625           *      year (52 or 53), and the next week is week 1.
 626           *
 627           * ADR: This means if Jan 1 was Monday through Thursday,
 628           *      it was week 1, otherwise week 52 or 53.
 629           *
 630           * XPG4 erroneously included POSIX.2 rationale text in the
 631           * main body of the standard. Thus it requires week 53.
 632           */
 633  
 634          int weeknum, jan1day, diff;
 635  
 636          /* get week number, Monday as first day of the week */
 637          weeknum = weeknumber(timeptr, 1);
 638  
 639          /*
 640           * With thanks and tip of the hatlo to tml@tik.vtt.fi
 641           *
 642           * What day of the week does January 1 fall on?
 643           * We know that
 644           *      (timeptr->tm_yday - jan1.tm_yday) MOD 7 ==
 645           *              (timeptr->tm_wday - jan1.tm_wday) MOD 7
 646           * and that
 647           *      jan1.tm_yday == 0
 648           * and that
 649           *      timeptr->tm_wday MOD 7 == timeptr->tm_wday
 650           * from which it follows that. . .
 651           */
 652          jan1day = timeptr->tm_wday - (timeptr->tm_yday % 7);
 653          if (jan1day < 0)
 654                  jan1day += 7;
 655  
 656          /*
 657           * If Jan 1 was a Monday through Thursday, it was in
 658           * week 1.  Otherwise it was last year's highest week, which is
 659           * this year's week 0.
 660           *
 661           * What does that mean?
 662           * If Jan 1 was Monday, the week number is exactly right, it can
 663           *      never be 0.
 664           * If it was Tuesday through Thursday, the weeknumber is one
 665           *      less than it should be, so we add one.
 666           * Otherwise, Friday, Saturday or Sunday, the week number is
 667           * OK, but if it is 0, it needs to be 52 or 53.
 668           */
 669          switch (jan1day) {
 670          case 1:         /* Monday */
 671                  break;
 672          case 2:         /* Tuesday */
 673          case 3:         /* Wednesday */
 674          case 4:         /* Thursday */
 675                  weeknum++;
 676                  break;
 677          case 5:         /* Friday */
 678          case 6:         /* Saturday */
 679          case 0:         /* Sunday */
 680                  if (weeknum == 0) {
 681  #ifdef USE_BROKEN_XPG4
 682                          /* XPG4 (as of March 1994) says 53 unconditionally */
 683                          weeknum = 53;
 684  #else
 685                          /* get week number of last week of last year */
 686                          struct tm dec31ly;      /* 12/31 last year */
 687                          dec31ly = *timeptr;
 688                          dec31ly.tm_year--;
 689                          dec31ly.tm_mon = 11;
 690                          dec31ly.tm_mday = 31;
 691                          dec31ly.tm_wday = (jan1day == 0) ? 6 : jan1day - 1;
 692                          dec31ly.tm_yday = 364 + isleap(dec31ly.tm_year + 1900L);
 693                          weeknum = iso8601wknum(& dec31ly);
 694  #endif
 695                  }
 696                  break;
 697          }
 698  
 699          if (timeptr->tm_mon == 11) {
 700                  /*
 701                   * The last week of the year
 702                   * can be in week 1 of next year.
 703                   * Sigh.
 704                   *
 705                   * This can only happen if
 706                   *      M   T  W
 707                   *      29  30 31
 708                   *      30  31
 709                   *      31
 710                   */
 711                  int wday, mday;
 712  
 713                  wday = timeptr->tm_wday;
 714                  mday = timeptr->tm_mday;
 715                  if (   (wday == 1 && (mday >= 29 && mday <= 31))
 716                      || (wday == 2 && (mday == 30 || mday == 31))
 717                      || (wday == 3 &&  mday == 31))
 718                          weeknum = 1;
 719          }
 720  
 721          return weeknum;
 722  }
 723  #endif
 724  
 725  /* weeknumber --- figure how many weeks into the year */
 726  
 727  /* With thanks and tip of the hatlo to ado@elsie.nci.nih.gov */
 728  
 729  #ifndef __STDC__
 730  static int
 731  weeknumber(timeptr, firstweekday)
 732  const struct tm *timeptr;
 733  int firstweekday;
 734  #else
 735  static int
 736  weeknumber(const struct tm *timeptr, int firstweekday)
 737  #endif
 738  {
 739          int wday = timeptr->tm_wday;
 740          int ret;
 741  
 742          if (firstweekday == 1) {
 743                  if (wday == 0)  /* sunday */
 744                          wday = 6;
 745                  else
 746                          wday--;
 747          }
 748          ret = ((timeptr->tm_yday + 7 - wday) / 7);
 749          if (ret < 0)
 750                  ret = 0;
 751          return ret;
 752  }
 753  
 754  #if 0
 755  /* ADR --- I'm loathe to mess with ado's code ... */
 756  
 757  Date:         Wed, 24 Apr 91 20:54:08 MDT
 758  From: Michal Jaegermann <audfax!emory!vm.ucs.UAlberta.CA!NTOMCZAK>
 759  To: arnold@audiofax.com
 760  
 761  Hi Arnold,
 762  in a process of fixing of strftime() in libraries on Atari ST I grabbed
 763  some pieces of code from your own strftime.  When doing that it came
 764  to mind that your weeknumber() function compiles a little bit nicer
 765  in the following form:
 766  /*
 767   * firstweekday is 0 if starting in Sunday, non-zero if in Monday
 768   */
 769  {
 770      return (timeptr->tm_yday - timeptr->tm_wday +
 771              (firstweekday ? (timeptr->tm_wday ? 8 : 1) : 7)) / 7;
 772  }
 773  How nicer it depends on a compiler, of course, but always a tiny bit.
 774  
 775     Cheers,
 776     Michal
 777     ntomczak@vm.ucs.ualberta.ca
 778  #endif
 779  
 780  #ifdef  TEST_STRFTIME
 781  
 782  /*
 783   * NAME:
 784   *      tst
 785   *
 786   * SYNOPSIS:
 787   *      tst
 788   *
 789   * DESCRIPTION:
 790   *      "tst" is a test driver for the function "strftime".
 791   *
 792   * OPTIONS:
 793   *      None.
 794   *
 795   * AUTHOR:
 796   *      Karl Vogel
 797   *      Control Data Systems, Inc.
 798   *      vogelke@c-17igp.wpafb.af.mil
 799   *
 800   * BUGS:
 801   *      None noticed yet.
 802   *
 803   * COMPILE:
 804   *      cc -o tst -DTEST_STRFTIME strftime.c
 805   */
 806  
 807  /* ADR: I reformatted this to my liking, and deleted some unneeded code. */
 808  
 809  #ifndef NULL
 810  #include        <stdio.h>
 811  #endif
 812  #include        <sys/time.h>
 813  #include        <string.h>
 814  
 815  #define         MAXTIME         132
 816  
 817  /*
 818   * Array of time formats.
 819   */
 820  
 821  static char *array[] =
 822  {
 823          "(%%A)      full weekday name, var length (Sunday..Saturday)  %A",
 824          "(%%B)       full month name, var length (January..December)  %B",
 825          "(%%C)                                               Century  %C",
 826          "(%%D)                                       date (%%m/%%d/%%y)  %D",
 827          "(%%E)                           Locale extensions (ignored)  %E",
 828          "(%%H)                          hour (24-hour clock, 00..23)  %H",
 829          "(%%I)                          hour (12-hour clock, 01..12)  %I",
 830          "(%%M)                                       minute (00..59)  %M",
 831          "(%%O)                           Locale extensions (ignored)  %O",
 832          "(%%R)                                 time, 24-hour (%%H:%%M)  %R",
 833          "(%%S)                                       second (00..60)  %S",
 834          "(%%T)                              time, 24-hour (%%H:%%M:%%S)  %T",
 835          "(%%U)    week of year, Sunday as first day of week (00..53)  %U",
 836          "(%%V)                    week of year according to ISO 8601  %V",
 837          "(%%W)    week of year, Monday as first day of week (00..53)  %W",
 838          "(%%X)     appropriate locale time representation (%H:%M:%S)  %X",
 839          "(%%Y)                           year with century (1970...)  %Y",
 840          "(%%Z) timezone (EDT), or blank if timezone not determinable  %Z",
 841          "(%%a)          locale's abbreviated weekday name (Sun..Sat)  %a",
 842          "(%%b)            locale's abbreviated month name (Jan..Dec)  %b",
 843          "(%%c)           full date (Sat Nov  4 12:02:33 1989)%n%t%t%t  %c",
 844          "(%%d)                             day of the month (01..31)  %d",
 845          "(%%e)               day of the month, blank-padded ( 1..31)  %e",
 846          "(%%h)                                should be same as (%%b)  %h",
 847          "(%%j)                            day of the year (001..366)  %j",
 848          "(%%k)               hour, 24-hour clock, blank pad ( 0..23)  %k",
 849          "(%%l)               hour, 12-hour clock, blank pad ( 0..12)  %l",
 850          "(%%m)                                        month (01..12)  %m",
 851          "(%%p)              locale's AM or PM based on 12-hour clock  %p",
 852          "(%%r)                   time, 12-hour (same as %%I:%%M:%%S %%p)  %r",
 853          "(%%u) ISO 8601: Weekday as decimal number [1 (Monday) - 7]   %u",
 854          "(%%v)                                VMS date (dd-bbb-YYYY)  %v",
 855          "(%%w)                       day of week (0..6, Sunday == 0)  %w",
 856          "(%%x)                appropriate locale date representation  %x",
 857          "(%%y)                      last two digits of year (00..99)  %y",
 858          "(%%z)      timezone offset east of GMT as HHMM (e.g. -0500)  %z",
 859          (char *) NULL
 860  };
 861  
 862  /* main routine. */
 863  
 864  int
 865  main(argc, argv)
 866  int argc;
 867  char **argv;
 868  {
 869          long time();
 870  
 871          char *next;
 872          char string[MAXTIME];
 873  
 874          int k;
 875          int length;
 876  
 877          struct tm *tm;
 878  
 879          long clock;
 880  
 881          /* Call the function. */
 882  
 883          clock = time((long *) 0);
 884          tm = localtime(&clock);
 885  
 886          for (k = 0; next = array[k]; k++) {
 887                  length = strftime(string, MAXTIME, next, tm);
 888                  printf("%s\n", string);
 889          }
 890  
 891          exit(0);
 892  }
 893  #endif  /* TEST_STRFTIME */