/*-------------------------------------------------------------------------
 *
 * nabstime.c--
 *    parse almost any absolute date getdate(3) can (& some it can't)
 *
 * Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *    $Header: /cvsroot/pgsql/src/backend/utils/adt/nabstime.c,v 1.24 1997/04/22 17:36:57 scrappy Exp $
 *
 *-------------------------------------------------------------------------
 */
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <sys/types.h>

#include "postgres.h"
#include <miscadmin.h>
#ifdef HAVE_FLOAT_H
# include <float.h>
#endif
#ifdef HAVE_LIMITS_H
# include <limits.h>
#endif
#ifndef USE_POSIX_TIME
#include <sys/timeb.h>
#endif
#include "utils/builtins.h"
#include "access/xact.h"


#define MIN_DAYNUM -24856			/* December 13, 1901 */
#define MAX_DAYNUM 24854			/* January 18, 2038 */


/* GetCurrentAbsoluteTime()
 * Get the current system time. Set timezone parameters if not specified elsewhere.
 * Define HasTZSet to allow clients to specify the default timezone.
 *
 * Returns the number of seconds since epoch (January 1 1970 GMT)
 */

AbsoluteTime
GetCurrentAbsoluteTime(void)
{
    time_t now;

#ifdef USE_POSIX_TIME
    now = time(NULL);
#else /* ! USE_POSIX_TIME */
    struct timeb tbnow;		/* the old V7-ism */

    (void) ftime(&tbnow);
    now = tbnow.time;
#endif

    if (! HasCTZSet) {
#ifdef USE_POSIX_TIME
#if defined(HAVE_TZSET) && defined(HAVE_INT_TIMEZONE)
	tzset();
	CTimeZone = timezone;
	CDayLight = daylight;
	strcpy( CTZName, tzname[0]);
#else /* !HAVE_TZSET */
	struct tm *tmnow = localtime(&now);

	CTimeZone = - tmnow->tm_gmtoff;	/* tm_gmtoff is Sun/DEC-ism */
	CDayLight = (tmnow->tm_isdst > 0);
	/* XXX is there a better way to get local timezone string in V7? - tgl 97/03/18 */
	strftime( CTZName, MAXTZLEN, "%Z", localtime(&now));
#endif
#else /* ! USE_POSIX_TIME */
	CTimeZone = tbnow.timezone * 60;
	CDayLight = (tbnow.dstflag != 0);
	/* XXX does this work to get the local timezone string in V7? - tgl 97/03/18 */
	strftime( CTZName, MAXTZLEN, "%Z", localtime(&now));
#endif 
    };

#ifdef DATEDEBUG
printf( "GetCurrentAbsoluteTime- timezone is %s -> %d seconds from UTC\n",
 CTZName, CTimeZone);
#endif

    return((AbsoluteTime) now);
} /* GetCurrentAbsoluteTime() */


void
GetCurrentTime(struct tm *tm)
{
    abstime2tm( GetCurrentTransactionStartTime(), &CTimeZone, tm);

    return;
} /* GetCurrentTime() */


void
abstime2tm(AbsoluteTime time, int *tzp, struct tm *tm)
{
    struct tm *tt;

#if FALSE
    if (tzp != NULL) time -= *tzp;
    tt = gmtime((time_t *) &time);
#endif
    /* XXX HACK to get time behavior compatible with Postgres v6.0 - tgl 97/04/07 */
    if (tzp != NULL) {
	tt = localtime((time_t *) &time);
    } else {
	tt = gmtime((time_t *) &time);
    };

    tm->tm_year = tt->tm_year+1900;
    tm->tm_mon = tt->tm_mon+1;
    tm->tm_mday = tt->tm_mday;
    tm->tm_hour = tt->tm_hour;
    tm->tm_min = tt->tm_min;
    tm->tm_sec = tt->tm_sec;
    tm->tm_isdst = tt->tm_isdst;

    return;
} /* abstime2tm() */


/* tm2abstime()
 * Convert a tm structure to abstime.
 * Note that tm has full year (not 1900-based) and 1-based month.
 */
