home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 October / usenetsourcesnewsgroupsinfomagicoctober1994disk2.iso / unix / volume27 / strftime-6.0 / part01 next >
Encoding:
Text File  |  1994-02-22  |  29.7 KB  |  1,253 lines

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