/* dtime.c - routines to do ``ARPA-style'' time structures

ver  date   who remarks
--- ------- --- -------------------------------------------------------------
01B 15nov86 JP  Thouroughly hacked by Jef Poskanzer.
01A ??????? MTR Original version from the MH 6.5 distribution, courtesy
	          of Marshall Rose.

*/


#include "tws.h"
#include <stdio.h>
#include <sys/types.h>
#include <time.h>
#ifdef  SYS5
#include <string.h>
#else SYS5
#include <strings.h>
#include <sys/timeb.h>
#endif SYS5

#ifdef	SYS5
extern int  daylight;
extern long timezone;
extern char *tzname[];
#endif	SYS5

/*  */

#define	abs(a) ( a >= 0 ? a : -a )

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

char *tw_dotw[] = {
    "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", NULL };

char *tw_ldotw[] = {
    "Sunday", "Monday", "Tuesday", "Wednesday",
    "Thursday", "Friday", "Saturday", NULL };

/*  */

static struct zone
    {
    char *std;
    char *dst;
    int shift;
    }
    zones[] = {
	"GMT", "BST", 0,
	"EST", "EDT", -5,
	"CST", "CDT", -6,
	"MST", NULL, -7,
	"PST", "PDT", -8,
	"A", NULL, -1,
	"B", NULL, -2,
	"C", NULL, -3,
	"D", NULL, -4,
	"E", NULL, -5,
	"F", NULL, -6,
	"G", NULL, -7,
	"H", NULL, -8,
	"I", NULL, -9,
	"K", NULL, -10,
	"L", NULL, -11,
	"M", NULL, -12,
	"N", NULL, 1,
#ifndef	HUJI
	"O", NULL, 2,
#else	HUJI
	"JST", "JDT", 2,
#endif	HUJI
	"P", NULL, 3,
	"Q", NULL, 4,
	"R", NULL, 5,
	"S", NULL, 6,
	"T", NULL, 7,
	"U", NULL, 8,
	"V", NULL, 9,
	"W", NULL, 10,
	"X", NULL, 11,
	"Y", NULL, 12,
	NULL };

#define CENTURY 19

long time( );
struct tm *localtime( );

/*  */

char *dtimenow( )
    {
    long clock;

    (void) time( &clock );
    return ( dtime( &clock ) );
    }


char *
dctime( tw )
struct tws *tw;
    {
    static char buffer[25];

    if ( tw == NULL )
	return ( NULL );

    (void) sprintf( buffer, "%.3s %.3s %02d %02d:%02d:%02d %.4d\n",
	    tw_dotw[tw -> tw_wday], tw_moty[tw -> tw_mon], tw -> tw_mday,
	    tw -> tw_hour, tw -> tw_min, tw -> tw_sec,
	    tw -> tw_year >= 100 ? tw -> tw_year : 1900 + tw -> tw_year );

    return ( buffer );
    }

/*  */

struct tws *
dtwstime( )
    {
    long clock;

    (void) time( &clock );
    return ( dlocaltime( &clock ) );
    }


struct tws *
dlocaltime( clock )
long *clock;
    {
    register struct tm *tm;
#ifndef SYS5
    struct timeb tb;
#endif not SYS5
    static struct tws tw;

    if ( clock == NULL )
	return ( NULL );
    tw.tw_flags = TW_NULL;

    tm = localtime( clock );
    tw.tw_sec = tm -> tm_sec;
    tw.tw_min = tm -> tm_min;
    tw.tw_hour = tm -> tm_hour;
    tw.tw_mday = tm -> tm_mday;
    tw.tw_mon = tm -> tm_mon;
    tw.tw_year = tm -> tm_year;
    tw.tw_wday = tm -> tm_wday;
    tw.tw_yday = tm -> tm_yday;
    if ( tm -> tm_isdst )
	tw.tw_flags |= TW_DST;
#ifndef  SYS5
    ftime( &tb );
    tw.tw_zone = -tb.timezone;
#else   SYS5
    tzset( );
    tw.tw_zone = -(timezone / 60);
#endif  SYS5
    tw.tw_flags &= ~TW_SDAY;
    tw.tw_flags |= TW_SEXP;
    tw.tw_clock = *clock;

    return ( &tw );
    }


