home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 October / usenetsourcesnewsgroupsinfomagicoctober1994disk2.iso / unix / volume28 / strftime-6.1 / part01 next >
Encoding:
Text File  |  1994-05-11  |  31.1 KB  |  1,311 lines

  1. Newsgroups: comp.sources.unix
  2. From: arnold@skeeve.atl.ga.us (Arnold Robbins)
  3. Subject: v28i034: strftime - strftime(3) and date(1) implementation, V6.1, Part01/01
  4. Message-id: <1.768719373.27299@gw.home.vix.com>
  5. Sender: unix-sources-moderator@gw.home.vix.com
  6. Approved: vixie@gw.home.vix.com
  7.  
  8. Submitted-By: arnold@skeeve.atl.ga.us (Arnold Robbins)
  9. Posting-Number: Volume 28, Issue 34
  10. Archive-Name: strftime-6.1/part01
  11.  
  12. In Volume 27, Issue 207 our moderator writes:
  13.  
  14. >[ this recurring package is the cleanest, nicest date(1) and strftime(3)
  15. >  i have ever seen, including all free or vendor systems.    --vix ]
  16.  
  17. To which I can only say, "gosh, thanks!".
  18.  
  19. I had written:
  20.  
  21. > It seems to be an annual event, something in the circling of the stars
  22. > overhead, that requires me to post this, at least once a year. (:-)
  23. > I sincerely hope this will be it (at least 'til next year! :-)
  24.  
  25. Alas, it seems to be an annual event that shortly after posting my package,
  26. I also post bug fixes. :-)
  27.  
  28. There are three main fixes here. 1) An update to the date.1 man page to
  29. reflect that POSIX.2 is not in draft any more, 2) Yet another fix to
  30. the %V code, and 3) Portability enhancements if there is no tm_zone
  31. field in the struct tm.
  32.  
  33. Thanks to all who sent mail and helped me track down the ISO 8601 issues.
  34.  
  35. This is strftime 6.1. The package is small enough I'm just reposting the
  36. whole thing.
  37.  
  38. Arnold Robbins -- The Basement Computer        | Laundry increases
  39. Internet: arnold@skeeve.ATL.GA.US        | exponentially in the
  40. UUCP:    emory!skeeve!arnold            | number of children.
  41. Bitnet:    Forget it. Get on a real network.    |    -- Miriam Robbins
  42. ------------------------- cut here ------------------------------------
  43.  
  44. #! /bin/sh
  45. echo - 'README'
  46. cat << 'EOF' > 'README'
  47. Tue May 10 21:43:29 EDT 1994
  48.  
  49. This package implements the Posix 1003.2 date command, as a wrapper around
  50. an extended version of the ANSI strftime(3) library routine.
  51. Everything in it is public domain.
  52.  
  53. Arnold Robbins -- The Basement Computer        | Laundry increases
  54. Internet: arnold@skeeve.ATL.GA.US        | exponentially in the
  55. UUCP:    { gatech, emory }!skeeve!arnold        | number of children.
  56. Bitnet:    Forget it. Get on a real network.    |    -- Miriam Robbins
  57. EOF
  58. echo - 'Makefile'
  59. cat << 'EOF' > 'Makefile'
  60. # Makefile for PD date and strftime
  61.  
  62. SRCS= date.c strftime.c
  63. OBJS= date.o strftime.o
  64. DOCS= date.1 strftime.3
  65.  
  66. # Uncomment the define of HAVE_TZNAME if your system has the tzname[] array.
  67. # Uncomment the define of TM_IN_SYS_TIME if struct tm is in <sys/time.h>.
  68. # Uncomment the define of TM_ZONE if your struct tm has the tm_zone field.
  69. CFLAGS= -O #-DHAVE_TZNAME #-DTM_IN_SYS_TIME #-DHAVE_TM_ZONE
  70.  
  71. date: $(OBJS)
  72.     $(CC) $(CFLAGS) $(OBJS) -o $@
  73.  
  74. date.o: date.c
  75.  
  76. strftime.o: strftime.c
  77. EOF
  78. echo - 'date.c'
  79. cat << 'EOF' > 'date.c'
  80. /*
  81.  * date.c
  82.  *
  83.  * Public domain implementation of Posix 1003.2
  84.  * date command.  Lets strftime() do the dirty work.
  85.  *
  86.  * Arnold Robbins
  87.  * arnold@skeeve.atl.ga.us
  88.  * April, 1991
  89.  *
  90.  * Bug fix courtesy of Chris Ritson (C.R.Ritson@newcastle.ac.uk),
  91.  * February, 1994.
  92.  */
  93.  
  94. #include <stdio.h>
  95. #include <sys/types.h>
  96. #include <time.h>
  97.  
  98. extern char *malloc();
  99. extern size_t strftime();
  100. extern int getopt();
  101. extern int optind;
  102.  
  103. int
  104. main(argc, argv)
  105. int argc;
  106. char **argv;
  107. {
  108.     time_t clock;
  109.     struct tm *now;
  110.     int c, size, ret;
  111.     char *defhow = "%a %b %e %H:%M:%S %Z %Y";
  112.     char *howto = defhow;
  113.     char *buf;
  114.  
  115.     while ((c = getopt(argc, argv, "u")) != -1)
  116.         switch (c) {
  117.         case 'u':
  118.             putenv("TZ=GMT0");
  119.             break;
  120.         default:
  121.             fprintf(stderr, "usage: %s [-u] [+format_str]\n",
  122.                 argv[0]);
  123.             exit(1);
  124.         }
  125.  
  126.     time(& clock);
  127.     now = localtime(& clock);
  128.  
  129.     if (optind < argc && argv[optind][0] == '+')
  130.         howto = & argv[optind][1];
  131.  
  132.     size = strlen(howto) * 10;
  133.     if (size < 26)
  134.         size = 26;
  135.     if ((buf = malloc(size)) == NULL) {
  136.         perror("not enough memory");
  137.         exit(1);
  138.     }
  139.  
  140.     ret = strftime(buf, size, howto, now);
  141.     if (ret != 0)
  142.         printf("%s\n", buf);
  143.     else {
  144.         fprintf(stderr, "conversion failed\n");
  145.         exit(1);
  146.     }
  147.     
  148.     exit(0);
  149. }
  150. EOF
  151. echo - 'strftime.c'
  152. cat << 'EOF' > 'strftime.c'
  153. /*
  154.  * strftime.c
  155.  *
  156.  * Public-domain implementation of ANSI C library routine.
  157.  *
  158.  * It's written in old-style C for maximal portability.
  159.  * However, since I'm used to prototypes, I've included them too.
  160.  *
  161.  * If you want stuff in the System V ascftime routine, add the SYSV_EXT define.
  162.  * For extensions from SunOS, add SUNOS_EXT.
  163.  * For stuff needed to implement the P1003.2 date command, add POSIX2_DATE.
  164.  * For VMS dates, add VMS_EXT.
  165.  * For complete POSIX semantics, add POSIX_SEMANTICS.
  166.  *
  167.  * The code for %c, %x, and %X is my best guess as to what's "appropriate".
  168.  * This version ignores LOCALE information.
  169.  * It also doesn't worry about multi-byte characters.
  170.  * So there.
  171.  *
  172.  * This file is also shipped with GAWK (GNU Awk), gawk specific bits of
  173.  * code are included if GAWK is defined.
  174.  *
  175.  * Arnold Robbins
  176.  * January, February, March, 1991
  177.  * Updated March, April 1992
  178.  * Updated April, 1993
  179.  * Updated February, 1994
  180.  * Updated May, 1994
  181.  *
  182.  * Fixes from ado@elsie.nci.nih.gov
  183.  * February 1991, May 1992
  184.  * Fixes from Tor Lillqvist tml@tik.vtt.fi
  185.  * May, 1993
  186.  * Further fixes from ado@elsie.nci.nih.gov
  187.  * February 1994
  188.  */
  189.  
  190. #ifndef GAWK
  191. #include <stdio.h>
  192. #include <ctype.h>
  193. #include <string.h>
  194. #include <time.h>
  195. #endif
  196. #if defined(TM_IN_SYS_TIME) || ! defined(GAWK)
  197. #include <sys/types.h>
  198. #include <sys/time.h>
  199. #endif
  200.  
  201. /* defaults: season to taste */
  202. #define SYSV_EXT    1    /* stuff in System V ascftime routine */
  203. #define SUNOS_EXT    1    /* stuff in SunOS strftime routine */
  204. #define POSIX2_DATE    1    /* stuff in Posix 1003.2 date command */
  205. #define VMS_EXT        1    /* include %v for VMS date format */
  206. #ifndef GAWK
  207. #define POSIX_SEMANTICS    1    /* call tzset() if TZ changes */
  208. #endif
  209.  
  210. #if defined(POSIX2_DATE)
  211. #if ! defined(SYSV_EXT)
  212. #define SYSV_EXT    1
  213. #endif
  214. #if ! defined(SUNOS_EXT)
  215. #define SUNOS_EXT    1
  216. #endif
  217. #endif
  218.  
  219. #if defined(POSIX2_DATE)
  220. #define adddecl(stuff)    stuff
  221. #else
  222. #define adddecl(stuff)
  223. #endif
  224.  
  225. #undef strchr    /* avoid AIX weirdness */
  226.  
  227. #ifndef __STDC__
  228. #define const    /**/
  229. extern void *malloc();
  230. extern void *realloc();
  231. extern void tzset();
  232. extern char *strchr();
  233. extern char *getenv();
  234. static int weeknumber();
  235. adddecl(static int iso8601wknum();)
  236. #else
  237. extern void *malloc(unsigned count);
  238. extern void *realloc(void *ptr, unsigned count);
  239. extern void tzset(void);
  240. extern char *strchr(const char *str, int ch);
  241. extern char *getenv(const char *v);
  242. static int weeknumber(const struct tm *timeptr, int firstweekday);
  243. adddecl(static int iso8601wknum(const struct tm *timeptr);)
  244. #endif
  245.  
  246. #ifdef __GNUC__
  247. #define inline    __inline__
  248. #else
  249. #define inline    /**/
  250. #endif
  251.  
  252. #define range(low, item, hi)    max(low, min(item, hi))
  253.  
  254. #if !defined(OS2) && !defined(MSDOS) && defined(HAVE_TZNAME)
  255. extern char *tzname[2];
  256. extern int daylight;
  257. #endif
  258.  
  259. /* min --- return minimum of two numbers */
  260.  
  261. #ifndef __STDC__
  262. static inline int
  263. min(a, b)
  264. int a, b;
  265. #else
  266. static inline int
  267. min(int a, int b)
  268. #endif
  269. {
  270.     return (a < b ? a : b);
  271. }
  272.  
  273. /* max --- return maximum of two numbers */
  274.  
  275. #ifndef __STDC__
  276. static inline int
  277. max(a, b)
  278. int a, b;
  279. #else
  280. static inline int
  281. max(int a, int b)
  282. #endif
  283. {
  284.     return (a > b ? a : b);
  285. }
  286.  
  287. /* strftime --- produce formatted time */
  288.  
  289. #ifndef __STDC__
  290. size_t
  291. strftime(s, maxsize, format, timeptr)
  292. char *s;
  293. size_t maxsize;
  294. const char *format;
  295. const struct tm *timeptr;
  296. #else
  297. size_t
  298. strftime(char *s, size_t maxsize, const char *format, const struct tm *timeptr)
  299. #endif
  300. {
  301.     char *endp = s + maxsize;
  302.     char *start = s;
  303.     auto char tbuf[100];
  304.     int i;
  305.     static short first = 1;
  306. #ifdef POSIX_SEMANTICS
  307.     static char *savetz = NULL;
  308.     static int savetzlen = 0;
  309.     char *tz;
  310. #endif /* POSIX_SEMANTICS */
  311. #ifndef HAVE_TM_ZONE
  312.     extern char *timezone();
  313.     struct timeval tv;
  314.     struct timezone zone;
  315. #endif /* HAVE_TM_ZONE */
  316.  
  317.     /* various tables, useful in North America */
  318.     static const char *days_a[] = {
  319.         "Sun", "Mon", "Tue", "Wed",
  320.         "Thu", "Fri", "Sat",
  321.     };
  322.     static const char *days_l[] = {
  323.         "Sunday", "Monday", "Tuesday", "Wednesday",
  324.         "Thursday", "Friday", "Saturday",
  325.     };
  326.     static const char *months_a[] = {
  327.         "Jan", "Feb", "Mar", "Apr", "May", "Jun",
  328.         "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
  329.     };
  330.     static const char *months_l[] = {
  331.         "January", "February", "March", "April",
  332.         "May", "June", "July", "August", "September",
  333.         "October", "November", "December",
  334.     };
  335.     static const char *ampm[] = { "AM", "PM", };
  336.  
  337.     if (s == NULL || format == NULL || timeptr == NULL || maxsize == 0)
  338.         return 0;
  339.  
  340.     /* quick check if we even need to bother */
  341.     if (strchr(format, '%') == NULL && strlen(format) + 1 >= maxsize)
  342.         return 0;
  343.  
  344. #ifndef POSIX_SEMANTICS
  345.     if (first) {
  346.         tzset();
  347.         first = 0;
  348.     }
  349. #else    /* POSIX_SEMANTICS */
  350.     tz = getenv("TZ");
  351.     if (first) {
  352.         if (tz != NULL) {
  353.             int tzlen = strlen(tz);
  354.  
  355.             savetz = (char *) malloc(tzlen + 1);
  356.             if (savetz != NULL) {
  357.                 savetzlen = tzlen + 1;
  358.                 strcpy(savetz, tz);
  359.             }
  360.         }
  361.         tzset();
  362.         first = 0;
  363.     }
  364.     /* if we have a saved TZ, and it is different, recapture and reset */
  365.     if (tz && savetz && (tz[0] != savetz[0] || strcmp(tz, savetz) != 0)) {
  366.         i = strlen(tz) + 1;
  367.         if (i > savetzlen) {
  368.             savetz = (char *) realloc(savetz, i);
  369.             if (savetz) {
  370.                 savetzlen = i;
  371.                 strcpy(savetz, tz);
  372.             }
  373.         } else
  374.             strcpy(savetz, tz);
  375.         tzset();
  376.     }
  377. #endif    /* POSIX_SEMANTICS */
  378.  
  379.     for (; *format && s < endp - 1; format++) {
  380.         tbuf[0] = '\0';
  381.         if (*format != '%') {
  382.             *s++ = *format;
  383.             continue;
  384.         }
  385.     again:
  386.         switch (*++format) {
  387.         case '\0':
  388.             *s++ = '%';
  389.             goto out;
  390.  
  391.         case '%':
  392.             *s++ = '%';
  393.             continue;
  394.  
  395.         case 'a':    /* abbreviated weekday name */
  396.             if (timeptr->tm_wday < 0 || timeptr->tm_wday > 6)
  397.                 strcpy(tbuf, "?");
  398.             else
  399.                 strcpy(tbuf, days_a[timeptr->tm_wday]);
  400.             break;
  401.  
  402.         case 'A':    /* full weekday name */
  403.             if (timeptr->tm_wday < 0 || timeptr->tm_wday > 6)
  404.                 strcpy(tbuf, "?");
  405.             else
  406.                 strcpy(tbuf, days_l[timeptr->tm_wday]);
  407.             break;
  408.  
  409. #ifdef SYSV_EXT
  410.         case 'h':    /* abbreviated month name */
  411. #endif
  412.         case 'b':    /* abbreviated month name */
  413.             if (timeptr->tm_mon < 0 || timeptr->tm_mon > 11)
  414.                 strcpy(tbuf, "?");
  415.             else
  416.                 strcpy(tbuf, months_a[timeptr->tm_mon]);
  417.             break;
  418.  
  419.         case 'B':    /* full month name */
  420.             if (timeptr->tm_mon < 0 || timeptr->tm_mon > 11)
  421.                 strcpy(tbuf, "?");
  422.             else
  423.                 strcpy(tbuf, months_l[timeptr->tm_mon]);
  424.             break;
  425.  
  426.         case 'c':    /* appropriate date and time representation */
  427.             sprintf(tbuf, "%s %s %2d %02d:%02d:%02d %d",
  428.                 days_a[range(0, timeptr->tm_wday, 6)],
  429.                 months_a[range(0, timeptr->tm_mon, 11)],
  430.                 range(1, timeptr->tm_mday, 31),
  431.                 range(0, timeptr->tm_hour, 23),
  432.                 range(0, timeptr->tm_min, 59),
  433.                 range(0, timeptr->tm_sec, 61),
  434.                 timeptr->tm_year + 1900);
  435.             break;
  436.  
  437.         case 'd':    /* day of the month, 01 - 31 */
  438.             i = range(1, timeptr->tm_mday, 31);
  439.             sprintf(tbuf, "%02d", i);
  440.             break;
  441.  
  442.         case 'H':    /* hour, 24-hour clock, 00 - 23 */
  443.             i = range(0, timeptr->tm_hour, 23);
  444.             sprintf(tbuf, "%02d", i);
  445.             break;
  446.  
  447.         case 'I':    /* hour, 12-hour clock, 01 - 12 */
  448.             i = range(0, timeptr->tm_hour, 23);
  449.             if (i == 0)
  450.                 i = 12;
  451.             else if (i > 12)
  452.                 i -= 12;
  453.             sprintf(tbuf, "%02d", i);
  454.             break;
  455.  
  456.         case 'j':    /* day of the year, 001 - 366 */
  457.             sprintf(tbuf, "%03d", timeptr->tm_yday + 1);
  458.             break;
  459.  
  460.         case 'm':    /* month, 01 - 12 */
  461.             i = range(0, timeptr->tm_mon, 11);
  462.             sprintf(tbuf, "%02d", i + 1);
  463.             break;
  464.  
  465.         case 'M':    /* minute, 00 - 59 */
  466.             i = range(0, timeptr->tm_min, 59);
  467.             sprintf(tbuf, "%02d", i);
  468.             break;
  469.  
  470.         case 'p':    /* am or pm based on 12-hour clock */
  471.             i = range(0, timeptr->tm_hour, 23);
  472.             if (i < 12)
  473.                 strcpy(tbuf, ampm[0]);
  474.             else
  475.                 strcpy(tbuf, ampm[1]);
  476.             break;
  477.  
  478.         case 'S':    /* second, 00 - 61 */
  479.             i = range(0, timeptr->tm_sec, 61);
  480.             sprintf(tbuf, "%02d", i);
  481.             break;
  482.  
  483.         case 'U':    /* week of year, Sunday is first day of week */
  484.             sprintf(tbuf, "%02d", weeknumber(timeptr, 0));
  485.             break;
  486.  
  487.         case 'w':    /* weekday, Sunday == 0, 0 - 6 */
  488.             i = range(0, timeptr->tm_wday, 6);
  489.             sprintf(tbuf, "%d", i);
  490.             break;
  491.  
  492.         case 'W':    /* week of year, Monday is first day of week */
  493.             sprintf(tbuf, "%02d", weeknumber(timeptr, 1));
  494.             break;
  495.  
  496.         case 'x':    /* appropriate date representation */
  497.             sprintf(tbuf, "%s %s %2d %d",
  498.                 days_a[range(0, timeptr->tm_wday, 6)],
  499.                 months_a[range(0, timeptr->tm_mon, 11)],
  500.                 range(1, timeptr->tm_mday, 31),
  501.                 timeptr->tm_year + 1900);
  502.             break;
  503.  
  504.         case 'X':    /* appropriate time representation */
  505.             sprintf(tbuf, "%02d:%02d:%02d",
  506.                 range(0, timeptr->tm_hour, 23),
  507.                 range(0, timeptr->tm_min, 59),
  508.                 range(0, timeptr->tm_sec, 61));
  509.             break;
  510.  
  511.         case 'y':    /* year without a century, 00 - 99 */
  512.             i = timeptr->tm_year % 100;
  513.             sprintf(tbuf, "%02d", i);
  514.             break;
  515.  
  516.         case 'Y':    /* year with century */
  517.             sprintf(tbuf, "%d", 1900 + timeptr->tm_year);
  518.             break;
  519.  
  520.         case 'Z':    /* time zone name or abbrevation */
  521. #ifdef HAVE_TZNAME
  522.             i = (daylight && timeptr->tm_isdst);    /* 0 or 1 */
  523.             strcpy(tbuf, tzname[i]);
  524. #else
  525. #ifdef HAVE_TM_ZONE
  526.             strcpy(tbuf, timeptr->tm_zone);
  527. #else
  528.             gettimeofday(& tv, & zone);
  529.             strcpy(tbuf, timezone(zone.tz_minuteswest,
  530.                         timeptr->tm_isdst));
  531. #endif
  532. #endif
  533.             break;
  534.  
  535. #ifdef SYSV_EXT
  536.         case 'n':    /* same as \n */
  537.             tbuf[0] = '\n';
  538.             tbuf[1] = '\0';
  539.             break;
  540.  
  541.         case 't':    /* same as \t */
  542.             tbuf[0] = '\t';
  543.             tbuf[1] = '\0';
  544.             break;
  545.  
  546.         case 'D':    /* date as %m/%d/%y */
  547.             strftime(tbuf, sizeof tbuf, "%m/%d/%y", timeptr);
  548.             break;
  549.  
  550.         case 'e':    /* day of month, blank padded */
  551.             sprintf(tbuf, "%2d", range(1, timeptr->tm_mday, 31));
  552.             break;
  553.  
  554.         case 'r':    /* time as %I:%M:%S %p */
  555.             strftime(tbuf, sizeof tbuf, "%I:%M:%S %p", timeptr);
  556.             break;
  557.  
  558.         case 'R':    /* time as %H:%M */
  559.             strftime(tbuf, sizeof tbuf, "%H:%M", timeptr);
  560.             break;
  561.  
  562.         case 'T':    /* time as %H:%M:%S */
  563.             strftime(tbuf, sizeof tbuf, "%H:%M:%S", timeptr);
  564.             break;
  565. #endif
  566.  
  567. #ifdef SUNOS_EXT
  568.         case 'k':    /* hour, 24-hour clock, blank pad */
  569.             sprintf(tbuf, "%2d", range(0, timeptr->tm_hour, 23));
  570.             break;
  571.  
  572.         case 'l':    /* hour, 12-hour clock, 1 - 12, blank pad */
  573.             i = range(0, timeptr->tm_hour, 23);
  574.             if (i == 0)
  575.                 i = 12;
  576.             else if (i > 12)
  577.                 i -= 12;
  578.             sprintf(tbuf, "%2d", i);
  579.             break;
  580. #endif
  581.  
  582.  
  583. #ifdef VMS_EXT
  584.         case 'v':    /* date as dd-bbb-YYYY */
  585.             sprintf(tbuf, "%02d-%3.3s-%4d",
  586.                 range(1, timeptr->tm_mday, 31),
  587.                 months_a[range(0, timeptr->tm_mon, 11)],
  588.                 timeptr->tm_year + 1900);
  589.             for (i = 3; i < 6; i++)
  590.                 if (islower(tbuf[i]))
  591.                     tbuf[i] = toupper(tbuf[i]);
  592.             break;
  593. #endif
  594.  
  595.  
  596. #ifdef POSIX2_DATE
  597.         case 'C':
  598.             sprintf(tbuf, "%02d", (timeptr->tm_year + 1900) / 100);
  599.             break;
  600.  
  601.  
  602.         case 'E':
  603.         case 'O':
  604.             /* POSIX locale extensions, ignored for now */
  605.             goto again;
  606.  
  607.         case 'V':    /* week of year according ISO 8601 */
  608. #if defined(GAWK) && defined(VMS_EXT)
  609.         {
  610.             extern int do_lint;
  611.             extern void warning();
  612.             static int warned = 0;
  613.  
  614.             if (! warned && do_lint) {
  615.                 warned = 1;
  616.                 warning(
  617.     "conversion %%V added in P1003.2; for VMS style date, use %%v");
  618.             }
  619.         }
  620. #endif
  621.             sprintf(tbuf, "%02d", iso8601wknum(timeptr));
  622.             break;
  623.  
  624.         case 'u':
  625.         /* ISO 8601: Weekday as a decimal number [1 (Monday) - 7] */
  626.             sprintf(tbuf, "%d", timeptr->tm_wday == 0 ? 7 :
  627.                     timeptr->tm_wday);
  628.             break;
  629. #endif    /* POSIX2_DATE */
  630.         default:
  631.             tbuf[0] = '%';
  632.             tbuf[1] = *format;
  633.             tbuf[2] = '\0';
  634.             break;
  635.         }
  636.         i = strlen(tbuf);
  637.         if (i) {
  638.             if (s + i < endp - 1) {
  639.                 strcpy(s, tbuf);
  640.                 s += i;
  641.             } else
  642.                 return 0;
  643.         }
  644.     }
  645. out:
  646.     if (s < endp && *format == '\0') {
  647.         *s = '\0';
  648.         return (s - start);
  649.     } else
  650.         return 0;
  651. }
  652.  
  653. /* isleap --- is a year a leap year? */
  654.  
  655. #ifndef __STDC__
  656. static int
  657. isleap(year)
  658. int year;
  659. #else
  660. static int
  661. isleap(int year)
  662. #endif
  663. {
  664.     return ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0);
  665. }
  666.  
  667.  
  668. #ifdef POSIX2_DATE
  669. /* iso8601wknum --- compute week number according to ISO 8601 */
  670.  
  671. #ifndef __STDC__
  672. static int
  673. iso8601wknum(timeptr)
  674. const struct tm *timeptr;
  675. #else
  676. static int
  677. iso8601wknum(const struct tm *timeptr)
  678. #endif
  679. {
  680.     /*
  681.      * From 1003.2:
  682.      *    If the week (Monday to Sunday) containing January 1
  683.      *    has four or more days in the new year, then it is week 1;
  684.      *    otherwise it is the highest numbered week of the previous
  685.      *    (52 or 53) year, and the next week is week 1.
  686.      *
  687.      * ADR: This means if Jan 1 was Monday through Thursday,
  688.      *    it was week 1, otherwise week 52 or 53.
  689.      *
  690.      * XPG4 erroneously included POSIX.2 rationale text in the
  691.      * main body of the standard. Thus it requires week 53.
  692.      */
  693.  
  694.     int weeknum, jan1day, diff;
  695.  
  696.     /* get week number, Monday as first day of the week */
  697.     weeknum = weeknumber(timeptr, 1);
  698.  
  699.     /*
  700.      * With thanks and tip of the hatlo to tml@tik.vtt.fi
  701.      *
  702.      * What day of the week does January 1 fall on?
  703.      * We know that
  704.      *    (timeptr->tm_yday - jan1.tm_yday) MOD 7 ==
  705.      *        (timeptr->tm_wday - jan1.tm_wday) MOD 7
  706.      * and that
  707.      *     jan1.tm_yday == 0
  708.      * and that
  709.      *     timeptr->tm_wday MOD 7 == timeptr->tm_wday
  710.      * from which it follows that. . .
  711.       */
  712.     jan1day = timeptr->tm_wday - (timeptr->tm_yday % 7);
  713.     if (jan1day < 0)
  714.         jan1day += 7;
  715.  
  716.     /*
  717.      * If Jan 1 was a Monday through Thursday, it was in
  718.      * week 1.  Otherwise it was last year's highest week, which is
  719.      * this year's week 0.
  720.      *
  721.      * What does that mean?
  722.      * If Jan 1 was Monday, the week number is exactly right, it can
  723.      *    never be 0.
  724.      * If it was Tuesday through Thursday, the weeknumber is one
  725.      *    less than it should be, so we add one.
  726.      * Otherwise, Friday, Saturday or Sunday, the week number is
  727.      * OK, but if it is 0, it needs to be 52 or 53.
  728.      */
  729.     switch (jan1day) {
  730.     case 1:        /* Monday */
  731.         break;
  732.     case 2:        /* Tuesday */
  733.     case 3:        /* Wednedsday */
  734.     case 4:        /* Thursday */
  735.         weeknum++;
  736.         break;
  737.     case 5:        /* Friday */
  738.     case 6:        /* Saturday */
  739.     case 0:        /* Sunday */
  740.         if (weeknum == 0) {
  741. #ifdef USE_BROKEN_XPG4
  742.             /* XPG4 (as of March 1994) says 53 unconditionally */
  743.             weeknum = 53;
  744. #else
  745.             /* get week number of last week of last year */
  746.             struct tm dec31ly;    /* 12/31 last year */
  747.             dec31ly = *timeptr;
  748.             dec31ly.tm_year--;
  749.             dec31ly.tm_mon = 11;
  750.             dec31ly.tm_mday = 31;
  751.             dec31ly.tm_wday = (jan1day == 0) ? 6 : jan1day - 1;
  752.             dec31ly.tm_yday = 364 + isleap(dec31ly.tm_year + 1900);
  753.             weeknum = iso8601wknum(& dec31ly);
  754. #endif
  755.         }
  756.         break;
  757.     }
  758.     return weeknum;
  759. }
  760. #endif
  761.  
  762. /* weeknumber --- figure how many weeks into the year */
  763.  
  764. /* With thanks and tip of the hatlo to ado@elsie.nci.nih.gov */
  765.  
  766. #ifndef __STDC__
  767. static int
  768. weeknumber(timeptr, firstweekday)
  769. const struct tm *timeptr;
  770. int firstweekday;
  771. #else
  772. static int
  773. weeknumber(const struct tm *timeptr, int firstweekday)
  774. #endif
  775. {
  776.     int wday = timeptr->tm_wday;
  777.     int ret;
  778.  
  779.     if (firstweekday == 1) {
  780.         if (wday == 0)    /* sunday */
  781.             wday = 6;
  782.         else
  783.             wday--;
  784.     }
  785.     ret = ((timeptr->tm_yday + 7 - wday) / 7);
  786.     if (ret < 0)
  787.         ret = 0;
  788.     return ret;
  789. }
  790.  
  791. #if 0
  792. /* ADR --- I'm loathe to mess with ado's code ... */
  793.  
  794. Date:         Wed, 24 Apr 91 20:54:08 MDT
  795. From: Michal Jaegermann <audfax!emory!vm.ucs.UAlberta.CA!NTOMCZAK>
  796. To: arnold@audiofax.com
  797.  
  798. Hi Arnold,
  799. in a process of fixing of strftime() in libraries on Atari ST I grabbed
  800. some pieces of code from your own strftime.  When doing that it came
  801. to mind that your weeknumber() function compiles a little bit nicer
  802. in the following form:
  803. /*
  804.  * firstweekday is 0 if starting in Sunday, non-zero if in Monday
  805.  */
  806. {
  807.     return (timeptr->tm_yday - timeptr->tm_wday +
  808.         (firstweekday ? (timeptr->tm_wday ? 8 : 1) : 7)) / 7;
  809. }
  810. How nicer it depends on a compiler, of course, but always a tiny bit.
  811.  
  812.    Cheers,
  813.    Michal
  814.    ntomczak@vm.ucs.ualberta.ca
  815. #endif
  816.  
  817. #ifdef    TEST_STRFTIME
  818.  
  819. /*
  820.  * NAME:
  821.  *    tst
  822.  *
  823.  * SYNOPSIS:
  824.  *    tst
  825.  *
  826.  * DESCRIPTION:
  827.  *    "tst" is a test driver for the function "strftime".
  828.  *
  829.  * OPTIONS:
  830.  *    None.
  831.  *
  832.  * AUTHOR:
  833.  *    Karl Vogel
  834.  *    Control Data Systems, Inc.
  835.  *    vogelke@c-17igp.wpafb.af.mil
  836.  *
  837.  * BUGS:
  838.  *    None noticed yet.
  839.  *
  840.  * COMPILE:
  841.  *    cc -o tst -DTEST_STRFTIME strftime.c
  842.  */
  843.  
  844. /* ADR: I reformatted this to my liking, and deleted some unneeded code. */
  845.  
  846. #ifndef NULL
  847. #include    <stdio.h>
  848. #endif
  849. #include    <sys/time.h>
  850. #include    <string.h>
  851.  
  852. #define        MAXTIME        132
  853.  
  854. /*
  855.  * Array of time formats.
  856.  */
  857.  
  858. static char *array[] =
  859. {
  860.     "(%%A)      full weekday name, var length (Sunday..Saturday)  %A",
  861.     "(%%B)       full month name, var length (January..December)  %B",
  862.     "(%%C)                                               Century  %C",
  863.     "(%%D)                                       date (%%m/%%d/%%y)  %D",
  864.     "(%%E)                           Locale extensions (ignored)  %E",
  865.     "(%%H)                          hour (24-hour clock, 00..23)  %H",
  866.     "(%%I)                          hour (12-hour clock, 01..12)  %I",
  867.     "(%%M)                                       minute (00..59)  %M",
  868.     "(%%O)                           Locale extensions (ignored)  %O",
  869.     "(%%R)                                 time, 24-hour (%%H:%%M)  %R",
  870.     "(%%S)                                       second (00..61)  %S",
  871.     "(%%T)                              time, 24-hour (%%H:%%M:%%S)  %T",
  872.     "(%%U)    week of year, Sunday as first day of week (00..53)  %U",
  873.     "(%%V)                    week of year according to ISO 8601  %V",
  874.     "(%%W)    week of year, Monday as first day of week (00..53)  %W",
  875.     "(%%X)     appropriate locale time representation (%H:%M:%S)  %X",
  876.     "(%%Y)                           year with century (1970...)  %Y",
  877.     "(%%Z) timezone (EDT), or blank if timezone not determinable  %Z",
  878.     "(%%a)          locale's abbreviated weekday name (Sun..Sat)  %a",
  879.     "(%%b)            locale's abbreviated month name (Jan..Dec)  %b",
  880.     "(%%c)           full date (Sat Nov  4 12:02:33 1989)%n%t%t%t  %c",
  881.     "(%%d)                             day of the month (01..31)  %d",
  882.     "(%%e)               day of the month, blank-padded ( 1..31)  %e",
  883.     "(%%h)                                should be same as (%%b)  %h",
  884.     "(%%j)                            day of the year (001..366)  %j",
  885.     "(%%k)               hour, 24-hour clock, blank pad ( 0..23)  %k",
  886.     "(%%l)               hour, 12-hour clock, blank pad ( 0..12)  %l",
  887.     "(%%m)                                        month (01..12)  %m",
  888.     "(%%p)              locale's AM or PM based on 12-hour clock  %p",
  889.     "(%%r)                   time, 12-hour (same as %%I:%%M:%%S %%p)  %r",
  890.     "(%%u) ISO 8601: Weekday as decimal number [1 (Monday) - 7]   %u",
  891.     "(%%v)                                VAX date (dd-bbb-YYYY)  %v",
  892.     "(%%w)                       day of week (0..6, Sunday == 0)  %w",
  893.     "(%%x)                appropriate locale date representation  %x",
  894.     "(%%y)                      last two digits of year (00..99)  %y",
  895.     (char *) NULL
  896. };
  897.  
  898. /* main routine. */
  899.  
  900. int
  901. main(argc, argv)
  902. int argc;
  903. char **argv;
  904. {
  905.     long time();
  906.  
  907.     char *next;
  908.     char string[MAXTIME];
  909.  
  910.     int k;
  911.     int length;
  912.  
  913.     struct tm *tm;
  914.  
  915.     long clock;
  916.  
  917.     /* Call the function. */
  918.  
  919.     clock = time((long *) 0);
  920.     tm = localtime(&clock);
  921.  
  922.     for (k = 0; next = array[k]; k++) {
  923.         length = strftime(string, MAXTIME, next, tm);
  924.         printf("%s\n", string);
  925.     }
  926.  
  927.     exit(0);
  928. }
  929. #endif    /* TEST_STRFTIME */
  930. EOF
  931. echo - 'date.1'
  932. cat << 'EOF' > 'date.1'
  933. .TH DATE 1
  934. .SH NAME
  935. date \- write the date and time
  936. .SH SYNOPSIS
  937. .B date
  938. [
  939. .B \-u
  940. ] [
  941. .RI + format
  942. ]
  943. .SH DESCRIPTION
  944. .I Date
  945. writes the current date and time to the standard output.
  946. It is intended to be compliant with the Posix
  947. 1003.2 Command Language and Utilities standard.
  948. Therefore, it is purposely
  949. .I not
  950. usable by the super-user for setting the system time.
  951. .LP
  952. .I Date
  953. accepts one option:
  954. .TP
  955. .B \-u
  956. Perform operations as if the
  957. .B TZ
  958. environment variable was set to
  959. .BR GMT0 .
  960. .LP
  961. If an argument to 
  962. .I date
  963. is given that begins with a ``+'',
  964. then the output is controlled by the contents of the rest of
  965. the string.  Normal text is output unmodified, while field descriptors
  966. in the format string are substituted for.
  967. .LP
  968. The
  969. .I date
  970. program is essentially a wrapper around
  971. .IR strftime (3);
  972. see there for a description of the available formatting options.
  973. .LP
  974. If no format string is given, or if it does not begin with a ``+'',
  975. then the default format of \fB"%a %b %e %H:%M:%S %Z %Y"\fR will
  976. be used.  This produces the traditional style of output, such as
  977. ``Sun Mar 17 10:32:47 EST 1991''.
  978. .SH SEE ALSO
  979. time(2), strftime(3), localtime(3)
  980. .SH BUGS
  981. This version only works for the POSIX locale.
  982. .SH AUTHOR
  983. .nf
  984. Arnold Robbins
  985. .sp
  986. INTERNET: arnold@skeeve.atl.ga.us
  987. UUCP:     emory!skeeve!arnold
  988. Phone:    +1 404 248 9324
  989. .fi
  990. EOF
  991. echo - 'strftime.3'
  992. cat << 'EOF' > 'strftime.3'
  993. .TH STRFTIME 3
  994. .SH NAME
  995. strftime \- generate formatted time information
  996. .SH SYNOPSIS
  997. .ft B
  998. .nf
  999. #include <sys/types.h>
  1000. #include <time.h>
  1001. .sp
  1002. size_t strftime(char *s, size_t maxsize, const char *format,
  1003.     const struct tm *timeptr);
  1004. .SH DESCRIPTION
  1005. The following description is transcribed verbatim from the December 7, 1988
  1006. draft standard for ANSI C.
  1007. This draft is essentially identical in technical content
  1008. to the final version of the standard.
  1009. .LP
  1010. The
  1011. .B strftime
  1012. function places characters into the array pointed to by
  1013. .B s
  1014. as controlled by the string pointed to by
  1015. .BR format .
  1016. The format shall be a multibyte character sequence, beginning and ending in
  1017. its initial shift state.
  1018. The
  1019. .B format
  1020. string consists of zero or more conversion specifiers and ordinary
  1021. multibyte characters.  A conversion specifier consists of a
  1022. .B %
  1023. character followed by a character that determines the behavior of the
  1024. conversion specifier.
  1025. All ordinary multibyte characters (including the terminating null
  1026. character) are copied unchanged into the array.
  1027. If copying takes place between objects that overlap the behavior is undefined.
  1028. No more than
  1029. .B maxsize
  1030. characters are placed into the array.
  1031. Each conversion specifier is replaced by appropriate characters as described
  1032. in the following list.
  1033. The appropriate characters are determined by the
  1034. .B LC_TIME
  1035. category of the current locale and by the values contained in the
  1036. structure pointed to by
  1037. .BR timeptr .
  1038. .TP
  1039. .B %a
  1040. is replaced by the locale's abbreviated weekday name.
  1041. .TP
  1042. .B %A
  1043. is replaced by the locale's full weekday name.
  1044. .TP
  1045. .B %b
  1046. is replaced by the locale's abbreviated month name.
  1047. .TP
  1048. .B %B
  1049. is replaced by the locale's full month name.
  1050. .TP
  1051. .B %c
  1052. is replaced by the locale's appropriate date and time representation.
  1053. .TP
  1054. .B %d
  1055. is replaced by the day of the month as a decimal number
  1056. .RB ( 01 - 31 ).
  1057. .TP
  1058. .B %H
  1059. is replaced by the hour (24-hour clock) as a decimal number
  1060. .RB ( 00 - 23 ).
  1061. .TP
  1062. .B %I
  1063. is replaced by the hour (12-hour clock) as a decimal number
  1064. .RB ( 01 - 12 ).
  1065. .TP
  1066. .B %j
  1067. is replaced by the day of the year as a decimal number
  1068. .RB ( 001 - 366 ).
  1069. .TP
  1070. .B %m
  1071. is replaced by the month as a decimal number
  1072. .RB ( 01 - 12 ).
  1073. .TP
  1074. .B %M
  1075. is replaced by the minute as a decimal number
  1076. .RB ( 00 - 59 ).
  1077. .TP
  1078. .B %p
  1079. is replaced by the locale's equivalent of the AM/PM designations associated
  1080. with a 12-hour clock.
  1081. .TP
  1082. .B %S
  1083. is replaced by the second as a decimal number
  1084. .RB ( 00 - 61 ).
  1085. .TP
  1086. .B %U
  1087. is replaced by the week number of the year (the first Sunday as the first
  1088. day of week 1) as a decimal number
  1089. .RB ( 00 - 53 ).
  1090. .TP
  1091. .B %w
  1092. is replaced by the weekday as a decimal number
  1093. .RB [ "0 " (Sunday)- 6 ].
  1094. .TP
  1095. .B %W
  1096. is replaced by the week number of the year (the first Monday as the first
  1097. day of week 1) as a decimal number
  1098. .RB ( 00 - 53 ).
  1099. .TP
  1100. .B %x
  1101. is replaced by the locale's appropriate date representation.
  1102. .TP
  1103. .B %X
  1104. is replaced by the locale's appropriate time representation.
  1105. .TP
  1106. .B %y
  1107. is replaced by the year without century as a decimal number
  1108. .RB ( 00 - 99 ).
  1109. .TP
  1110. .B %Y
  1111. is replaced by the year with century as a decimal number.
  1112. .TP
  1113. .B %Z
  1114. is replaced by the time zone name or abbreviation, or by no characters if
  1115. no time zone is determinable.
  1116. .TP
  1117. .B %%
  1118. is replaced by
  1119. .BR % .
  1120. .LP
  1121. If a conversion specifier is not one of the above, the behavior is
  1122. undefined.
  1123. .SH RETURNS
  1124. If the total number of resulting characters including the terminating null
  1125. character is not more than
  1126. .BR maxsize ,
  1127. the
  1128. .B strftime
  1129. function returns the number of characters placed into the array pointed to
  1130. by
  1131. .B s
  1132. not including the terminating null character.
  1133. Otherwise, zero is returned and the contents of the array are indeterminate.
  1134. .SH NON-ANSI EXTENSIONS
  1135. If
  1136. .B SYSV_EXT
  1137. is defined when the routine is compiled, then the following additional
  1138. conversions will be available.
  1139. These are borrowed from the System V
  1140. .IR cftime (3)
  1141. and
  1142. .IR ascftime (3)
  1143. routines.
  1144. .TP
  1145. .B %D
  1146. is equivalent to specifying
  1147. .BR %m/%d/%y .
  1148. .TP
  1149. .B %e
  1150. is replaced by the day of the month,
  1151. padded with a blank if it is only one digit.
  1152. .TP
  1153. .B %h
  1154. is equivalent to
  1155. .BR %b ,
  1156. above.
  1157. .TP
  1158. .B %n
  1159. is replaced with a newline character (\s-1ASCII LF\s+1).
  1160. .TP
  1161. .B %r
  1162. is equivalent to specifying
  1163. .BR "%I:%M:%S %p" .
  1164. .TP
  1165. .B %R
  1166. is equivalent to specifying
  1167. .BR %H:%M .
  1168. .TP
  1169. .B %T
  1170. is equivalent to specifying
  1171. .BR %H:%M:%S .
  1172. .TP
  1173. .B %t
  1174. is replaced with a \s-1TAB\s+1 character.
  1175. .PP
  1176. If
  1177. .B SUNOS_EXT
  1178. is defined when the routine is compiled, then the following additional
  1179. conversions will be available.
  1180. These are borrowed from the SunOS version of
  1181. .IR strftime .
  1182. .TP
  1183. .B %k
  1184. is replaced by the hour (24-hour clock) as a decimal number
  1185. .RB ( 0 - 23 ).
  1186. Single digit numbers are padded with a blank.
  1187. .TP
  1188. .B %l
  1189. is replaced by the hour (12-hour clock) as a decimal number
  1190. .RB ( 1 - 12 ).
  1191. Single digit numbers are padded with a blank.
  1192. .SH POSIX 1003.2 EXTENSIONS
  1193. If
  1194. .B POSIX2_DATE
  1195. is defined, then all of the conversions available with
  1196. .B SYSV_EXT
  1197. and
  1198. .B SUNOS_EXT
  1199. are available, as well as the
  1200. following additional conversions:
  1201. .TP
  1202. .B %C
  1203. The century, as a number between 00 and 99.
  1204. .TP
  1205. .B %u
  1206. is replaced by the weekday as a decimal number
  1207. .RB [ "1 " (Monday)- 7 ].
  1208. .TP
  1209. .B %V
  1210. is replaced by the week number of the year (the first Monday as the first
  1211. day of week 1) as a decimal number
  1212. .RB ( 01 - 53 ).
  1213. The method for determining the week number is as specified by ISO 8601
  1214. (to wit: if the week containing January 1 has four or more days in the
  1215. new year, then it is week 1, otherwise it is the highest numbered
  1216. week of the previous year (52 or 53)
  1217. and the next week is week 1).
  1218. .LP
  1219. The text of the POSIX standard for the
  1220. .I date
  1221. utility describes
  1222. .B %U
  1223. and
  1224. .B %W
  1225. this way:
  1226. .TP
  1227. .B %U
  1228. is replaced by the week number of the year (the first Sunday as the first
  1229. day of week 1) as a decimal number
  1230. .RB ( 00 - 53 ).
  1231. All days in a new year preceding the first Sunday are considered to be
  1232. in week 0.
  1233. .TP
  1234. .B %W
  1235. is replaced by the week number of the year (the first Monday as the first
  1236. day of week 1) as a decimal number
  1237. .RB ( 00 - 53 ).
  1238. All days in a new year preceding the first Monday are considered to be
  1239. in week 0.
  1240. .LP
  1241. In addition, the alternate representations
  1242. .BR %Ec ,
  1243. .BR %EC ,
  1244. .BR %Ex ,
  1245. .BR %Ey ,
  1246. .BR %EY ,
  1247. .BR %Od ,
  1248. .BR %Oe ,
  1249. .BR %OH ,
  1250. .BR %OI ,
  1251. .BR %Om ,
  1252. .BR %OM ,
  1253. .BR %OS ,
  1254. .BR %Ou ,
  1255. .BR %OU ,
  1256. .BR %OV ,
  1257. .BR %Ow ,
  1258. .BR %OW ,
  1259. and
  1260. .B %Oy
  1261. are recognized, but their normal representations are used.
  1262. .SH VMS EXTENSIONS
  1263. If
  1264. .B VMS_EXT
  1265. is defined, then the following additional conversion is available:
  1266. .TP
  1267. .B %v
  1268. The date in VMS format (e.g. 20-JUN-1991).
  1269. .SH SEE ALSO
  1270. .IR time (2),
  1271. .IR ctime (3),
  1272. .IR localtime (3),
  1273. .IR tzset (3)
  1274. .SH BUGS
  1275. This version does not handle multibyte characters or pay attention to the
  1276. setting of the
  1277. .B LC_TIME
  1278. environment variable.
  1279. .LP
  1280. It is not clear what is ``appropriate'' for the C locale; the values
  1281. returned are a best guess on the author's part.
  1282. .SH CAVEATS
  1283. The pre-processor symbol
  1284. .B POSIX_SEMANTICS
  1285. is automatically defined, which forces the code to call
  1286. .IR tzset (3)
  1287. whenever the
  1288. .B TZ
  1289. environment variable has changed.
  1290. If this routine will be used in an application that will not be changing
  1291. .BR TZ ,
  1292. then there may be some performance improvements by not defining
  1293. .BR POSIX_SEMANTICS .
  1294. .SH AUTHOR
  1295. .nf
  1296. Arnold Robbins
  1297. .sp
  1298. INTERNET: arnold@skeeve.atl.ga.us
  1299. UUCP:     emory!skeeve!arnold
  1300. Phone:    +1 404 248 9324
  1301. .fi
  1302. .SH ACKNOWLEDGEMENTS
  1303. Thanks to Geoff Clare <gwc@root.co.uk> for helping debug earlier
  1304. versions of this routine, and for advice about POSIX semantics.
  1305. Additional thanks to Arthur David Olsen <ado@elsie.nci.nih.gov>
  1306. for some code improvements.
  1307. Thanks also to Tor Lillqvist <tml@tik.vtt.fi>
  1308. for code fixes to the ISO 8601 code.
  1309. EOF
  1310.