[comp.lang.c] Where is strftime

edh@ux.acs.umn.edu (Merlinus Ambrosius) (08/11/90)

In the standard library in K&R (ANSI) on page 256 there is a very nice
looking procedure for formatting the current time into whatever form you
like.  It doesn't seem to be present on this system, or on a local Sparc.
Anybody know where I can get this?  What it does (according to the book)
is:

size_t strftime(char *s, size_t smax, const char *fmt, const struct tm *tp)

It formats date and time info from *tp into s according the fmt, using a
printf-like format.  i.e.  "%y%m%d %H%M%S" would put 900810 045835 into s.

This is similar to how "date" does it too, of course, but if something like
strftime already exists, I could sure use it!

All help is appreciated,
			Eric Hendrickson
-- 
/---------"Oh carrots are divine, you get a dozen for dime, its maaaagic."---
|Eric (the "Mentat-Philosopher") Hendrickson	  Academic Computing Services
|edh@ux.acs.umn.edu	   The game is afoot!	      University of Minnesota
\----"What does 'masochist' and 'amnesia' mean?   Beats me, I don't know."---

henry@zoo.toronto.edu (Henry Spencer) (08/12/90)

In article <2048@ux.acs.umn.edu> edh@ux.acs.umn.edu (Eric D. Hendrickson) writes:
>In the standard library in K&R (ANSI) on page 256 there is a very nice
>looking procedure for formatting the current time into whatever form you
>like.  It doesn't seem to be present on this system, or on a local Sparc.

Many systems are not yet ANSI-compliant.

I don't know of any redistributable strftime() implementation.
-- 
It is not possible to both understand  | Henry Spencer at U of Toronto Zoology
and appreciate Intel CPUs. -D.Wolfskill|  henry@zoo.toronto.edu   utzoo!henry

alf@xenon.stgt.sub.org (Ingo Feulner) (08/12/90)

In article <1990Aug11.230626.2706@zoo.toronto.edu>, henry@zoo.toronto.edu (Henry Spencer) writes:
> In article <2048@ux.acs.umn.edu> edh@ux.acs.umn.edu (Eric D. Hendrickson) writes:
> >In the standard library in K&R (ANSI) on page 256 there is a very nice
> >looking procedure for formatting the current time into whatever form you
> >like.  It doesn't seem to be present on this system, or on a local Sparc.
> 
> Many systems are not yet ANSI-compliant.
Hope that will change in the near future...
> 
> I don't know of any redistributable strftime() implementation.

Here is one. It comes with a public domain C compiler named Sozobon C (for
the Amiga), so I think I can post this.

-Enjoy, Ingo.

-------------------------snip----------------------snap--------------

/*
 *  STRFTIME.C
 */

#include <time.h>
#include <string.h>
#include <stddef.h>

static char *AbMonth[12] = { "Jan","Feb","Mar","Apr","May","Jun","Jul",
			     "Aug","Sep","Oct","Nov","Dec"
			    };

static char *FuMonth[12] = { "January", "February", "March", "April", "May",
			     "June", "July", "August", "September", "October",
			     "November", "December"
			    };

static char *AbDow[12] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };

static char *FuDow[12] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday",
			   "Friday", "Saturday"
			};