struct tws *
dgmtime( clock )
long *clock;
    {
    register struct tm *tm;
    static struct tws tw;

    if ( clock == NULL )
	return ( NULL );
    tw.tw_flags = TW_NULL;

    tm = gmtime( clock );
    tw.tw_sec = tm -> tm_sec;
    tw.tw_min = tm -> tm_min;
    tw.tw_hour = tm -> tm_hour;
    tw.tw_mday = tm -> tm_mday;
    tw.tw_mon = tm -> tm_mon;
    tw.tw_year = tm -> tm_year;
    tw.tw_wday = tm -> tm_wday;
    tw.tw_yday = tm -> tm_yday;
    if ( tm -> tm_isdst )
	tw.tw_flags |= TW_DST;
    tw.tw_zone = 0;
    tw.tw_flags &= ~TW_SDAY;
    tw.tw_flags |= TW_SEXP;
    tw.tw_clock = *clock;

    return( &tw );
    }

/*  */

char *
dasctime( tw, flags )
struct tws *tw;
int flags;
    {
    static char buffer[80], result[80];

    if ( tw == NULL )
	return ( NULL );

    (void) sprintf( buffer, "%02d %s %02d %02d:%02d:%02d %s",
	    tw -> tw_mday, tw_moty[tw -> tw_mon], tw -> tw_year,
	    tw -> tw_hour, tw -> tw_min, tw -> tw_sec,
	    dtimezone( tw -> tw_zone, tw -> tw_flags | flags ) );

    if ( (tw -> tw_flags & TW_SDAY) == TW_SEXP )
	(void) sprintf( result, "%s, %s", tw_dotw[tw -> tw_wday], buffer );
    else
	if ( (tw -> tw_flags & TW_SDAY) == TW_SNIL )
	    (void) strcpy( result, buffer );
	else
	    (void) sprintf( result, "%s (%s)", buffer, tw_dotw[tw -> tw_wday] );

    return ( result );
    }

/*  */

char *
dtimezone( offset, flags )
int offset, flags;
    {
    register int hours, mins;
    register struct zone *z;
    static char buffer[10];

    if ( offset < 0 )
	{
	mins = -((-offset) % 60);
	hours = -((-offset) / 60);
	}
    else
	{
	mins = offset % 60;
	hours = offset / 60;
	}

    if ( !(flags & TW_ZONE) && mins == 0 )
	for ( z = zones; z -> std; z++ )
	    if ( z -> shift == hours )
		return ( z -> dst && (flags & TW_DST) ? z -> dst : z -> std );

#ifdef	DSTXXX
    if ( flags & TW_DST )
	hours += 1;
#endif	DSTXXX
    (void) sprintf( buffer, "%s%02d%02d",
	    offset < 0 ? "-" : "+", abs( hours ), abs( mins ) );
    return ( buffer );
    }

/*  */

void
twscopy( tb, tw )
struct tws *tb, *tw;
    {
#ifdef	notdef
    tb -> tw_sec = tw -> tw_sec;
    tb -> tw_min = tw -> tw_min;
    tb -> tw_hour = tw -> tw_hour;
    tb -> tw_mday = tw -> tw_mday;
    tb -> tw_mon = tw -> tw_mon;
    tb -> tw_year = tw -> tw_year;
    tb -> tw_wday = tw -> tw_wday;
    tb -> tw_yday = tw -> tw_yday;
    tb -> tw_zone = tw -> tw_zone;
    tb -> tw_clock = tw -> tw_clock;
    tb -> tw_flags = tw -> tw_flags;
#else	not notdef
    *tb = *tw;
#endif	not notdef
    }


int
twsort( tw1, tw2 )
struct tws *tw1, *tw2;
    {
    register long c1, c2;

    (void) twclock( tw1 );
    (void) twclock( tw2 );

    return ( (c1 = tw1 -> tw_clock) > (c2 = tw2 -> tw_clock) ? 1
	    : c1 == c2 ? 0 : -1 );
    }

/*  */