AbsoluteTime
tm2abstime( struct tm *tm, int tz)
{
    int day, sec;

    /* validate, before going out of range on some members */
    if (tm->tm_year < 1901 || tm->tm_year > 2038
      || tm->tm_mon < 1 || tm->tm_mon > 12
      || tm->tm_mday < 1 || tm->tm_mday > 31
      || tm->tm_hour < 0 || tm->tm_hour >= 24
      || tm->tm_min < 0 || tm->tm_min > 59
      || tm->tm_sec < 0 || tm->tm_sec > 59)
	return(INVALID_ABSTIME);

    day = (date2j( tm->tm_year, tm->tm_mon, tm->tm_mday) - date2j( 1970, 1, 1));

    /* check for time out of range */
    if ((day < MIN_DAYNUM) || (day > MAX_DAYNUM))
	return(INVALID_ABSTIME);

    /* convert to seconds */
    sec = tm->tm_sec + tz + (tm->tm_min +(day*24 + tm->tm_hour)*60)*60;

    /* check for overflow */
    if ((day == MAX_DAYNUM && sec < 0) ||
      (day == MIN_DAYNUM && sec > 0))
	return(INVALID_ABSTIME);

    /* daylight correction */
    if (tm->tm_isdst < 0) {		/* unknown; find out */
	tm->tm_isdst = (CDayLight > 0);
    };
    if (tm->tm_isdst > 0)
	sec -= 60*60;

    /* check for reserved values (e.g. "current" on edge of usual range */
    if (!AbsoluteTimeIsReal(sec))
	return(INVALID_ABSTIME);

    return(sec);
} /* tm2abstime() */


/* nabstimein()
 * Decode date/time string and return abstime.
 */
AbsoluteTime
nabstimein(char* str)
{
    AbsoluteTime result;

    double fsec;
    int tz = 0;
    struct tm date, *tm = &date;

    char *field[MAXDATEFIELDS];
    char lowstr[MAXDATELEN+1];
    int dtype;
    int nf, ftype[MAXDATEFIELDS];

    if (!PointerIsValid(str))
	elog(WARN,"Bad (null) abstime external representation",NULL);

    if (strlen(str) > MAXDATELEN)
	elog( WARN, "Bad (length) abstime external representation '%s'",str);

    if ((ParseDateTime( str, lowstr, field, ftype, MAXDATEFIELDS, &nf) != 0)
     || (DecodeDateTime( field, ftype, nf, &dtype, tm, &fsec, &tz) != 0))
	elog( WARN, "Bad abstime external representation '%s'",str);

#ifdef DATEDEBUG
printf( "nabstimein- %d fields are type %d (DTK_DATE=%d)\n", nf, dtype, DTK_DATE);
#endif

    switch (dtype) {
    case DTK_DATE:
	result = tm2abstime(tm, tz);
	break;

    case DTK_EPOCH:
	result = EPOCH_ABSTIME;
	break;

    case DTK_CURRENT:
	result = CURRENT_ABSTIME;
	break;

    case DTK_LATE:
	result = NOEND_ABSTIME;
	break;

    case DTK_EARLY:
	result = NOSTART_ABSTIME;
	break;

    case DTK_INVALID:
	result = INVALID_ABSTIME;
	break;

    default:
	elog(WARN,"Bad abstime (internal coding error) '%s'",str);
	result = INVALID_ABSTIME;
	break;
    };

    return result;
} /* nabstimein() */


/*
 * Given an AbsoluteTime return the English text version of the date
 */
char *
nabstimeout(AbsoluteTime time)
{
    /*
     * Fri Jan 28 23:05:29 1994 PST
     * 0        1         2
     * 12345678901234567890123456789
     *
     * we allocate some extra -- timezones are usually 3 characters but
     * this is not in the POSIX standard...
     */
    char buf[40];
    char* result;

    switch (time) {
    case EPOCH_ABSTIME:	  (void) strcpy(buf, EPOCH);	break;
    case INVALID_ABSTIME: (void) strcpy(buf, INVALID);	break;
    case CURRENT_ABSTIME: (void) strcpy(buf, DCURRENT);	break;
    case NOEND_ABSTIME:   (void) strcpy(buf, LATE);	break;
    case NOSTART_ABSTIME: (void) strcpy(buf, EARLY);	break;
    default:
	/* hack -- localtime happens to work for negative times */
	(void) strftime(buf, sizeof(buf), "%a %b %d %H:%M:%S %Y %Z",
			localtime((time_t *) &time));
	break;
    }
    result = (char*)palloc(strlen(buf) + 1);
    strcpy(result, buf);
    return result;
}