size_t
strftime(buf, max, fmt, tm)
char *buf;
size_t max;
const char *fmt;
const struct tm *tm;
{
    short i = 0;

    buf[i] = 0;

    while (*fmt) {
	char *ptr;

	i += strlen(buf + i);

	if (*fmt != '%') {
	    buf[i++] = *fmt++;
	    continue;
	}
	ptr = buf + i;
	fmt += 2;
	switch(fmt[-1]) {
	case '%':
	    strcpy(ptr, "%");
	    break;
	case 'a':   /*  abbreviated name for dow    */
	    strcpy(ptr, AbDow[tm->tm_wday]);
	    break;
	case 'A':   /*  full name for dow           */
	    strcpy(ptr, FuDow[tm->tm_wday]);
	    break;
	case 'b':   /*  abbreviated name for month  */
	    strcpy(ptr, AbMonth[tm->tm_mon]);
	    break;
	case 'B':   /*  full name for month         */
	    strcpy(ptr, FuMonth[tm->tm_mon]);
	    break;
	case 'c':   /*  default rep for date & time */
	    sprintf(ptr, "%s %s %02d %02d:%02d:%02d %d",
		AbDow[tm->tm_wday], AbMonth[tm->tm_mon], tm->tm_mday,
		tm->tm_hour, tm->tm_min, tm->tm_sec, tm->tm_year + 1900
	    );
	    break;
	case 'd':   /*  day of month as integer 01-31   */
	    sprintf(ptr, "%02d", tm->tm_mday);
	    break;
	case 'H':   /*  hour as integer 00-23       */
	    sprintf(ptr, "%02d", tm->tm_hour);
	    break;
	case 'I':   /*  hour as integer 01-12       */
	    sprintf(ptr, "%02d", (tm->tm_hour % 12) + 1);
	    break;
	case 'j':   /*  day of year as int 001-366  */
	    sprintf(ptr, "%03d", tm->tm_yday);
	    break;
	case 'm':   /*  month as integer 01-12      */
	    sprintf(ptr, "%02d", tm->tm_mon);
	    break;
	case 'M':   /*  minute as integer 00-59     */
	    sprintf(ptr, "%02d", tm->tm_min);
	    break;
	case 'p':   /*  'AM' or 'PM'                */
	    if (tm->tm_hour >= 12) {
		strcpy(ptr, "PM");
	    } else {
		strcpy(ptr, "AM");
	    }
	    break;
	case 'S':   /*  the second as an int 00-59  */
	    sprintf(ptr, "%02d", tm->tm_sec);
	    break;
	case 'U':   /*  week of year as int 00-53, regard sunday as first day in week   */
	    {
		int bdiw = tm->tm_yday - tm->tm_wday;	/* beginning of week */
		if (bdiw < 0)
		    bdiw = 0;
		else
		    bdiw = bdiw / 7;
		sprintf(ptr, "%02d", bdiw);
	    }
	    break;
	case 'w':   /*  day of week as int 0-6, sunday == 0 */
	    sprintf(ptr, "%d", tm->tm_wday);
	    break;
	case 'W':   /*  day of week as int 0-6, monday == 0 */
	    {
		int dowmon = tm->tm_wday - 1;
		if (dowmon < 0)
		    dowmon += 7;
		sprintf(ptr, "%d", dowmon);
	    }
	    break;
	case 'x':   /*  the locale's default rep for date   */
	    sprintf(ptr, "%s %s %02d",
		AbDow[tm->tm_wday], AbMonth[tm->tm_mon], tm->tm_mday
	    );
	    break;
	case 'X':   /*  the locale's default rep for time   */
	    sprintf(ptr, "%02d:%02d:%02d",
		tm->tm_hour, tm->tm_min, tm->tm_sec
	    );
	    break;
	case 'y':   /*  year within the century 00-99       */
	    sprintf(ptr, "%02d", tm->tm_year % 100);
	    break;
	case 'Y':   /*  the full year, including century    */
	    sprintf(ptr, "%04d", tm->tm_year + 1900);
	    break;
	case 'Z':   /*  the name of the time zone or ""     */
	    strcpy(ptr, "");
	    break;
	default:
	    strcpy(ptr, "%?");
	    break;
	}
    }
    i += strlen(buf + i);
    return((size_t)i);
}

-------------------------------snip--------------snap----------------


--
                     Ingo Feulner - alf@xenon.stgt.sub.org
     Wolfacher Weg 22 - 7030 Boeblingen - (+49) 7031 272691 - West Germany
                    Love your enemies. It'll make 'em crazy.
                          AMIGA - the only way to go!

alf@xenon.stgt.sub.org (Ingo Feulner) (08/12/90)

In article <2658@xenon.stgt.sub.org>, alf@xenon.stgt.sub.org (Ingo Feulner) writes:
> Here is one. It comes with a public domain C compiler named Sozobon C (for
> the Amiga), so I think I can post this.

Sorry, I must correct me. This routine comes with DICE, the C compiler
written by Matt Dillon.

-Ingo

--
                     Ingo Feulner - alf@xenon.stgt.sub.org
     Wolfacher Weg 22 - 7030 Boeblingen - (+49) 7031 272691 - West Germany
                    Love your enemies. It'll make 'em crazy.
                          AMIGA - the only way to go!

karl@haddock.ima.isc.com (Karl Heuer) (08/13/90)

In article <1990Aug11.230626.2706@zoo.toronto.edu> henry@zoo.toronto.edu (Henry Spencer) writes:
>I don't know of any redistributable strftime() implementation.

There's a strftime() in the Olson-Harris ctime package; I'm not sure of its
redistributability.  I've heard the whole package is PD, but there's a
Berkeley copyright notice in strftime.c.

Karl W. Z. Heuer (karl@kelp.ima.isc.com or ima!kelp!karl), The Walking Lint

zvs@bby.oz.au (Zev Sero) (08/16/90)

In article <2658@xenon.stgt.sub.org> alf@xenon.stgt.sub.org (Ingo Feulner)
supplies a strftime() function.