/* Julian day number of the Unix* clock's origin, 01 Jan 1970. */
#define JD1970 2440587L


long
twjuliandate( tw )
struct tws *tw;
    {
    register int mday, mon, year;
    register long a, b;
    double jd;

    if ( (mday = tw -> tw_mday) < 1 || mday > 31 ||
	    (mon = tw -> tw_mon + 1) < 1 || mon > 12 ||
	    (year = tw -> tw_year) < 1 || year > 10000 )
	return ( -1L );
    if ( year < 100 )
	year += CENTURY * 100;

    if ( mon == 1 || mon == 2 )
	{
	--year;
	mon += 12;
	}
    if ( year < 1583 )
	return ( -1L );
    a = year / 100;
    b = 2 - a + a / 4;
    b += (long) ( (double) year * 365.25 );
    b += (long) ( 30.6001 * ( (double) mon + 1.0 ) );
    jd = mday + b + 1720994.5;
    return ( (long) jd );
    }


long
twsubdayclock( tw )
struct tws *tw;
    {
    register int sec, min, hour;
    register long result;

    if ( (sec = tw -> tw_sec) < 0 || sec > 59 ||
	    (min = tw -> tw_min) < 0 || min > 59 ||
	    (hour = tw -> tw_hour) < 0 || hour > 23 )
	return ( -1L );

    result = ( hour * 60 + min ) * 60 + sec;
    result -= 60 * tw -> tw_zone;
    if ( tw -> tw_flags & TW_DST )
	result -= 60 * 60;

    return ( result );
    }


long
twclock( tw )
struct tws *tw;
    {
    register long jd, sdc, result;

    if ( tw -> tw_clock != 0L )
	return ( tw -> tw_clock );

    if ( ( jd = twjuliandate( tw ) ) == -1L )
	return ( tw -> tw_clock = -1L );
    if ( ( sdc = twsubdayclock( tw ) ) == -1L )
	return ( tw -> tw_clock = -1L );

    result = ( jd - JD1970 ) * 24 * 60 * 60 + sdc;

    return ( tw -> tw_clock = result );
    }

/*  */

/*** twsubtract - subtract tw2 from tw1, returning result in seconds

The point of this routine is that using twclock( tw1 ) - twclock( tw2 )
would limit you to dates after the Unix* Epoch ( 01 January 1970 ).  This
routine avoids that limit.  However, because the result is represented
by 32 bits, it is still limited to a span of two billion seconds, which is
about 66 years.

*/

long
twsubtract( tw1, tw2 )
struct tws *tw1, *tw2;
    {
    register long jd1, jd2, sdc1, sdc2, result;

    if ( ( jd1 = twjuliandate( tw1 ) ) == -1L )
	return ( 0L );
    if ( ( sdc1 = twsubdayclock( tw1 ) ) == -1L )
	return ( 0L );

    if ( ( jd2 = twjuliandate( tw2 ) ) == -1L )
	return ( 0L );
    if ( ( sdc2 = twsubdayclock( tw2 ) ) == -1L )
	return ( 0L );
    
    result = ( jd1 - jd2 ) * 24 * 60 * 60 + ( sdc1 - sdc2 );

    return ( result );
    }

/*  */

/*
 *    Simple calculation of day of the week.  Algorithm used is Zeller's
 *    congruence.  Currently, we assume if tw -> tw_year < 100
 *    then the century is CENTURY.
 */

set_dotw( tw )
struct tws *tw;
    {
    register int month, day, year, century;

    month = tw -> tw_mon - 1;
    day = tw -> tw_mday;
    year = tw -> tw_year % 100;
    century = tw -> tw_year >= 100 ? tw -> tw_year / 100 : CENTURY;

    if ( month <= 0 )
	{
	month += 12;
	if ( --year < 0 )
	    {
	    year += 100;
	    century--;
	    }
	}

    tw -> tw_wday =
	((26 * month - 2) / 10 + day + year + year / 4
	    - 3 * century / 4 + 1) % 7;

    tw -> tw_flags &= ~TW_SDAY;
    tw -> tw_flags |= TW_SIMP;
    }


/* * Unix is a virus from outer space. */
