[comp.unix.questions] Inverse for localtime

paulr@sequent.UUCP (Paul Reger) (08/26/89)

A while back someone asked if there was such a thing as an inverse
localtime() function. (localtime() maps a time_t * to a struct *tm).
I guess there isn't one, so I wrote one (The problem interested me
since it seemed natural for one to exist.).  Here it is:
 
#define Seconds_per_minute 60
#define Seconds_per_hour   60*Seconds_per_minute
#define Seconds_per_day    24*Seconds_per_hour
#define Seconds_per_month  30*Seconds_per_day
#define Seconds_per_year   365*Seconds_per_day

#include <sys/types.h>
#include <sys/time.h>

time_t
tm_to_time_t(t)
struct tm t;
{
    time_t high_guess = 0,low_guess = 0,middle;
    struct tm *temp;
    int score_high,score_low,nyears;
    static const int fudges[12] = { 0*Seconds_per_day, /* Jan. */
				    1*Seconds_per_day, /* Feb. */
				   -1*Seconds_per_day, /* Mar. */
				    0*Seconds_per_day, /* Apr. */
				    0*Seconds_per_day, /* May  */
				    1*Seconds_per_day, /* Jun. */
				    1*Seconds_per_day, /* Jul. */
				    2*Seconds_per_day, /* Aug. */
				    3*Seconds_per_day, /* Sep. */
				    3*Seconds_per_day, /* Oct. */
				    4*Seconds_per_day, /* Nov. */
				    4*Seconds_per_day  /* Dec. */    };

    nyears = t.tm_year - 70;
    low_guess = Seconds_per_year*nyears +
             (double) ((nyears-1)*Seconds_per_day) * 0.24 +  /* add in leap days... */
	     Seconds_per_month*t.tm_mon +
	     fudges[t.tm_mon] +                         /* Add in approximate days for 'non-30' day months. */
	    Seconds_per_day*(t.tm_mday - 1) + Seconds_per_hour*t.tm_hour + Seconds_per_minute*t.tm_min + t.tm_sec;
    high_guess = low_guess + 2*Seconds_per_day;  /* The initial guess will
    	    	    	    	        	    always be about 1 and a maximum of two days off.  Proof
    	    	    	    	        	    left to the reader. */

    while (abs((high_guess - low_guess)) > 1) {
	middle = (high_guess + low_guess) / 2;
	temp = localtime(&middle);

	score_high = ((temp->tm_year > t.tm_year) << 5) + ((temp->tm_mon > t.tm_mon) << 4) + ((temp->tm_mday > t.tm_mday) << 3) +
		((temp->tm_hour > t.tm_hour) << 2) + ((temp->tm_min > t.tm_min) << 1) + (temp->tm_sec > t.tm_sec);
	score_low = ((temp->tm_year < t.tm_year) << 5) + ((temp->tm_mon < t.tm_mon) << 4) + ((temp->tm_mday < t.tm_mday) << 3) +
		((temp->tm_hour < t.tm_hour) << 2) + ((temp->tm_min < t.tm_min) << 1) + (temp->tm_sec < t.tm_sec);

	if (score_high > score_low)
		high_guess = middle;
	else
		low_guess = middle;
    }
    return middle;
}

main(argc,argv)
    int argc;
    char *argv[];
{
    struct tm z;
    time_t    t;

    z.tm_hour = atoi(argv[1]);
    z.tm_min  = atoi(argv[2]);
    z.tm_sec  = atoi(argv[3]);
    z.tm_mday = atoi(argv[4]);
    z.tm_mon  = atoi(argv[5]);
    z.tm_year = atoi(argv[6]);

    t = tm_to_time_t(z);
    printf("%d is the time representation for: %s",t,ctime(&t));
}


All the standard disclaimers apply.  I only tested this a little bit.

Also, my solution is based on a binary search and is very costly.
Anyone know of a better solution ??

Note also that the elements in the struct besides the six above tm are
ignored.

Still also note that for some reason, the seconds are off by one some
times.

alfie@cs.warwick.ac.uk (Nick Holloway) (08/29/89)

In article <20750@sequent.UUCP> paulr@sequent.UUCP (Paul Reger) writes:
> A while back someone asked if there was such a thing as an inverse
> localtime() function. (localtime() maps a time_t * to a struct *tm).
> I guess there isn't one, so I wrote one (The problem interested me
> since it seemed natural for one to exist.).  Here it is:
> 	[ ... code ... ]

SunOS 4.x (possibly also 3.x) has a routine called timelocal(), which
takes (struct tm*) and returns a time_t. If there is no valid time,
(you give 1:30am, on the day when the clock goes forward an hour at
1:00am) a value of -1 is returned. There is also a routine timegm(),
which is to timegm() as timelocal() is to localtime().

These also only uses the tm_hour, tm_min, tm_sec, tm_mday, tm_mon, tm_year
fields in the structure [discovered by experimentation].

I thought it was natural for there to be an inverse to localtime().
Does this mean that this is another useful SunOS'ism [like statfs(2)]
only available on Suns, or will this spread to other platforms?

	Alfie
--
In real life: Nick Holloway <alfie@cs.warwick.ac.uk>

guy@auspex.auspex.com (Guy Harris) (08/31/89)

>SunOS 4.x (possibly also 3.x)

No, not 3.x - it first appeared in 4.0.

>I thought it was natural for there to be an inverse to localtime().

So do a lot of people, but the notion of a standard inverse function of
that sort is relatively recent, alas....

>Does this mean that this is another useful SunOS'ism [like statfs(2)]
>only available on Suns,

"statfs()" was, originally, a SunOS-ism, but is hardly available only on
Suns.  System V Release 3 picked up the idea, and has a slightly
different version of it (same name, though); it also comes with the NFS
source distribution, so any version of UNIX that includes NFS where the
people who put NFS into their system knew what they were doing also
includes "statfs()".  It's a useful function on any UNIX system that
supports multiple file system types - whether local (V7/S5 file system,
BSD file system, etc.) or remote (NFS, RFS, etc.).  (S5R4 will have
"statvfs", which is "statfs" with some additions, as well as "statfs".)

"timelocal()" originally appeared in the Arthur Olson time zone code,
which was picked up by Sun in SunOS 4.0.  It was presented as
"standard-inspired", since it does not appear in any standard, but was
inspired by the "mktime()" routine proposed for the ANSI C standard.

>or will this spread to other platforms?

I don't know that "timelocal()" will - in fact, it'll be deprecated in
SunOS 4.1, in favor of "mktime()", although it will still be present and
supported.  However, I expect "mktime()" - which, as long as you set
"tm_isdst" of the "struct tm" you pass to it to a negative value,
behaves the same as "timelocal()" - to spread to most platforms, since
the proposed ANSI C standard requires it.  The latest version of the
Arthur Olson time zone code includes "mktime()", and it will be present
in SunOS 4.1.