/* turn a (struct tm) and a few variables into a time_t, with range checking */
AbsoluteTime
dateconv(register struct tm *tm, int zone)
{
    tm->tm_wday = tm->tm_yday = 0;

#if FALSE
    if (tm->tm_year < 70) {
	tm->tm_year += 2000;
    } else if (tm->tm_year < 1000) {
	tm->tm_year += 1900;
    };
#endif

    /* validate, before going out of range on some members */
    if (tm->tm_year < 1901 || tm->tm_year > 2038
      || tm->tm_mon < 1 || tm->tm_mon > 12
      || tm->tm_mday < 1 || tm->tm_mday > 31
      || tm->tm_hour < 0 || tm->tm_hour >= 24
      || tm->tm_min < 0 || tm->tm_min > 59
      || tm->tm_sec < 0 || tm->tm_sec > 59)
	return INVALID_ABSTIME;

    /*
     * zone should really be -zone, and tz should be set to tp->value, not
     * -tp->value.  Or the table could be fixed.
     */
    tm->tm_sec += zone;		/* mktime lets it be out of range */

    /* convert to seconds */
    return qmktime(tm);
}


/*
 * near-ANSI qmktime suitable for use by dateconv; not necessarily as paranoid
 * as ANSI requires, and it may not canonicalise the struct tm.  Ignores tm_wday
 * and tm_yday.
 */
time_t
qmktime(struct tm *tm)
{
    time_t sec;

    int day;

#if FALSE
    /* If it was a 2 digit year */
    if (tm->tm_year < 100)
	tm->tm_year += 1900;
#endif

    day = (date2j( tm->tm_year, tm->tm_mon, tm->tm_mday) - date2j( 1970, 1, 1));

    /* check for time out of range */
    if ((day < MIN_DAYNUM) || (day > MAX_DAYNUM))
	return INVALID_ABSTIME;

    /* convert to seconds */
    sec = tm->tm_sec + (tm->tm_min +(day*24 + tm->tm_hour)*60)*60;

    /* check for overflow */
    if ((day == MAX_DAYNUM && sec < 0) ||
	(day == MIN_DAYNUM && sec > 0))
	return INVALID_ABSTIME;

    /* check for reserved values (e.g. "current" on edge of usual range */
    if (!AbsoluteTimeIsReal(sec))
	return INVALID_ABSTIME;

    /* daylight correction */
    if (tm->tm_isdst < 0) {		/* unknown; find out */
	tm->tm_isdst = (CDayLight > 0);
    };
    if (tm->tm_isdst > 0)
	sec -= 60*60;

    return sec;
} /* qmktime() */


/*
 *  AbsoluteTimeIsBefore -- true iff time1 is before time2.
 *  AbsoluteTimeIsBefore -- true iff time1 is after time2.
 */
bool
AbsoluteTimeIsBefore(AbsoluteTime time1, AbsoluteTime time2)
{
    Assert(AbsoluteTimeIsValid(time1));
    Assert(AbsoluteTimeIsValid(time2));

    if (time1 == CURRENT_ABSTIME)
	time1 = GetCurrentTransactionStartTime();

    if (time2 == CURRENT_ABSTIME)
	time2 = GetCurrentTransactionStartTime();

    return (time1 < time2);
}

bool
AbsoluteTimeIsAfter(AbsoluteTime time1, AbsoluteTime time2)
{
    Assert(AbsoluteTimeIsValid(time1));
    Assert(AbsoluteTimeIsValid(time2));

    if (time1 == CURRENT_ABSTIME)
	time1 = GetCurrentTransactionStartTime();

    if (time2 == CURRENT_ABSTIME)
	time2 = GetCurrentTransactionStartTime();

    return (time1 > time2);
}


/* abstime_finite()
 */
bool
abstime_finite(AbsoluteTime abstime)
{
    return((abstime != INVALID_ABSTIME)
      && (abstime != NOSTART_ABSTIME) && (abstime != NOEND_ABSTIME));
} /* abstime_datetime() */


/*
 *	abstimeeq	- returns 1, iff arguments are equal
 *	abstimene	- returns 1, iff arguments are not equal
 *	abstimelt	- returns 1, iff t1 less than t2
 *	abstimegt	- returns 1, iff t1 greater than t2
 *	abstimele	- returns 1, iff t1 less than or equal to t2
 *	abstimege	- returns 1, iff t1 greater than or equal to t2
 */
bool
abstimeeq(AbsoluteTime t1, AbsoluteTime t2)
{
    if (t1 == INVALID_ABSTIME || t2 == INVALID_ABSTIME)
	return(FALSE);
    if (t1 == CURRENT_ABSTIME)
	t1 = GetCurrentTransactionStartTime();
    if (t2 == CURRENT_ABSTIME)
	t2 = GetCurrentTransactionStartTime();

    return(t1 == t2);
}