A few problems:

  size_t
  strftime(buf, max, fmt, tm)
  char *buf;
  size_t max;
  const char *fmt;
  const struct tm *tm;
  {

All very well, but nowhere in the function is max ever checked!
_______
      short i = 0;
      buf[i] = 0;
      while (*fmt) {
  	  char *ptr;
>	  i += strlen(buf + i);
	  if (*fmt != '%') {
	      buf[i++] = *fmt++;
	      continue;
	  }

What on earth is the function of the line indicated?  I can't imagine
what the author is *trying* to do, but here is how it will actually
work (or rather, not work).

Imagine the following setup:
buf is passed to strftime() containing "Some message",
and fmt contains "Date: %A %d %B %Y".
  
The first thing strftime() does is 
      short i = 0;
      buf[i] = 0;
so buf now contains 
{'\0','o','m','e',' ','m','e','s','s','a','g','e','\0'}.
Now *fmt is 'D', so we enter the loop.
      while (*fmt) {
	  i += strlen(buf + i);
strlen (buf + 0) is 0, so i still equals 0.  
	  if (*fmt != '%') {
	      buf[i++] = *fmt++;
	      continue;
buf now contains
{'D','o','m','e',' ','m','e','s','s','a','g','e','\0'},
i is 1, and fmt points to "ate: %A %d %B %Y".
Now *fmt is 'a', so we enter the loop.
      while (*fmt) {
	  i += strlen(buf + i);
strlen (buf + 1) is 11, so i is now 12!
_________
	case '%':
	    strcpy(ptr, "%");
	    break;
And many similar cases.  Surely 
	    *ptr = '%';
would be much more efficient!
_________
	case 'I':   /*  hour as integer 01-12       */
	    sprintf(ptr, "%02d", (tm->tm_hour % 12) + 1);
	    break;
Say what???!!!  This should, of course, be something like
	    sprintf(ptr, "%02d",
		 tm->tm_hour % 12 ? tm->tm_hour % 12 : 12);
_________
	case 'j':   /*  day of year as int 001-366  */
	    sprintf(ptr, "%03d", tm->tm_yday);
	    break;
	case 'm':   /*  month as integer 01-12      */
	    sprintf(ptr, "%02d", tm->tm_mon);
	    break;
Must add 1 in both cases
_________
	case 'p':   /*  'AM' or 'PM'                */
	    if (tm->tm_hour >= 12) {
		strcpy(ptr, "PM");
	    } else {
		strcpy(ptr, "AM");
	    }
	    break;
Would be better as
            *ptr++ = tm->tm_hour < 12 ? 'A' : 'P';
	    *ptr = 'M';
_________
	case 'U':   /*  week of year as int 00-53, regard sunday as first day in week   */
	    {
		int bdiw = tm->tm_yday - tm->tm_wday;	/* beginning of week */
		if (bdiw < 0)
		    bdiw = 0;
		else
		    bdiw = bdiw / 7;
		sprintf(ptr, "%02d", bdiw);
	    }
	    break;

This will only work if the year begins on a Sunday.  Consider what
happens if the year begins on a Monday.  Sunday 7 Jan has a yday of 6,
and a wday of 0.  (6 - 0) / 7  gives an answer of 0.  The correct
answer is 1.  This code should be:
	case 'U':  /* Sunday week of the year */
		sprintf (ptr, "%02d", (tm->yday + 6 - tm->wday) / 7);
		break;
_________
	case 'W':   /*  day of week as int 0-6, monday == 0 */
	    {
		int dowmon = tm->tm_wday - 1;
		if (dowmon < 0)
		    dowmon += 7;
		sprintf(ptr, "%d", dowmon);
	    }
	    break;
This is wrong.  %W is `the Monday week of the year, from 00', i.e. it
is exactly the same as %U except that the week is considered to run
from Monday to Sunday.  The correct formula is:
	case 'W':  /* Monday week of the year */
	    sprintf (ptr, "%02d",
	        (tm->yday + 6 - (tm->wday ? tm->wday - 1 : 6)) / 7);
________
	case 'x':   /*  the locale's default rep for date   */
	    sprintf(ptr, "%s %s %02d",
		AbDow[tm->tm_wday], AbMonth[tm->tm_mon], tm->tm_mday
	    );
	    break;
	case 'X':   /*  the locale's default rep for time   */
	    sprintf(ptr, "%02d:%02d:%02d",
		tm->tm_hour, tm->tm_min, tm->tm_sec
	    );
	    break;
These are obviously not fully implemented.  Fair enough, provided that
the documentation makes this clear.
________
	case 'y':   /*  year within the century 00-99       */
	    sprintf(ptr, "%02d", tm->tm_year % 100);
	    break;
Should be (tm->tm_year + 1900) % 100, to handle years before 1900.
(Years BCE are not handled properly anyway by lots of things, and it's
too much bother to try).
________
	case 'Z':   /*  the name of the time zone or ""     */
	    strcpy(ptr, "");
	    break;
What does this do?  If you've decided to do nothing because you don't
know how to find the correct timezone (and I don't know how either),
then why not 
	case 'Z': /* I don't know how to do this */
		break;
Why the useless strcpy()?
This should be documented, so that individual users can change the
code to put in their local timezone.  Our implementation of strftime()
here simply puts in "EST", and this is clearly documented.
________
    i += strlen(buf + i);
What does this do?
_______
    return((size_t)i);
i should have been defined as a size_t in the first place.  If you
have already made the (quite reasonable) assumption that the size of
the buffer will fit comfortably in an int, why cast it now?
--
				Zev Sero  -  zvs@bby.oz.au
Violence is not a pleasant thing. It has caused much suffering in the world
since its invention, and many are convinced that it is Quite A Bad Thing.
					- Steven Megachiropter Foster

ado@elsie.UUCP (Arthur David Olson) (08/18/90)

> I don't know of any redistributable strftime() implementation.

See pub/tz89.shar.Z on uunet.uu.net for a redistributable strftime
(among other things).
-- 
	Arthur David Olson   ado@alw.nih.gov   ADO is a trademark of Ampex.