bool
abstimene(AbsoluteTime t1, AbsoluteTime t2)
{ 
    if (t1 == INVALID_ABSTIME || t2 == INVALID_ABSTIME)
	return(FALSE);
    if (t1 == CURRENT_ABSTIME)
	t1 = GetCurrentTransactionStartTime();
    if (t2 == CURRENT_ABSTIME)
	t2 = GetCurrentTransactionStartTime();

    return(t1 != t2);
}

bool
abstimelt(AbsoluteTime t1, AbsoluteTime t2)
{ 
    if (t1 == INVALID_ABSTIME || t2 == INVALID_ABSTIME)
	return(FALSE);
    if (t1 == CURRENT_ABSTIME)
	t1 = GetCurrentTransactionStartTime();
    if (t2 == CURRENT_ABSTIME)
	t2 = GetCurrentTransactionStartTime();

    return(t1 < t2);
}

bool
abstimegt(AbsoluteTime t1, AbsoluteTime t2)
{ 
    if (t1 == INVALID_ABSTIME || t2 == INVALID_ABSTIME)
	return(FALSE);
    if (t1 == CURRENT_ABSTIME)
	t1 = GetCurrentTransactionStartTime();
    if (t2 == CURRENT_ABSTIME)
	t2 = GetCurrentTransactionStartTime();

    return(t1 > t2);
}

bool
abstimele(AbsoluteTime t1, AbsoluteTime t2)
{ 
    if (t1 == INVALID_ABSTIME || t2 == INVALID_ABSTIME)
	return(FALSE);
    if (t1 == CURRENT_ABSTIME)
	t1 = GetCurrentTransactionStartTime();
    if (t2 == CURRENT_ABSTIME)
	t2 = GetCurrentTransactionStartTime();

    return(t1 <= t2);
}

bool
abstimege(AbsoluteTime t1, AbsoluteTime t2)
{ 
    if (t1 == INVALID_ABSTIME || t2 == INVALID_ABSTIME)
	return(FALSE);
    if (t1 == CURRENT_ABSTIME)
	t1 = GetCurrentTransactionStartTime();
    if (t2 == CURRENT_ABSTIME)
	t2 = GetCurrentTransactionStartTime();

    return(t1 >= t2);
}


/* datetime_abstime()
 * Convert datetime to abstime.
 */
AbsoluteTime
datetime_abstime(DateTime *datetime)
{
    AbsoluteTime result;

    double fsec;
    struct tm tt, *tm = &tt;

    if (!PointerIsValid(datetime)) {
	result = INVALID_ABSTIME;

    } else if (DATETIME_IS_INVALID(*datetime)) {
	result = INVALID_ABSTIME;

    } else if (DATETIME_IS_NOBEGIN(*datetime)) {
	result = NOSTART_ABSTIME;

    } else if (DATETIME_IS_NOEND(*datetime)) {
	result = NOEND_ABSTIME;

    } else {
	if (DATETIME_IS_RELATIVE(*datetime)) {
	    datetime2tm( SetDateTime(*datetime), &CTimeZone, tm, &fsec);
	    result = tm2abstime( tm, 0);

	} else if (datetime2tm( *datetime, &CTimeZone, tm, &fsec) == 0) {
	    result = tm2abstime( tm, 0);

	} else {
	    result = INVALID_ABSTIME;
	};
    };

    return(result);
} /* datetime_abstime() */

/* abstime_datetime()
 * Convert datetime to abstime.
 */
DateTime *
abstime_datetime(AbsoluteTime abstime)
{
    DateTime *result;

    if (!PointerIsValid(result = PALLOCTYPE(DateTime)))
	elog(WARN,"Unable to allocate space to convert abstime to datetime",NULL);

    switch (abstime) {
    case INVALID_ABSTIME:
	DATETIME_INVALID(*result);
	break;

    case NOSTART_ABSTIME:
	DATETIME_NOBEGIN(*result);
	break;

    case NOEND_ABSTIME:
	DATETIME_NOEND(*result);
	break;

    case EPOCH_ABSTIME:
	DATETIME_EPOCH(*result);
	break;

    case CURRENT_ABSTIME:
	DATETIME_CURRENT(*result);
	break;

    default:
	*result = abstime + ((date2j( 1970, 1, 1) - date2j( 2000, 1, 1))*86400);
	break;
    };

    return(result);
} /* abstime_datetime() */
