diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c index 4ca3850f79..7102f64a44 100644 --- a/src/backend/utils/adt/date.c +++ b/src/backend/utils/adt/date.c @@ -1,1303 +1,559 @@ /*------------------------------------------------------------------------- * * date.c - * Utilities for the built-in type "AbsoluteTime" (defined in nabstime). - * Functions for the built-in type "RelativeTime". - * Functions for the built-in type "TimeInterval". + * implements DATE and TIME data types specified in SQL-92 standard * * Portions Copyright (c) 1996-2000, PostgreSQL, Inc - * Portions Copyright (c) 1994, Regents of the University of California + * Portions Copyright (c) 1994-5, Regents of the University of California * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/utils/adt/date.c,v 1.41 2000/01/26 05:57:13 momjian Exp $ - * - * NOTES - * This code is actually (almost) unused. - * It needs to be integrated with Time and struct trange. - * - * XXX This code needs to be rewritten to work with the "new" definitions - * XXX in h/tim.h. Look for int32's, int, long, etc. in the code. The - * XXX definitions in h/tim.h may need to be rethought also. - * - * XXX This code has been cleaned up some - avi 07/07/93 + * $Header: /cvsroot/pgsql/src/backend/utils/adt/date.c,v 1.42 2000/02/16 18:17:02 thomas Exp $ * *------------------------------------------------------------------------- */ -#include -#include -#include -#include +#include #include "postgres.h" #ifdef HAVE_FLOAT_H #include #endif -#ifdef HAVE_LIMITS_H -#include -#ifndef MAXINT -#define MAXINT INT_MAX -#endif -#else -#ifdef HAVE_VALUES_H -#include -#endif -#endif - -#include "access/xact.h" #include "miscadmin.h" #include "utils/builtins.h" -#include "utils/dt.h" -#define INVALID_RELTIME_STR "Undefined RelTime" -#define INVALID_RELTIME_STR_LEN (sizeof(INVALID_RELTIME_STR)-1) -#define RELTIME_LABEL '@' -#define RELTIME_PAST "ago" -#define DIRMAXLEN (sizeof(RELTIME_PAST)-1) +static int +date2tm(DateADT dateVal, int *tzp, struct tm * tm, double *fsec, char **tzn); -/* - * Unix epoch is Jan 1 00:00:00 1970. Postgres knows about times - * sixty-eight years on either side of that. - */ - -#define IsSpace(C) ((C) == ' ') - -#define T_INTERVAL_INVAL 0 /* data represents no valid interval */ -#define T_INTERVAL_VALID 1 /* data represents a valid interval */ -/* - * ['Mon May 10 23:59:12 1943 PST' 'Sun Jan 14 03:14:21 1973 PST'] - * 0 1 2 3 4 5 6 - * 1234567890123456789012345678901234567890123456789012345678901234 - * - * we allocate some extra -- timezones are usually 3 characters but - * this is not in the POSIX standard... - */ -#define T_INTERVAL_LEN 80 -#define INVALID_INTERVAL_STR "Undefined Range" -#define INVALID_INTERVAL_STR_LEN (sizeof(INVALID_INTERVAL_STR)-1) - -#define ABSTIMEMIN(t1, t2) abstimele((t1),(t2)) ? (t1) : (t2) -#define ABSTIMEMAX(t1, t2) abstimelt((t1),(t2)) ? (t2) : (t1) - -#ifdef NOT_USED -static char *unit_tab[] = { - "second", "seconds", "minute", "minutes", - "hour", "hours", "day", "days", "week", "weeks", -"month", "months", "year", "years"}; - -#define UNITMAXLEN 7 /* max length of a unit name */ -#define NUNITS 14 /* number of different units */ - -/* table of seconds per unit (month = 30 days, year = 365 days) */ -static int sec_tab[] = { - 1, 1, 60, 60, - 3600, 3600, 86400, 86400, 604800, 604800, -2592000, 2592000, 31536000, 31536000}; - -#endif - -/* - * Function prototypes -- internal to this file only - */ - -static void reltime2tm(RelativeTime time, struct tm * tm); - -#ifdef NOT_USED -static int correct_unit(char *unit, int *unptr); -static int correct_dir(char *direction, int *signptr); - -#endif - -static int istinterval(char *i_string, - AbsoluteTime *i_start, - AbsoluteTime *i_end); /***************************************************************************** - * USER I/O ROUTINES * + * Date ADT *****************************************************************************/ -/* - * reltimein - converts a reltime string in an internal format - */ -RelativeTime -reltimein(char *str) -{ - RelativeTime result; +/* date_in() + * Given date text string, convert to internal date format. + */ +DateADT +date_in(char *str) +{ + DateADT date; + double fsec; struct tm tt, *tm = &tt; - double fsec; + int tzp; int dtype; + int nf; char *field[MAXDATEFIELDS]; - int nf, - ftype[MAXDATEFIELDS]; + int ftype[MAXDATEFIELDS]; char lowstr[MAXDATELEN + 1]; if (!PointerIsValid(str)) elog(ERROR, "Bad (null) date external representation"); - if (strlen(str) > MAXDATELEN) - elog(ERROR, "Bad (length) reltime external representation '%s'", str); - if ((ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf) != 0) - || (DecodeDateDelta(field, ftype, nf, &dtype, tm, &fsec) != 0)) - elog(ERROR, "Bad reltime external representation '%s'", str); + || (DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tzp) != 0)) + elog(ERROR, "Bad date external representation '%s'", str); switch (dtype) { - case DTK_DELTA: - result = ((((tm->tm_hour * 60) + tm->tm_min) * 60) + tm->tm_sec); - result += (((tm->tm_year * 365) + (tm->tm_mon * 30) + tm->tm_mday) * (24 * 60 * 60)); - return result; + case DTK_DATE: + break; + + case DTK_CURRENT: + GetCurrentTime(tm); + break; + + case DTK_EPOCH: + tm->tm_year = 1970; + tm->tm_mon = 1; + tm->tm_mday = 1; + break; default: - return INVALID_RELTIME; + elog(ERROR, "Unrecognized date external representation '%s'", str); } - elog(ERROR, "Bad reltime (internal coding error) '%s'", str); - return INVALID_RELTIME; -} /* reltimein() */ + date = (date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - date2j(2000, 1, 1)); + return date; +} /* date_in() */ -/* - * reltimeout - converts the internal format to a reltime string +/* date_out() + * Given internal format date, convert to text string. */ char * -reltimeout(RelativeTime time) +date_out(DateADT date) { char *result; struct tm tt, *tm = &tt; char buf[MAXDATELEN + 1]; - if (time == INVALID_RELTIME) - { - strcpy(buf, INVALID_RELTIME_STR); + j2date((date + date2j(2000, 1, 1)), + &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday)); - } - else - { - reltime2tm(time, tm); - EncodeTimeSpan(tm, 0, DateStyle, buf); - } + EncodeDateOnly(tm, DateStyle, buf); result = palloc(strlen(buf) + 1); + strcpy(result, buf); return result; -} /* reltimeout() */ +} /* date_out() */ - -#define TMODULO(t,q,u) \ -do { \ - q = (t / u); \ - if (q != 0) t -= (q * u); \ -} while(0) - -static void -reltime2tm(RelativeTime time, struct tm * tm) +bool +date_eq(DateADT dateVal1, DateADT dateVal2) { - TMODULO(time, tm->tm_year, 31536000); - TMODULO(time, tm->tm_mon, 2592000); - TMODULO(time, tm->tm_mday, 86400); - TMODULO(time, tm->tm_hour, 3600); - TMODULO(time, tm->tm_min, 60); - TMODULO(time, tm->tm_sec, 1); + return dateVal1 == dateVal2; +} - return; -} /* reltime2tm() */ +bool +date_ne(DateADT dateVal1, DateADT dateVal2) +{ + return dateVal1 != dateVal2; +} + +bool +date_lt(DateADT dateVal1, DateADT dateVal2) +{ + return dateVal1 < dateVal2; +} /* date_lt() */ + +bool +date_le(DateADT dateVal1, DateADT dateVal2) +{ + return dateVal1 <= dateVal2; +} /* date_le() */ + +bool +date_gt(DateADT dateVal1, DateADT dateVal2) +{ + return dateVal1 > dateVal2; +} /* date_gt() */ + +bool +date_ge(DateADT dateVal1, DateADT dateVal2) +{ + return dateVal1 >= dateVal2; +} /* date_ge() */ -#ifdef NOT_USED int -dummyfunc() +date_cmp(DateADT dateVal1, DateADT dateVal2) { - char *timestring; - long quantity; - int i; - int unitnr; + if (dateVal1 < dateVal2) + return -1; + else if (dateVal1 > dateVal2) + return 1; + return 0; +} /* date_cmp() */ - timestring = (char *) palloc(Max(strlen(INVALID_RELTIME_STR), - UNITMAXLEN) + 1); - if (timevalue == INVALID_RELTIME) - { - strcpy(timestring, INVALID_RELTIME_STR); - return timestring; - } +DateADT +date_larger(DateADT dateVal1, DateADT dateVal2) +{ + return date_gt(dateVal1, dateVal2) ? dateVal1 : dateVal2; +} /* date_larger() */ - if (timevalue == 0) - i = 1; /* unit = 'seconds' */ - else - for (i = 12; i >= 0; i = i - 2) - if ((timevalue % sec_tab[i]) == 0) - break; /* appropriate unit found */ - unitnr = i; - quantity = (timevalue / sec_tab[unitnr]); - if (quantity > 1 || quantity < -1) - unitnr++; /* adjust index for PLURAL of unit */ - if (quantity >= 0) - sprintf(timestring, "%c %lu %s", RELTIME_LABEL, - quantity, unit_tab[unitnr]); - else - sprintf(timestring, "%c %lu %s %s", RELTIME_LABEL, - (quantity * -1), unit_tab[unitnr], RELTIME_PAST); - return timestring; -} +DateADT +date_smaller(DateADT dateVal1, DateADT dateVal2) +{ + return date_lt(dateVal1, dateVal2) ? dateVal1 : dateVal2; +} /* date_smaller() */ -#endif - - -/* - * tintervalin - converts an interval string to an internal format +/* Compute difference between two dates in days. */ -TimeInterval -tintervalin(char *intervalstr) +int4 +date_mi(DateADT dateVal1, DateADT dateVal2) { - int error; - AbsoluteTime i_start, - i_end, - t1, - t2; - TimeInterval interval; + return dateVal1 - dateVal2; +} /* date_mi() */ - interval = (TimeInterval) palloc(sizeof(TimeIntervalData)); - error = istinterval(intervalstr, &t1, &t2); - if (error == 0) - interval->status = T_INTERVAL_INVAL; - if (t1 == INVALID_ABSTIME || t2 == INVALID_ABSTIME) - interval->status = T_INTERVAL_INVAL; /* undefined */ - else - { - i_start = ABSTIMEMIN(t1, t2); - i_end = ABSTIMEMAX(t1, t2); - interval->data[0] = i_start; - interval->data[1] = i_end; - interval->status = T_INTERVAL_VALID; - } - return interval; -} - - -/* - * tintervalout - converts an internal interval format to a string - * +/* Add a number of days to a date, giving a new date. + * Must handle both positive and negative numbers of days. */ -char * -tintervalout(TimeInterval interval) +DateADT +date_pli(DateADT dateVal, int4 days) { - char *i_str, - *p; + return dateVal + days; +} /* date_pli() */ - i_str = (char *) palloc(T_INTERVAL_LEN); /* ['...' '...'] */ - strcpy(i_str, "[\""); - if (interval->status == T_INTERVAL_INVAL) - strcat(i_str, INVALID_INTERVAL_STR); - else +/* Subtract a number of days from a date, giving a new date. + */ +DateADT +date_mii(DateADT dateVal, int4 days) +{ + return date_pli(dateVal, -days); +} /* date_mii() */ + + +/* date_timestamp() + * Convert date to timestamp data type. + */ +Timestamp * +date_timestamp(DateADT dateVal) +{ + Timestamp *result; + struct tm tt, + *tm = &tt; + int tz; + double fsec = 0; + char *tzn; + + result = palloc(sizeof(Timestamp)); + + if (date2tm(dateVal, &tz, tm, &fsec, &tzn) != 0) + elog(ERROR, "Unable to convert date to timestamp"); + + if (tm2timestamp(tm, fsec, &tz, result) != 0) + elog(ERROR, "Timestamp out of range"); + + return result; +} /* date_timestamp() */ + + +/* timestamp_date() + * Convert timestamp to date data type. + */ +DateADT +timestamp_date(Timestamp *timestamp) +{ + DateADT result; + struct tm tt, + *tm = &tt; + int tz; + double fsec; + char *tzn; + + if (!PointerIsValid(timestamp)) + elog(ERROR, "Unable to convert null timestamp to date"); + + if (TIMESTAMP_NOT_FINITE(*timestamp)) + elog(ERROR, "Unable to convert timestamp to date"); + + if (TIMESTAMP_IS_EPOCH(*timestamp)) { - p = nabstimeout(interval->data[0]); - strcat(i_str, p); - pfree(p); - strcat(i_str, "\" \""); - p = nabstimeout(interval->data[1]); - strcat(i_str, p); - pfree(p); + timestamp2tm(SetTimestamp(*timestamp), NULL, tm, &fsec, NULL); + } - strcat(i_str, "\"]\0"); - return i_str; -} - - -/***************************************************************************** - * PUBLIC ROUTINES * - *****************************************************************************/ - -RelativeTime -timespan_reltime(TimeSpan *timespan) -{ - RelativeTime time; - int year, - month; - double span; - - if (!PointerIsValid(timespan)) - time = INVALID_RELTIME; - - if (TIMESPAN_IS_INVALID(*timespan)) + else if (TIMESTAMP_IS_CURRENT(*timestamp)) { - time = INVALID_RELTIME; + timestamp2tm(SetTimestamp(*timestamp), &tz, tm, &fsec, &tzn); } else { - if (timespan->month == 0) - { - year = 0; - month = 0; - - } - else if (abs(timespan->month) >= 12) - { - year = (timespan->month / 12); - month = (timespan->month % 12); - - } - else - { - year = 0; - month = timespan->month; - } - - span = (((((double) 365 * year) + ((double) 30 * month)) * 86400) + timespan->time); - - time = (((span > INT_MIN) && (span < INT_MAX)) ? span : INVALID_RELTIME); + if (timestamp2tm(*timestamp, &tz, tm, &fsec, &tzn) != 0) + elog(ERROR, "Unable to convert timestamp to date"); } - return time; -} /* timespan_reltime() */ + result = (date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - date2j(2000, 1, 1)); + + return result; +} /* timestamp_date() */ -TimeSpan * -reltime_timespan(RelativeTime reltime) +/* abstime_date() + * Convert abstime to date data type. + */ +DateADT +abstime_date(AbsoluteTime abstime) { - TimeSpan *result; - int year, - month; + DateADT result; + struct tm tt, + *tm = &tt; + int tz; - if (!PointerIsValid(result = palloc(sizeof(TimeSpan)))) - elog(ERROR, "Memory allocation failed, can't convert reltime to timespan"); - - switch (reltime) + switch (abstime) { - case INVALID_RELTIME: - TIMESPAN_INVALID(*result); + case INVALID_ABSTIME: + case NOSTART_ABSTIME: + case NOEND_ABSTIME: + elog(ERROR, "Unable to convert reserved abstime value to date"); + + /* + * pretend to drop through to make compiler think that result + * will be set + */ + + case EPOCH_ABSTIME: + result = date2j(1970, 1, 1) - date2j(2000, 1, 1); + break; + + case CURRENT_ABSTIME: + GetCurrentTime(tm); + result = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - date2j(2000, 1, 1); break; default: - TMODULO(reltime, year, 31536000); - TMODULO(reltime, month, 2592000); - - result->time = reltime; - result->month = ((12 * year) + month); + abstime2tm(abstime, &tz, tm, NULL); + result = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - date2j(2000, 1, 1); + break; } return result; -} /* reltime_timespan() */ +} /* abstime_date() */ -/* - * mktinterval - creates a time interval with endpoints t1 and t2 +/* date2tm() + * Convert date to time structure. + * Note that date is an implicit local time, but the system calls assume + * that everything is GMT. So, convert to GMT, rotate to local time, + * and then convert again to try to get the time zones correct. */ -TimeInterval -mktinterval(AbsoluteTime t1, AbsoluteTime t2) +static int +date2tm(DateADT dateVal, int *tzp, struct tm * tm, double *fsec, char **tzn) { - AbsoluteTime tstart = ABSTIMEMIN(t1, t2), - tend = ABSTIMEMAX(t1, t2); - TimeInterval interval; + struct tm *tx; + time_t utime; - interval = (TimeInterval) palloc(sizeof(TimeIntervalData)); - if (t1 == INVALID_ABSTIME || t2 == INVALID_ABSTIME) - interval->status = T_INTERVAL_INVAL; + *fsec = 0; + + j2date((dateVal + date2j(2000, 1, 1)), &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday)); + tm->tm_hour = 0; + tm->tm_min = 0; + tm->tm_sec = 0; + tm->tm_isdst = -1; + + if (IS_VALID_UTIME(tm->tm_year, tm->tm_mon, tm->tm_mday)) + { + + /* convert to system time */ + utime = ((dateVal + (date2j(2000, 1, 1) - date2j(1970, 1, 1))) * 86400); + /* rotate to noon to get the right day in time zone */ + utime += (12 * 60 * 60); + +#ifdef USE_POSIX_TIME + tx = localtime(&utime); + + tm->tm_year = tx->tm_year + 1900; + tm->tm_mon = tx->tm_mon + 1; + tm->tm_mday = tx->tm_mday; + tm->tm_isdst = tx->tm_isdst; + +#if defined(HAVE_TM_ZONE) + tm->tm_gmtoff = tx->tm_gmtoff; + tm->tm_zone = tx->tm_zone; + + /* tm_gmtoff is Sun/DEC-ism */ + *tzp = -(tm->tm_gmtoff); + if (tzn != NULL) + *tzn = (char *) tm->tm_zone; +#elif defined(HAVE_INT_TIMEZONE) +#ifdef __CYGWIN__ + *tzp = (tm->tm_isdst ? (_timezone - 3600) : _timezone); +#else + *tzp = (tm->tm_isdst ? (timezone - 3600) : timezone); +#endif + if (tzn != NULL) + *tzn = tzname[(tm->tm_isdst > 0)]; +#else +#error USE_POSIX_TIME is defined but neither HAVE_TM_ZONE or HAVE_INT_TIMEZONE are defined +#endif +#else /* !USE_POSIX_TIME */ + *tzp = CTimeZone; /* V7 conventions; don't know timezone? */ + if (tzn != NULL) + *tzn = CTZName; +#endif + + /* otherwise, outside of timezone range so convert to GMT... */ + } else { - interval->status = T_INTERVAL_VALID; - interval->data[0] = tstart; - interval->data[1] = tend; + *tzp = 0; + tm->tm_isdst = 0; + if (tzn != NULL) + *tzn = NULL; } - return interval; -} - -/* - * timepl, timemi and abstimemi use the formula - * abstime + reltime = abstime - * so abstime - reltime = abstime - * and abstime - abstime = reltime - */ - -/* - * timepl - returns the value of (abstime t1 + relime t2) - */ -AbsoluteTime -timepl(AbsoluteTime t1, RelativeTime t2) -{ - if (t1 == CURRENT_ABSTIME) - t1 = GetCurrentTransactionStartTime(); - - if (AbsoluteTimeIsReal(t1) && - RelativeTimeIsValid(t2) && - ((t2 > 0) ? (t1 < NOEND_ABSTIME - t2) - : (t1 > NOSTART_ABSTIME - t2))) /* prevent overflow */ - return t1 + t2; - - return INVALID_ABSTIME; -} - - -/* - * timemi - returns the value of (abstime t1 - reltime t2) - */ -AbsoluteTime -timemi(AbsoluteTime t1, RelativeTime t2) -{ - if (t1 == CURRENT_ABSTIME) - t1 = GetCurrentTransactionStartTime(); - - if (AbsoluteTimeIsReal(t1) && - RelativeTimeIsValid(t2) && - ((t2 > 0) ? (t1 > NOSTART_ABSTIME + t2) - : (t1 < NOEND_ABSTIME + t2))) /* prevent overflow */ - return t1 - t2; - - return INVALID_ABSTIME; -} - - -/* - * abstimemi - returns the value of (abstime t1 - abstime t2) - */ -static RelativeTime -abstimemi(AbsoluteTime t1, AbsoluteTime t2) -{ - if (t1 == CURRENT_ABSTIME) - t1 = GetCurrentTransactionStartTime(); - if (t2 == CURRENT_ABSTIME) - t2 = GetCurrentTransactionStartTime(); - - if (AbsoluteTimeIsReal(t1) && - AbsoluteTimeIsReal(t2)) - return t1 - t2; - - return INVALID_RELTIME; -} - - -/* - * ininterval - returns 1, iff absolute date is in the interval - */ -int -ininterval(AbsoluteTime t, TimeInterval interval) -{ - if (interval->status == T_INTERVAL_VALID && t != INVALID_ABSTIME) - return (abstimege(t, interval->data[0]) && - abstimele(t, interval->data[1])); return 0; -} - -/* - * intervalrel - returns relative time corresponding to interval - */ -RelativeTime -intervalrel(TimeInterval interval) -{ - if (interval->status == T_INTERVAL_VALID) - return abstimemi(interval->data[1], interval->data[0]); - else - return INVALID_RELTIME; -} - -/* - * timenow - returns time "now", internal format - * - * Now AbsoluteTime is time since Jan 1 1970 -mer 7 Feb 1992 - */ -AbsoluteTime -timenow() -{ - time_t sec; - - if (time(&sec) < 0) - return INVALID_ABSTIME; - return (AbsoluteTime) sec; -} - -/* - * reltimeeq - returns 1, iff arguments are equal - * reltimene - returns 1, iff arguments are not equal - * reltimelt - returns 1, iff t1 less than t2 - * reltimegt - returns 1, iff t1 greater than t2 - * reltimele - returns 1, iff t1 less than or equal to t2 - * reltimege - returns 1, iff t1 greater than or equal to t2 - */ -bool -reltimeeq(RelativeTime t1, RelativeTime t2) -{ - if (t1 == INVALID_RELTIME || t2 == INVALID_RELTIME) - return 0; - return t1 == t2; -} - -bool -reltimene(RelativeTime t1, RelativeTime t2) -{ - if (t1 == INVALID_RELTIME || t2 == INVALID_RELTIME) - return 0; - return t1 != t2; -} - -bool -reltimelt(RelativeTime t1, RelativeTime t2) -{ - if (t1 == INVALID_RELTIME || t2 == INVALID_RELTIME) - return 0; - return t1 < t2; -} - -bool -reltimegt(RelativeTime t1, RelativeTime t2) -{ - if (t1 == INVALID_RELTIME || t2 == INVALID_RELTIME) - return 0; - return t1 > t2; -} - -bool -reltimele(RelativeTime t1, RelativeTime t2) -{ - if (t1 == INVALID_RELTIME || t2 == INVALID_RELTIME) - return 0; - return t1 <= t2; -} - -bool -reltimege(RelativeTime t1, RelativeTime t2) -{ - if (t1 == INVALID_RELTIME || t2 == INVALID_RELTIME) - return 0; - return t1 >= t2; -} - - -/* - * intervalsame - returns 1, iff interval i1 is same as interval i2 - * Check begin and end time. - */ -bool -intervalsame(TimeInterval i1, TimeInterval i2) -{ - if (i1->status == T_INTERVAL_INVAL || i2->status == T_INTERVAL_INVAL) - return FALSE; /* invalid interval */ - return (abstimeeq(i1->data[0], i2->data[0]) && - abstimeeq(i1->data[1], i2->data[1])); -} /* intervalsame() */ - - -/* - * intervaleq - returns 1, iff interval i1 is equal to interval i2 - * Check length of intervals. - */ -bool -intervaleq(TimeInterval i1, TimeInterval i2) -{ - AbsoluteTime t10, - t11, - t20, - t21; - - if (i1->status == T_INTERVAL_INVAL || i2->status == T_INTERVAL_INVAL) - return FALSE; /* invalid interval */ - - t10 = i1->data[0]; - t11 = i1->data[1]; - t20 = i2->data[0]; - t21 = i2->data[1]; - - if ((t10 == INVALID_ABSTIME) || (t20 == INVALID_ABSTIME) - || (t20 == INVALID_ABSTIME) || (t21 == INVALID_ABSTIME)) - return FALSE; - - if (t10 == CURRENT_ABSTIME) - t10 = GetCurrentTransactionStartTime(); - if (t11 == CURRENT_ABSTIME) - t11 = GetCurrentTransactionStartTime(); - if (t20 == CURRENT_ABSTIME) - t20 = GetCurrentTransactionStartTime(); - if (t21 == CURRENT_ABSTIME) - t21 = GetCurrentTransactionStartTime(); - - return (t11 - t10) == (t21 - t20); -} /* intervaleq() */ - -/* - * intervalne - returns 1, iff interval i1 is not equal to interval i2 - * Check length of intervals. - */ -bool -intervalne(TimeInterval i1, TimeInterval i2) -{ - AbsoluteTime t10, - t11, - t20, - t21; - - if (i1->status == T_INTERVAL_INVAL || i2->status == T_INTERVAL_INVAL) - return FALSE; /* invalid interval */ - - t10 = i1->data[0]; - t11 = i1->data[1]; - t20 = i2->data[0]; - t21 = i2->data[1]; - - if ((t10 == INVALID_ABSTIME) || (t20 == INVALID_ABSTIME) - || (t20 == INVALID_ABSTIME) || (t21 == INVALID_ABSTIME)) - return FALSE; - - if (t10 == CURRENT_ABSTIME) - t10 = GetCurrentTransactionStartTime(); - if (t11 == CURRENT_ABSTIME) - t11 = GetCurrentTransactionStartTime(); - if (t20 == CURRENT_ABSTIME) - t20 = GetCurrentTransactionStartTime(); - if (t21 == CURRENT_ABSTIME) - t21 = GetCurrentTransactionStartTime(); - - return (t11 - t10) != (t21 - t20); -} /* intervalne() */ - -/* - * intervallt - returns TRUE, iff interval i1 is less than interval i2 - * Check length of intervals. - */ -bool -intervallt(TimeInterval i1, TimeInterval i2) -{ - AbsoluteTime t10, - t11, - t20, - t21; - - if (i1->status == T_INTERVAL_INVAL || i2->status == T_INTERVAL_INVAL) - return FALSE; /* invalid interval */ - - t10 = i1->data[0]; - t11 = i1->data[1]; - t20 = i2->data[0]; - t21 = i2->data[1]; - - if ((t10 == INVALID_ABSTIME) || (t20 == INVALID_ABSTIME) - || (t20 == INVALID_ABSTIME) || (t21 == INVALID_ABSTIME)) - return FALSE; - - if (t10 == CURRENT_ABSTIME) - t10 = GetCurrentTransactionStartTime(); - if (t11 == CURRENT_ABSTIME) - t11 = GetCurrentTransactionStartTime(); - if (t20 == CURRENT_ABSTIME) - t20 = GetCurrentTransactionStartTime(); - if (t21 == CURRENT_ABSTIME) - t21 = GetCurrentTransactionStartTime(); - - return (t11 - t10) < (t21 - t20); -} /* intervallt() */ - -/* - * intervalle - returns TRUE, iff interval i1 is less than or equal to interval i2 - * Check length of intervals. - */ -bool -intervalle(TimeInterval i1, TimeInterval i2) -{ - AbsoluteTime t10, - t11, - t20, - t21; - - if (i1->status == T_INTERVAL_INVAL || i2->status == T_INTERVAL_INVAL) - return FALSE; /* invalid interval */ - - t10 = i1->data[0]; - t11 = i1->data[1]; - t20 = i2->data[0]; - t21 = i2->data[1]; - - if ((t10 == INVALID_ABSTIME) || (t20 == INVALID_ABSTIME) - || (t20 == INVALID_ABSTIME) || (t21 == INVALID_ABSTIME)) - return FALSE; - - if (t10 == CURRENT_ABSTIME) - t10 = GetCurrentTransactionStartTime(); - if (t11 == CURRENT_ABSTIME) - t11 = GetCurrentTransactionStartTime(); - if (t20 == CURRENT_ABSTIME) - t20 = GetCurrentTransactionStartTime(); - if (t21 == CURRENT_ABSTIME) - t21 = GetCurrentTransactionStartTime(); - - return (t11 - t10) <= (t21 - t20); -} /* intervalle() */ - -/* - * intervalgt - returns TRUE, iff interval i1 is less than interval i2 - * Check length of intervals. - */ -bool -intervalgt(TimeInterval i1, TimeInterval i2) -{ - AbsoluteTime t10, - t11, - t20, - t21; - - if (i1->status == T_INTERVAL_INVAL || i2->status == T_INTERVAL_INVAL) - return FALSE; /* invalid interval */ - - t10 = i1->data[0]; - t11 = i1->data[1]; - t20 = i2->data[0]; - t21 = i2->data[1]; - - if ((t10 == INVALID_ABSTIME) || (t20 == INVALID_ABSTIME) - || (t20 == INVALID_ABSTIME) || (t21 == INVALID_ABSTIME)) - return FALSE; - - if (t10 == CURRENT_ABSTIME) - t10 = GetCurrentTransactionStartTime(); - if (t11 == CURRENT_ABSTIME) - t11 = GetCurrentTransactionStartTime(); - if (t20 == CURRENT_ABSTIME) - t20 = GetCurrentTransactionStartTime(); - if (t21 == CURRENT_ABSTIME) - t21 = GetCurrentTransactionStartTime(); - - return (t11 - t10) > (t21 - t20); -} /* intervalgt() */ - -/* - * intervalge - returns TRUE, iff interval i1 is less than or equal to interval i2 - * Check length of intervals. - */ -bool -intervalge(TimeInterval i1, TimeInterval i2) -{ - AbsoluteTime t10, - t11, - t20, - t21; - - if (i1->status == T_INTERVAL_INVAL || i2->status == T_INTERVAL_INVAL) - return FALSE; /* invalid interval */ - - t10 = i1->data[0]; - t11 = i1->data[1]; - t20 = i2->data[0]; - t21 = i2->data[1]; - - if ((t10 == INVALID_ABSTIME) || (t20 == INVALID_ABSTIME) - || (t20 == INVALID_ABSTIME) || (t21 == INVALID_ABSTIME)) - return FALSE; - - if (t10 == CURRENT_ABSTIME) - t10 = GetCurrentTransactionStartTime(); - if (t11 == CURRENT_ABSTIME) - t11 = GetCurrentTransactionStartTime(); - if (t20 == CURRENT_ABSTIME) - t20 = GetCurrentTransactionStartTime(); - if (t21 == CURRENT_ABSTIME) - t21 = GetCurrentTransactionStartTime(); - - return (t11 - t10) >= (t21 - t20); -} /* intervalge() */ - - -/* - * intervalleneq - returns 1, iff length of interval i is equal to - * reltime t - */ -bool -intervalleneq(TimeInterval i, RelativeTime t) -{ - RelativeTime rt; - - if ((i->status == T_INTERVAL_INVAL) || (t == INVALID_RELTIME)) - return 0; - rt = intervalrel(i); - return rt != INVALID_RELTIME && rt == t; -} - -/* - * intervallenne - returns 1, iff length of interval i is not equal - * to reltime t - */ -bool -intervallenne(TimeInterval i, RelativeTime t) -{ - RelativeTime rt; - - if ((i->status == T_INTERVAL_INVAL) || (t == INVALID_RELTIME)) - return 0; - rt = intervalrel(i); - return rt != INVALID_RELTIME && rt != t; -} - -/* - * intervallenlt - returns 1, iff length of interval i is less than - * reltime t - */ -bool -intervallenlt(TimeInterval i, RelativeTime t) -{ - RelativeTime rt; - - if ((i->status == T_INTERVAL_INVAL) || (t == INVALID_RELTIME)) - return 0; - rt = intervalrel(i); - return rt != INVALID_RELTIME && rt < t; -} - -/* - * intervallengt - returns 1, iff length of interval i is greater than - * reltime t - */ -bool -intervallengt(TimeInterval i, RelativeTime t) -{ - RelativeTime rt; - - if ((i->status == T_INTERVAL_INVAL) || (t == INVALID_RELTIME)) - return 0; - rt = intervalrel(i); - return rt != INVALID_RELTIME && rt > t; -} - -/* - * intervallenle - returns 1, iff length of interval i is less or equal - * than reltime t - */ -bool -intervallenle(TimeInterval i, RelativeTime t) -{ - RelativeTime rt; - - if ((i->status == T_INTERVAL_INVAL) || (t == INVALID_RELTIME)) - return 0; - rt = intervalrel(i); - return rt != INVALID_RELTIME && rt <= t; -} - -/* - * intervallenge - returns 1, iff length of interval i is greater or - * equal than reltime t - */ -bool -intervallenge(TimeInterval i, RelativeTime t) -{ - RelativeTime rt; - - if ((i->status == T_INTERVAL_INVAL) || (t == INVALID_RELTIME)) - return 0; - rt = intervalrel(i); - return rt != INVALID_RELTIME && rt >= t; -} - -/* - * intervalct - returns 1, iff interval i1 contains interval i2 - */ -bool -intervalct(TimeInterval i1, TimeInterval i2) -{ - if (i1->status == T_INTERVAL_INVAL || i2->status == T_INTERVAL_INVAL) - return 0; - return (abstimele(i1->data[0], i2->data[0]) && - abstimege(i1->data[1], i2->data[1])); -} - -/* - * intervalov - returns 1, iff interval i1 (partially) overlaps i2 - */ -bool -intervalov(TimeInterval i1, TimeInterval i2) -{ - if (i1->status == T_INTERVAL_INVAL || i2->status == T_INTERVAL_INVAL) - return 0; - return (!(abstimelt(i1->data[1], i2->data[0]) || - abstimegt(i1->data[0], i2->data[1]))); -} - -/* - * intervalstart - returns the start of interval i - */ -AbsoluteTime -intervalstart(TimeInterval i) -{ - if (i->status == T_INTERVAL_INVAL) - return INVALID_ABSTIME; - return i->data[0]; -} - -/* - * intervalend - returns the end of interval i - */ -AbsoluteTime -intervalend(TimeInterval i) -{ - if (i->status == T_INTERVAL_INVAL) - return INVALID_ABSTIME; - return i->data[1]; -} +} /* date2tm() */ /***************************************************************************** - * PRIVATE ROUTINES * + * Time ADT *****************************************************************************/ -#ifdef NOT_USED -/* - * isreltime - returns 1, iff datestring is of type reltime - * 2, iff datestring is 'invalid time' identifier - * 0, iff datestring contains a syntax error - * VALID time less or equal +/- `@ 68 years' - * - */ -int -isreltime(char *str) + +TimeADT * +time_in(char *str) { + TimeADT *time; + + double fsec; struct tm tt, *tm = &tt; - double fsec; - int dtype; - char *field[MAXDATEFIELDS]; - int nf, - ftype[MAXDATEFIELDS]; + + int nf; char lowstr[MAXDATELEN + 1]; + char *field[MAXDATEFIELDS]; + int dtype; + int ftype[MAXDATEFIELDS]; if (!PointerIsValid(str)) - return 0; - - if (strlen(str) > MAXDATELEN) - return 0; + elog(ERROR, "Bad (null) time external representation"); if ((ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf) != 0) - || (DecodeDateDelta(field, ftype, nf, &dtype, tm, &fsec) != 0)) - return 0; + || (DecodeTimeOnly(field, ftype, nf, &dtype, tm, &fsec) != 0)) + elog(ERROR, "Bad time external representation '%s'", str); - switch (dtype) - { - case (DTK_DELTA): - return (abs(tm->tm_year) <= 68) ? 1 : 0; - break; + time = palloc(sizeof(TimeADT)); - case (DTK_INVALID): - return 2; - break; + *time = ((((tm->tm_hour * 60) + tm->tm_min) * 60) + tm->tm_sec + fsec); - default: - return 0; - break; - } + return time; +} /* time_in() */ - return 0; -} /* isreltime() */ -#endif +char * +time_out(TimeADT *time) +{ + char *result; + struct tm tt, + *tm = &tt; + + double fsec; + char buf[MAXDATELEN + 1]; + + if (!PointerIsValid(time)) + return NULL; + + tm->tm_hour = (*time / (60 * 60)); + tm->tm_min = (((int) (*time / 60)) % 60); + tm->tm_sec = (((int) *time) % 60); + + fsec = 0; + + EncodeTimeOnly(tm, fsec, DateStyle, buf); + + result = palloc(strlen(buf) + 1); + + strcpy(result, buf); + + return result; +} /* time_out() */ + + +bool +time_eq(TimeADT *time1, TimeADT *time2) +{ + if (!PointerIsValid(time1) || !PointerIsValid(time2)) + return FALSE; + + return *time1 == *time2; +} /* time_eq() */ + +bool +time_ne(TimeADT *time1, TimeADT *time2) +{ + if (!PointerIsValid(time1) || !PointerIsValid(time2)) + return FALSE; + + return *time1 != *time2; +} /* time_eq() */ + +bool +time_lt(TimeADT *time1, TimeADT *time2) +{ + if (!PointerIsValid(time1) || !PointerIsValid(time2)) + return FALSE; + + return *time1 < *time2; +} /* time_eq() */ + +bool +time_le(TimeADT *time1, TimeADT *time2) +{ + if (!PointerIsValid(time1) || !PointerIsValid(time2)) + return FALSE; + + return *time1 <= *time2; +} /* time_eq() */ + +bool +time_gt(TimeADT *time1, TimeADT *time2) +{ + if (!PointerIsValid(time1) || !PointerIsValid(time2)) + return FALSE; + + return *time1 > *time2; +} /* time_eq() */ + +bool +time_ge(TimeADT *time1, TimeADT *time2) +{ + if (!PointerIsValid(time1) || !PointerIsValid(time2)) + return FALSE; + + return *time1 >= *time2; +} /* time_eq() */ -#ifdef NOT_USED int -dummyfunc() +time_cmp(TimeADT *time1, TimeADT *time2) { - char *p; - char c; - int i; - char unit[UNITMAXLEN]; - char direction[DIRMAXLEN]; - int localSign; - int localUnitNumber; - long localQuantity; + return (*time1 < *time2) ? -1 : (((*time1 > *time2) ? 1 : 0)); +} /* time_cmp() */ - if (!PointerIsValid(sign)) - sign = &localSign; - if (!PointerIsValid(unitnr)) - unitnr = &localUnitNumber; - - if (!PointerIsValid(quantity)) - quantity = &localQuantity; - - unit[0] = '\0'; - direction[0] = '\0'; - p = timestring; - /* skip leading blanks */ - while ((c = *p) != '\0') - { - if (c != ' ') - break; - p++; - } - - /* Test whether 'invalid time' identifier or not */ - if (!strncmp(INVALID_RELTIME_STR, p, strlen(INVALID_RELTIME_STR) + 1)) - return 2; /* correct 'invalid time' identifier found */ - - /* handle label of relative time */ - if (c != RELTIME_LABEL) - return 0; /* syntax error */ - c = *++p; - if (c != ' ') - return 0; /* syntax error */ - p++; - /* handle the quantity */ - *quantity = 0; - for (;;) - { - c = *p; - if (isdigit(c)) - { - *quantity = *quantity * 10 + (c - '0'); - p++; - } - else - { - if (c == ' ') - break; /* correct quantity found */ - else - return 0; /* syntax error */ - } - } - - /* handle unit */ - p++; - i = 0; - for (;;) - { - c = *p; - if (c >= 'a' && c <= 'z' && i <= (UNITMAXLEN - 1)) - { - unit[i] = c; - p++; - i++; - } - else - { - if ((c == ' ' || c == '\0') - && correct_unit(unit, unitnr)) - break; /* correct unit found */ - else - return 0; /* syntax error */ - } - } - - /* handle optional direction */ - if (c == ' ') - p++; - i = 0; - *sign = 1; - for (;;) - { - c = *p; - if (c >= 'a' && c <= 'z' && i <= (DIRMAXLEN - 1)) - { - direction[i] = c; - p++; - i++; - } - else - { - if ((c == ' ' || c == '\0') && i == 0) - { - *sign = 1; - break; /* no direction specified */ - } - if ((c == ' ' || c == '\0') && i != 0) - { - direction[i] = '\0'; - correct_dir(direction, sign); - break; /* correct direction found */ - } - else - return 0; /* syntax error */ - } - } - - return 1; -} - -/* - * correct_unit - returns 1, iff unit is a correct unit description - * - * output parameter: - * unptr: points to an integer which is the appropriate unit number - * (see function isreltime()) +/* timestamp_time() + * Convert timestamp to time data type. */ -static int -correct_unit(char *unit, int *unptr) +TimeADT * +timestamp_time(Timestamp *timestamp) { - int j = 0; + TimeADT *result; + struct tm tt, + *tm = &tt; + int tz; + double fsec; + char *tzn; - while (j < NUNITS) + if (!PointerIsValid(timestamp)) + elog(ERROR, "Unable to convert null timestamp to date"); + + if (TIMESTAMP_NOT_FINITE(*timestamp)) + elog(ERROR, "Unable to convert timestamp to date"); + + if (TIMESTAMP_IS_EPOCH(*timestamp)) { - if (strncmp(unit, unit_tab[j], strlen(unit_tab[j])) == 0) - { - *unptr = j; - return 1; - } - j++; + timestamp2tm(SetTimestamp(*timestamp), NULL, tm, &fsec, NULL); + } - return 0; /* invalid unit descriptor */ -} - -/* - * correct_dir - returns 1, iff direction is a correct identifier - * - * output parameter: - * signptr: points to -1 if dir corresponds to past tense - * else to 1 - */ -static int -correct_dir(char *direction, int *signptr) -{ - *signptr = 1; - if (strncmp(RELTIME_PAST, direction, strlen(RELTIME_PAST) + 1) == 0) + else if (TIMESTAMP_IS_CURRENT(*timestamp)) { - *signptr = -1; - return 1; + timestamp2tm(SetTimestamp(*timestamp), &tz, tm, &fsec, &tzn); + } else - return 0; /* invalid direction descriptor */ -} + { + if (timestamp2tm(*timestamp, &tz, tm, &fsec, &tzn) != 0) + elog(ERROR, "Unable to convert timestamp to date"); + } -#endif + result = palloc(sizeof(TimeADT)); -/* - * istinterval - returns 1, iff i_string is a valid interval descr. - * 0, iff i_string is NOT a valid interval desc. - * 2, iff any time is INVALID_ABSTIME - * - * output parameter: - * i_start, i_end: interval margins - * - * Time interval: - * `[' {` '} `'' `'' {` '} `'' `'' {` '} `]' - * - * OR `Undefined Range' (see also INVALID_INTERVAL_STR) - * - * where satisfies the syntax of absolute time. - * - * e.g. [ ' Jan 18 1902' 'Jan 1 00:00:00 1970'] + *result = ((((tm->tm_hour * 60) + tm->tm_min) * 60) + tm->tm_sec + fsec); + + return result; +} /* timestamp_time() */ + + +/* datetime_timestamp() + * Convert date and time to timestamp data type. */ -static int -istinterval(char *i_string, - AbsoluteTime *i_start, - AbsoluteTime *i_end) +Timestamp * +datetime_timestamp(DateADT date, TimeADT *time) { - char *p, - *p1; - char c; + Timestamp *result; - p = i_string; - /* skip leading blanks up to '[' */ - while ((c = *p) != '\0') + if (!PointerIsValid(time)) { - if (IsSpace(c)) - p++; - else if (c != '[') - return 0; /* syntax error */ - else - break; + result = palloc(sizeof(Timestamp)); + TIMESTAMP_INVALID(*result); } - p++; - /* skip leading blanks up to "'" */ - while ((c = *p) != '\0') + else { - if (IsSpace(c)) - p++; - else if (c != '"') - return 0; /* syntax error */ - else - break; + result = date_timestamp(date); + *result += *time; } - p++; - if (strncmp(INVALID_INTERVAL_STR, p, strlen(INVALID_INTERVAL_STR)) == 0) - return 0; /* undefined range, handled like a syntax - * err. */ - /* search for the end of the first date and change it to a NULL */ - p1 = p; - while ((c = *p1) != '\0') - { - if (c == '"') - { - *p1 = '\0'; - break; - } - p1++; - } - /* get the first date */ - *i_start = nabstimein(p); /* first absolute date */ - /* rechange NULL at the end of the first date to a "'" */ - *p1 = '"'; - p = ++p1; - /* skip blanks up to "'", beginning of second date */ - while ((c = *p) != '\0') - { - if (IsSpace(c)) - p++; - else if (c != '"') - return 0; /* syntax error */ - else - break; - } - p++; - /* search for the end of the second date and change it to a NULL */ - p1 = p; - while ((c = *p1) != '\0') - { - if (c == '"') - { - *p1 = '\0'; - break; - } - p1++; - } - /* get the second date */ - *i_end = nabstimein(p); /* second absolute date */ - /* rechange NULL at the end of the first date to a ''' */ - *p1 = '"'; - p = ++p1; - /* skip blanks up to ']' */ - while ((c = *p) != '\0') - { - if (IsSpace(c)) - p++; - else if (c != ']') - return 0; /* syntax error */ - else - break; - } - p++; - c = *p; - if (c != '\0') - return 0; /* syntax error */ - /* it seems to be a valid interval */ - return 1; -} - -/***************************************************************************** - * - *****************************************************************************/ - -/* - * timeofday - - * returns the current time as a text. similar to timenow() but returns - * seconds with more precision (up to microsecs). (I need this to compare - * the Wisconsin benchmark with Illustra whose TimeNow() shows current - * time with precision up to microsecs.) - ay 3/95 - */ -text * -timeofday(void) -{ - - struct timeval tp; - struct timezone tpz; - char templ[500]; - char buf[500]; - text *tm; - int len = 0; - - gettimeofday(&tp, &tpz); - strftime(templ, sizeof(templ), "%a %b %d %H:%M:%S.%%d %Y %Z", - localtime((time_t *) &tp.tv_sec)); - sprintf(buf, templ, tp.tv_usec); - - len = VARHDRSZ + strlen(buf); - tm = (text *) palloc(len); - VARSIZE(tm) = len; - strncpy(VARDATA(tm), buf, strlen(buf)); - return tm; -} + return result; +} /* datetime_timestamp() */ diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c index 7a9df275fa..1418a0c34c 100644 --- a/src/backend/utils/adt/datetime.c +++ b/src/backend/utils/adt/datetime.c @@ -1,565 +1,2184 @@ /*------------------------------------------------------------------------- * * datetime.c - * implements DATE and TIME data types specified in SQL-92 standard + * Support functions for date/time types. * * Portions Copyright (c) 1996-2000, PostgreSQL, Inc - * Portions Copyright (c) 1994-5, Regents of the University of California + * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/utils/adt/datetime.c,v 1.41 2000/01/26 05:57:13 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/utils/adt/datetime.c,v 1.42 2000/02/16 18:17:02 thomas Exp $ * *------------------------------------------------------------------------- */ -#include +#include +#include +#include +#include #include "postgres.h" #ifdef HAVE_FLOAT_H #include #endif -#include "miscadmin.h" -#include "utils/builtins.h" +#ifdef HAVE_LIMITS_H +#include +#endif +#ifndef USE_POSIX_TIME +#include +#endif -static int date2tm(DateADT dateVal, int *tzp, struct tm * tm, double *fsec, char **tzn); +#include "miscadmin.h" +#include "utils/datetime.h" + + +#define USE_DATE_CACHE 1 +#define ROUND_ALL 0 + +int day_tab[2][13] = { + {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0}, +{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0}}; + + +char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", +"Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL}; + +char *days[] = {"Sunday", "Monday", "Tuesday", "Wednesday", +"Thursday", "Friday", "Saturday", NULL}; + + +#if 0 + + +static void GetEpochTime(struct tm * tm); + + +#endif + + +#define UTIME_MINYEAR (1901) +#define UTIME_MINMONTH (12) +#define UTIME_MINDAY (14) +#define UTIME_MAXYEAR (2038) +#define UTIME_MAXMONTH (01) +#define UTIME_MAXDAY (18) + +#define IS_VALID_UTIME(y,m,d) (((y > UTIME_MINYEAR) \ + || ((y == UTIME_MINYEAR) && ((m > UTIME_MINMONTH) \ + || ((m == UTIME_MINMONTH) && (d >= UTIME_MINDAY))))) \ + && ((y < UTIME_MAXYEAR) \ + || ((y == UTIME_MAXYEAR) && ((m < UTIME_MAXMONTH) \ + || ((m == UTIME_MAXMONTH) && (d <= UTIME_MAXDAY)))))) /***************************************************************************** - * Date ADT + * PRIVATE ROUTINES * *****************************************************************************/ +/* definitions for squeezing values into "value" */ +#define ABS_SIGNBIT (char) 0200 +#define VALMASK (char) 0177 +#define NEG(n) ((n)|ABS_SIGNBIT) +#define SIGNEDCHAR(c) ((c)&ABS_SIGNBIT? -((c)&VALMASK): (c)) +#define FROMVAL(tp) (-SIGNEDCHAR((tp)->value) * 10) /* uncompress */ +#define TOVAL(tp, v) ((tp)->value = ((v) < 0? NEG((-(v))/10): (v)/10)) -/* date_in() - * Given date text string, convert to internal date format. +/* + * to keep this table reasonably small, we divide the lexval for TZ and DTZ + * entries by 10 and truncate the text field at MAXTOKLEN characters. + * the text field is not guaranteed to be NULL-terminated. */ -DateADT -date_in(char *str) -{ - DateADT date; - double fsec; - struct tm tt, - *tm = &tt; - int tzp; - int dtype; - int nf; - char *field[MAXDATEFIELDS]; - int ftype[MAXDATEFIELDS]; - char lowstr[MAXDATELEN + 1]; +static datetkn datetktbl[] = { +/* text token lexval */ + {EARLY, RESERV, DTK_EARLY}, /* "-infinity" reserved for "early time" */ + {"acsst", DTZ, 63}, /* Cent. Australia */ + {"acst", TZ, 57}, /* Cent. Australia */ + {DA_D, ADBC, AD}, /* "ad" for years >= 0 */ + {"abstime", IGNORE, 0}, /* "abstime" for pre-v6.1 "Invalid + * Abstime" */ + {"adt", DTZ, NEG(18)}, /* Atlantic Daylight Time */ + {"aesst", DTZ, 66}, /* E. Australia */ + {"aest", TZ, 60}, /* Australia Eastern Std Time */ + {"ahst", TZ, NEG(60)}, /* Alaska-Hawaii Std Time */ + {"allballs", RESERV, DTK_ZULU}, /* 00:00:00 */ + {"am", AMPM, AM}, + {"apr", MONTH, 4}, + {"april", MONTH, 4}, + {"ast", TZ, NEG(24)}, /* Atlantic Std Time (Canada) */ + {"at", IGNORE, 0}, /* "at" (throwaway) */ + {"aug", MONTH, 8}, + {"august", MONTH, 8}, + {"awsst", DTZ, 54}, /* W. Australia */ + {"awst", TZ, 48}, /* W. Australia */ + {DB_C, ADBC, BC}, /* "bc" for years < 0 */ + {"bst", TZ, 6}, /* British Summer Time */ + {"bt", TZ, 18}, /* Baghdad Time */ + {"cadt", DTZ, 63}, /* Central Australian DST */ + {"cast", TZ, 57}, /* Central Australian ST */ + {"cat", TZ, NEG(60)}, /* Central Alaska Time */ + {"cct", TZ, 48}, /* China Coast */ + {"cdt", DTZ, NEG(30)}, /* Central Daylight Time */ + {"cet", TZ, 6}, /* Central European Time */ + {"cetdst", DTZ, 12}, /* Central European Dayl.Time */ +#if USE_AUSTRALIAN_RULES + {"cst", TZ, 63}, /* Australia Eastern Std Time */ +#else + {"cst", TZ, NEG(36)}, /* Central Standard Time */ +#endif + {DCURRENT, RESERV, DTK_CURRENT}, /* "current" is always now */ + {"dec", MONTH, 12}, + {"december", MONTH, 12}, + {"dnt", TZ, 6}, /* Dansk Normal Tid */ + {"dow", RESERV, DTK_DOW}, /* day of week */ + {"doy", RESERV, DTK_DOY}, /* day of year */ + {"dst", DTZMOD, 6}, + {"east", TZ, 60}, /* East Australian Std Time */ + {"edt", DTZ, NEG(24)}, /* Eastern Daylight Time */ + {"eet", TZ, 12}, /* East. Europe, USSR Zone 1 */ + {"eetdst", DTZ, 18}, /* Eastern Europe */ + {EPOCH, RESERV, DTK_EPOCH}, /* "epoch" reserved for system epoch time */ +#if USE_AUSTRALIAN_RULES + {"est", TZ, 60}, /* Australia Eastern Std Time */ +#else + {"est", TZ, NEG(30)}, /* Eastern Standard Time */ +#endif + {"feb", MONTH, 2}, + {"february", MONTH, 2}, + {"fri", DOW, 5}, + {"friday", DOW, 5}, + {"fst", TZ, 6}, /* French Summer Time */ + {"fwt", DTZ, 12}, /* French Winter Time */ + {"gmt", TZ, 0}, /* Greenwish Mean Time */ + {"gst", TZ, 60}, /* Guam Std Time, USSR Zone 9 */ + {"hdt", DTZ, NEG(54)}, /* Hawaii/Alaska */ + {"hmt", DTZ, 18}, /* Hellas ? ? */ + {"hst", TZ, NEG(60)}, /* Hawaii Std Time */ + {"idle", TZ, 72}, /* Intl. Date Line, East */ + {"idlw", TZ, NEG(72)}, /* Intl. Date Line, West */ + {LATE, RESERV, DTK_LATE}, /* "infinity" reserved for "late time" */ + {INVALID, RESERV, DTK_INVALID}, + /* "invalid" reserved for invalid time */ + {"ist", TZ, 12}, /* Israel */ + {"it", TZ, 21}, /* Iran Time */ + {"jan", MONTH, 1}, + {"january", MONTH, 1}, + {"jst", TZ, 54}, /* Japan Std Time,USSR Zone 8 */ + {"jt", TZ, 45}, /* Java Time */ + {"jul", MONTH, 7}, + {"july", MONTH, 7}, + {"jun", MONTH, 6}, + {"june", MONTH, 6}, + {"kst", TZ, 54}, /* Korea Standard Time */ + {"ligt", TZ, 60}, /* From Melbourne, Australia */ + {"mar", MONTH, 3}, + {"march", MONTH, 3}, + {"may", MONTH, 5}, + {"mdt", DTZ, NEG(36)}, /* Mountain Daylight Time */ + {"mest", DTZ, 12}, /* Middle Europe Summer Time */ + {"met", TZ, 6}, /* Middle Europe Time */ + {"metdst", DTZ, 12}, /* Middle Europe Daylight Time */ + {"mewt", TZ, 6}, /* Middle Europe Winter Time */ + {"mez", TZ, 6}, /* Middle Europe Zone */ + {"mon", DOW, 1}, + {"monday", DOW, 1}, + {"mst", TZ, NEG(42)}, /* Mountain Standard Time */ + {"mt", TZ, 51}, /* Moluccas Time */ + {"ndt", DTZ, NEG(15)}, /* Nfld. Daylight Time */ + {"nft", TZ, NEG(21)}, /* Newfoundland Standard Time */ + {"nor", TZ, 6}, /* Norway Standard Time */ + {"nov", MONTH, 11}, + {"november", MONTH, 11}, + {NOW, RESERV, DTK_NOW}, /* current transaction time */ + {"nst", TZ, NEG(21)}, /* Nfld. Standard Time */ + {"nt", TZ, NEG(66)}, /* Nome Time */ + {"nzdt", DTZ, 78}, /* New Zealand Daylight Time */ + {"nzst", TZ, 72}, /* New Zealand Standard Time */ + {"nzt", TZ, 72}, /* New Zealand Time */ + {"oct", MONTH, 10}, + {"october", MONTH, 10}, + {"on", IGNORE, 0}, /* "on" (throwaway) */ + {"pdt", DTZ, NEG(42)}, /* Pacific Daylight Time */ + {"pm", AMPM, PM}, + {"pst", TZ, NEG(48)}, /* Pacific Standard Time */ + {"sadt", DTZ, 63}, /* S. Australian Dayl. Time */ + {"sast", TZ, 57}, /* South Australian Std Time */ + {"sat", DOW, 6}, + {"saturday", DOW, 6}, + {"sep", MONTH, 9}, + {"sept", MONTH, 9}, + {"september", MONTH, 9}, + {"set", TZ, NEG(6)}, /* Seychelles Time ?? */ + {"sst", DTZ, 12}, /* Swedish Summer Time */ + {"sun", DOW, 0}, + {"sunday", DOW, 0}, + {"swt", TZ, 6}, /* Swedish Winter Time */ + {"thu", DOW, 4}, + {"thur", DOW, 4}, + {"thurs", DOW, 4}, + {"thursday", DOW, 4}, + {TODAY, RESERV, DTK_TODAY}, /* midnight */ + {TOMORROW, RESERV, DTK_TOMORROW}, /* tomorrow midnight */ + {"tue", DOW, 2}, + {"tues", DOW, 2}, + {"tuesday", DOW, 2}, + {"undefined", RESERV, DTK_INVALID}, /* "undefined" pre-v6.1 invalid + * time */ + {"ut", TZ, 0}, + {"utc", TZ, 0}, + {"wadt", DTZ, 48}, /* West Australian DST */ + {"wast", TZ, 42}, /* West Australian Std Time */ + {"wat", TZ, NEG(6)}, /* West Africa Time */ + {"wdt", DTZ, 54}, /* West Australian DST */ + {"wed", DOW, 3}, + {"wednesday", DOW, 3}, + {"weds", DOW, 3}, + {"wet", TZ, 0}, /* Western Europe */ + {"wetdst", DTZ, 6}, /* Western Europe */ + {"wst", TZ, 48}, /* West Australian Std Time */ + {"ydt", DTZ, NEG(48)}, /* Yukon Daylight Time */ + {YESTERDAY, RESERV, DTK_YESTERDAY}, /* yesterday midnight */ + {"yst", TZ, NEG(54)}, /* Yukon Standard Time */ + {"zp4", TZ, NEG(24)}, /* GMT +4 hours. */ + {"zp5", TZ, NEG(30)}, /* GMT +5 hours. */ + {"zp6", TZ, NEG(36)}, /* GMT +6 hours. */ + {"z", RESERV, DTK_ZULU}, /* 00:00:00 */ + {ZULU, RESERV, DTK_ZULU}, /* 00:00:00 */ +}; - if (!PointerIsValid(str)) - elog(ERROR, "Bad (null) date external representation"); +static unsigned int szdatetktbl = sizeof datetktbl / sizeof datetktbl[0]; - if ((ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf) != 0) - || (DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tzp) != 0)) - elog(ERROR, "Bad date external representation '%s'", str); +static datetkn deltatktbl[] = { +/* text token lexval */ + {"@", IGNORE, 0}, /* postgres relative time prefix */ + {DAGO, AGO, 0}, /* "ago" indicates negative time offset */ + {"c", UNITS, DTK_CENTURY}, /* "century" relative time units */ + {"cent", UNITS, DTK_CENTURY}, /* "century" relative time units */ + {"centuries", UNITS, DTK_CENTURY}, /* "centuries" relative time units */ + {DCENTURY, UNITS, DTK_CENTURY}, /* "century" relative time units */ + {"d", UNITS, DTK_DAY}, /* "day" relative time units */ + {DDAY, UNITS, DTK_DAY}, /* "day" relative time units */ + {"days", UNITS, DTK_DAY}, /* "days" relative time units */ + {"dec", UNITS, DTK_DECADE}, /* "decade" relative time units */ + {"decs", UNITS, DTK_DECADE},/* "decades" relative time units */ + {DDECADE, UNITS, DTK_DECADE}, /* "decade" relative time units */ + {"decades", UNITS, DTK_DECADE}, /* "decades" relative time units */ + {"h", UNITS, DTK_HOUR}, /* "hour" relative time units */ + {DHOUR, UNITS, DTK_HOUR}, /* "hour" relative time units */ + {"hours", UNITS, DTK_HOUR}, /* "hours" relative time units */ + {"hr", UNITS, DTK_HOUR}, /* "hour" relative time units */ + {"hrs", UNITS, DTK_HOUR}, /* "hours" relative time units */ + {INVALID, RESERV, DTK_INVALID}, /* "invalid" reserved for invalid + * time */ + {"m", UNITS, DTK_MINUTE}, /* "minute" relative time units */ + {"microsecon", UNITS, DTK_MICROSEC}, /* "microsecond" relative + * time units */ + {"mil", UNITS, DTK_MILLENIUM}, /* "millenium" relative time units */ + {"mils", UNITS, DTK_MILLENIUM}, /* "millenia" relative time units */ + {"millenia", UNITS, DTK_MILLENIUM}, /* "millenia" relative time units */ + {DMILLENIUM, UNITS, DTK_MILLENIUM}, /* "millenium" relative time units */ + {"millisecon", UNITS, DTK_MILLISEC}, /* relative time units */ + {"min", UNITS, DTK_MINUTE}, /* "minute" relative time units */ + {"mins", UNITS, DTK_MINUTE},/* "minutes" relative time units */ + {"mins", UNITS, DTK_MINUTE},/* "minutes" relative time units */ + {DMINUTE, UNITS, DTK_MINUTE}, /* "minute" relative time units */ + {"minutes", UNITS, DTK_MINUTE}, /* "minutes" relative time units */ + {"mon", UNITS, DTK_MONTH}, /* "months" relative time units */ + {"mons", UNITS, DTK_MONTH}, /* "months" relative time units */ + {DMONTH, UNITS, DTK_MONTH}, /* "month" relative time units */ + {"months", UNITS, DTK_MONTH}, + {"ms", UNITS, DTK_MILLISEC}, + {"msec", UNITS, DTK_MILLISEC}, + {DMILLISEC, UNITS, DTK_MILLISEC}, + {"mseconds", UNITS, DTK_MILLISEC}, + {"msecs", UNITS, DTK_MILLISEC}, + {"qtr", UNITS, DTK_QUARTER},/* "quarter" relative time */ + {DQUARTER, UNITS, DTK_QUARTER}, /* "quarter" relative time */ + {"reltime", IGNORE, 0}, /* for pre-v6.1 "Undefined Reltime" */ + {"s", UNITS, DTK_SECOND}, + {"sec", UNITS, DTK_SECOND}, + {DSECOND, UNITS, DTK_SECOND}, + {"seconds", UNITS, DTK_SECOND}, + {"secs", UNITS, DTK_SECOND}, + {DTIMEZONE, UNITS, DTK_TZ}, /* "timezone" time offset */ + {"tz", UNITS, DTK_TZ}, /* "timezone" time offset */ + {"tz_hour", UNITS, DTK_TZ_HOUR}, /* timezone hour units */ + {"tz_minute", UNITS, DTK_TZ_MINUTE}, /* timezone minutes units */ + {"undefined", RESERV, DTK_INVALID}, /* pre-v6.1 invalid time */ + {"us", UNITS, DTK_MICROSEC},/* "microsecond" relative time units */ + {"usec", UNITS, DTK_MICROSEC}, /* "microsecond" relative time + * units */ + {DMICROSEC, UNITS, DTK_MICROSEC}, /* "microsecond" relative time + * units */ + {"useconds", UNITS, DTK_MICROSEC}, /* "microseconds" relative time + * units */ + {"usecs", UNITS, DTK_MICROSEC}, /* "microseconds" relative time + * units */ + {"w", UNITS, DTK_WEEK}, /* "week" relative time units */ + {DWEEK, UNITS, DTK_WEEK}, /* "week" relative time units */ + {"weeks", UNITS, DTK_WEEK}, /* "weeks" relative time units */ + {"y", UNITS, DTK_YEAR}, /* "year" relative time units */ + {DYEAR, UNITS, DTK_YEAR}, /* "year" relative time units */ + {"years", UNITS, DTK_YEAR}, /* "years" relative time units */ + {"yr", UNITS, DTK_YEAR}, /* "year" relative time units */ + {"yrs", UNITS, DTK_YEAR}, /* "years" relative time units */ +}; - switch (dtype) - { - case DTK_DATE: - break; +static unsigned int szdeltatktbl = sizeof deltatktbl / sizeof deltatktbl[0]; - case DTK_CURRENT: - GetCurrentTime(tm); - break; +#if USE_DATE_CACHE +datetkn *datecache[MAXDATEFIELDS] = {NULL}; - case DTK_EPOCH: - tm->tm_year = 1970; - tm->tm_mon = 1; - tm->tm_mday = 1; - break; +datetkn *deltacache[MAXDATEFIELDS] = {NULL}; - default: - elog(ERROR, "Unrecognized date external representation '%s'", str); - } +#endif - date = (date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - date2j(2000, 1, 1)); - return date; -} /* date_in() */ - -/* date_out() - * Given internal format date, convert to text string. +/* + * Calendar time to Julian date conversions. + * Julian date is commonly used in astronomical applications, + * since it is numerically accurate and computationally simple. + * The algorithms here will accurately convert between Julian day + * and calendar date for all non-negative Julian days + * (i.e. from Nov 23, -4713 on). + * + * Ref: Explanatory Supplement to the Astronomical Almanac, 1992. + * University Science Books, 20 Edgehill Rd. Mill Valley CA 94941. + * + * Use the algorithm by Henry Fliegel, a former NASA/JPL colleague + * now at Aerospace Corp. (hi, Henry!) + * + * These routines will be used by other date/time packages - tgl 97/02/25 */ -char * -date_out(DateADT date) -{ - char *result; - struct tm tt, - *tm = &tt; - char buf[MAXDATELEN + 1]; - - j2date((date + date2j(2000, 1, 1)), - &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday)); - - EncodeDateOnly(tm, DateStyle, buf); - - result = palloc(strlen(buf) + 1); - - strcpy(result, buf); - - return result; -} /* date_out() */ - -bool -date_eq(DateADT dateVal1, DateADT dateVal2) -{ - return dateVal1 == dateVal2; -} - -bool -date_ne(DateADT dateVal1, DateADT dateVal2) -{ - return dateVal1 != dateVal2; -} - -bool -date_lt(DateADT dateVal1, DateADT dateVal2) -{ - return dateVal1 < dateVal2; -} /* date_lt() */ - -bool -date_le(DateADT dateVal1, DateADT dateVal2) -{ - return dateVal1 <= dateVal2; -} /* date_le() */ - -bool -date_gt(DateADT dateVal1, DateADT dateVal2) -{ - return dateVal1 > dateVal2; -} /* date_gt() */ - -bool -date_ge(DateADT dateVal1, DateADT dateVal2) -{ - return dateVal1 >= dateVal2; -} /* date_ge() */ int -date_cmp(DateADT dateVal1, DateADT dateVal2) +date2j(int y, int m, int d) { - if (dateVal1 < dateVal2) - return -1; - else if (dateVal1 > dateVal2) - return 1; - return 0; -} /* date_cmp() */ + int m12 = (m - 14) / 12; -DateADT -date_larger(DateADT dateVal1, DateADT dateVal2) + return ((1461 * (y + 4800 + m12)) / 4 + (367 * (m - 2 - 12 * (m12))) / 12 + - (3 * ((y + 4900 + m12) / 100)) / 4 + d - 32075); +} /* date2j() */ + +void +j2date(int jd, int *year, int *month, int *day) { - return date_gt(dateVal1, dateVal2) ? dateVal1 : dateVal2; -} /* date_larger() */ + int j, + y, + m, + d; -DateADT -date_smaller(DateADT dateVal1, DateADT dateVal2) + int i, + l, + n; + + l = jd + 68569; + n = (4 * l) / 146097; + l -= (146097 * n + 3) / 4; + i = (4000 * (l + 1)) / 1461001; + l += 31 - (1461 * i) / 4; + j = (80 * l) / 2447; + d = l - (2447 * j) / 80; + l = j / 11; + m = (j + 2) - (12 * l); + y = 100 * (n - 49) + i + l; + + *year = y; + *month = m; + *day = d; + return; +} /* j2date() */ + +int +j2day(int date) { - return date_lt(dateVal1, dateVal2) ? dateVal1 : dateVal2; -} /* date_smaller() */ + int day; -/* Compute difference between two dates in days. + day = (date + 1) % 7; + + return day; +} /* j2day() */ + + +#if 0 + + +static double +time2t(const int hour, const int min, const double sec) +{ + return (((hour * 60) + min) * 60) + sec; +} /* time2t() */ + +static void +dt2time(Timestamp jd, int *hour, int *min, double *sec) +{ + double time; + + time = jd; + + *hour = (time / 3600); + time -= ((*hour) * 3600); + *min = (time / 60); + time -= ((*min) * 60); + *sec = JROUND(time); + + return; +} /* dt2time() */ + + +#endif + + +/* + * parse and convert date in timestr (the normal interface) + * + * Returns the number of seconds since epoch (J2000) */ -int4 -date_mi(DateADT dateVal1, DateADT dateVal2) -{ - return dateVal1 - dateVal2; -} /* date_mi() */ -/* Add a number of days to a date, giving a new date. - * Must handle both positive and negative numbers of days. +/* ParseDateTime() + * Break string into tokens based on a date/time context. */ -DateADT -date_pli(DateADT dateVal, int4 days) +int +ParseDateTime(char *timestr, char *lowstr, + char **field, int *ftype, int maxfields, int *numfields) { - return dateVal + days; -} /* date_pli() */ + int nf = 0; + char *cp = timestr; + char *lp = lowstr; -/* Subtract a number of days from a date, giving a new date. - */ -DateADT -date_mii(DateADT dateVal, int4 days) -{ - return date_pli(dateVal, -days); -} /* date_mii() */ - - -/* date_datetime() - * Convert date to datetime data type. - */ -DateTime * -date_datetime(DateADT dateVal) -{ - DateTime *result; - struct tm tt, - *tm = &tt; - int tz; - double fsec = 0; - char *tzn; - - result = palloc(sizeof(DateTime)); - - if (date2tm(dateVal, &tz, tm, &fsec, &tzn) != 0) - elog(ERROR, "Unable to convert date to datetime"); - - if (tm2datetime(tm, fsec, &tz, result) != 0) - elog(ERROR, "Datetime out of range"); - - return result; -} /* date_datetime() */ - - -/* datetime_date() - * Convert datetime to date data type. - */ -DateADT -datetime_date(DateTime *datetime) -{ - DateADT result; - struct tm tt, - *tm = &tt; - int tz; - double fsec; - char *tzn; - - if (!PointerIsValid(datetime)) - elog(ERROR, "Unable to convert null datetime to date"); - - if (DATETIME_NOT_FINITE(*datetime)) - elog(ERROR, "Unable to convert datetime to date"); - - if (DATETIME_IS_EPOCH(*datetime)) + /* outer loop through fields */ + while (*cp != '\0') { - datetime2tm(SetDateTime(*datetime), NULL, tm, &fsec, NULL); + field[nf] = lp; - } - else if (DATETIME_IS_CURRENT(*datetime)) - { - datetime2tm(SetDateTime(*datetime), &tz, tm, &fsec, &tzn); + /* leading digit? then date or time */ + if (isdigit(*cp) || (*cp == '.')) + { + *lp++ = *cp++; + while (isdigit(*cp)) + *lp++ = *cp++; + /* time field? */ + if (*cp == ':') + { + ftype[nf] = DTK_TIME; + while (isdigit(*cp) || (*cp == ':') || (*cp == '.')) + *lp++ = *cp++; - } - else - { - if (datetime2tm(*datetime, &tz, tm, &fsec, &tzn) != 0) - elog(ERROR, "Unable to convert datetime to date"); - } + } + /* date field? allow embedded text month */ + else if ((*cp == '-') || (*cp == '/') || (*cp == '.')) + { + ftype[nf] = DTK_DATE; + while (isalnum(*cp) || (*cp == '-') || (*cp == '/') || (*cp == '.')) + *lp++ = tolower(*cp++); - result = (date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - date2j(2000, 1, 1)); - - return result; -} /* datetime_date() */ - - -/* abstime_date() - * Convert abstime to date data type. - */ -DateADT -abstime_date(AbsoluteTime abstime) -{ - DateADT result; - struct tm tt, - *tm = &tt; - int tz; - - switch (abstime) - { - case INVALID_ABSTIME: - case NOSTART_ABSTIME: - case NOEND_ABSTIME: - elog(ERROR, "Unable to convert reserved abstime value to date"); + } /* - * pretend to drop through to make compiler think that result - * will be set + * otherwise, number only and will determine year, month, or + * day later */ + else + ftype[nf] = DTK_NUMBER; - case EPOCH_ABSTIME: - result = date2j(1970, 1, 1) - date2j(2000, 1, 1); - break; + } - case CURRENT_ABSTIME: - GetCurrentTime(tm); - result = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - date2j(2000, 1, 1); - break; + /* + * text? then date string, month, day of week, special, or + * timezone + */ + else if (isalpha(*cp)) + { + ftype[nf] = DTK_STRING; + *lp++ = tolower(*cp++); + while (isalpha(*cp)) + *lp++ = tolower(*cp++); - default: - abstime2tm(abstime, &tz, tm, NULL); - result = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - date2j(2000, 1, 1); - break; + /* full date string with leading text month? */ + if ((*cp == '-') || (*cp == '/') || (*cp == '.')) + { + /* + * special case of Posix timezone "GMT-0800" + * Note that other sign (e.g. "GMT+0800" + * is recognized as two separate fields and handled later. + * XXX There is no room for a delimiter between + * the "GMT" and the "-0800", so we are going to just swallow the "GMT". + * But this leads to other troubles with the definition of signs, + * so we have to flip + * - thomas 2000-02-06 + */ + if ((*cp == '-') && isdigit(*(cp+1)) + && (strncmp(field[nf], "gmt", 3) == 0)) + { + *cp = '+'; + continue; + } + + ftype[nf] = DTK_DATE; + while (isdigit(*cp) || (*cp == '-') || (*cp == '/') || (*cp == '.')) + *lp++ = tolower(*cp++); + } + + /* skip leading spaces */ + } + else if (isspace(*cp)) + { + cp++; + continue; + + /* sign? then special or numeric timezone */ + } + else if ((*cp == '+') || (*cp == '-')) + { + *lp++ = *cp++; + /* soak up leading whitespace */ + while (isspace(*cp)) + cp++; + /* numeric timezone? */ + if (isdigit(*cp)) + { + ftype[nf] = DTK_TZ; + *lp++ = *cp++; + while (isdigit(*cp) || (*cp == ':')) + *lp++ = *cp++; + + /* special? */ + } + else if (isalpha(*cp)) + { + ftype[nf] = DTK_SPECIAL; + *lp++ = tolower(*cp++); + while (isalpha(*cp)) + *lp++ = tolower(*cp++); + + /* otherwise something wrong... */ + } + else + return -1; + + /* ignore punctuation but use as delimiter */ + } + else if (ispunct(*cp)) + { + cp++; + continue; + + } + else + return -1; + + /* force in a delimiter */ + *lp++ = '\0'; + nf++; + if (nf > MAXDATEFIELDS) + return -1; } - return result; -} /* abstime_date() */ + *numfields = nf; + + return 0; +} /* ParseDateTime() */ -/* date2tm() - * Convert date to time structure. - * Note that date is an implicit local time, but the system calls assume - * that everything is GMT. So, convert to GMT, rotate to local time, - * and then convert again to try to get the time zones correct. +/* DecodeDateTime() + * Interpret previously parsed fields for general date and time. + * Return 0 if full date, 1 if only time, and -1 if problems. + * External format(s): + * " -- ::" + * "Fri Feb-7-1997 15:23:27" + * "Feb-7-1997 15:23:27" + * "2-7-1997 15:23:27" + * "1997-2-7 15:23:27" + * "1997.038 15:23:27" (day of year 1-366) + * Also supports input in compact time: + * "970207 152327" + * "97038 152327" + * + * Use the system-provided functions to get the current time zone + * if not specified in the input string. + * If the date is outside the time_t system-supported time range, + * then assume GMT time zone. - tgl 97/05/27 */ -static int -date2tm(DateADT dateVal, int *tzp, struct tm * tm, double *fsec, char **tzn) +int +DecodeDateTime(char **field, int *ftype, int nf, + int *dtype, struct tm * tm, double *fsec, int *tzp) { - struct tm *tx; - time_t utime; + int fmask = 0, + tmask, + type; + int i; + int flen, + val; + int mer = HR24; + int haveTextMonth = FALSE; + int is2digits = FALSE; + int bc = FALSE; - *fsec = 0; - - j2date((dateVal + date2j(2000, 1, 1)), &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday)); + *dtype = DTK_DATE; tm->tm_hour = 0; tm->tm_min = 0; tm->tm_sec = 0; - tm->tm_isdst = -1; + *fsec = 0; + tm->tm_isdst = -1; /* don't know daylight savings time status + * apriori */ + if (tzp != NULL) + *tzp = 0; - if (IS_VALID_UTIME(tm->tm_year, tm->tm_mon, tm->tm_mday)) + for (i = 0; i < nf; i++) { + switch (ftype[i]) + { + case DTK_DATE: + if (DecodeDate(field[i], fmask, &tmask, tm) != 0) + return -1; + break; - /* convert to system time */ - utime = ((dateVal + (date2j(2000, 1, 1) - date2j(1970, 1, 1))) * 86400); - /* rotate to noon to get the right day in time zone */ - utime += (12 * 60 * 60); + case DTK_TIME: + if (DecodeTime(field[i], fmask, &tmask, tm, fsec) != 0) + return -1; + /* + * check upper limit on hours; other limits checked in + * DecodeTime() + */ + if (tm->tm_hour > 23) + return -1; + break; + + case DTK_TZ: + if (tzp == NULL) + return -1; + if (DecodeTimezone(field[i], tzp) != 0) + return -1; + tmask = DTK_M(TZ); + break; + + case DTK_NUMBER: + flen = strlen(field[i]); + + /* + * long numeric string and either no date or no time read + * yet? then interpret as a concatenated date or time... + */ + if ((flen > 4) && !((fmask & DTK_DATE_M) && (fmask & DTK_TIME_M))) + { + if (DecodeNumberField(flen, field[i], fmask, &tmask, tm, fsec, &is2digits) != 0) + return -1; + + } + /* otherwise it is a single date/time field... */ + else + { + if (DecodeNumber(flen, field[i], fmask, &tmask, tm, fsec, &is2digits) != 0) + return -1; + } + break; + + case DTK_STRING: + case DTK_SPECIAL: + type = DecodeSpecial(i, field[i], &val); + if (type == IGNORE) + continue; + + tmask = DTK_M(type); + switch (type) + { + case RESERV: + switch (val) + { + case DTK_NOW: + tmask = (DTK_DATE_M | DTK_TIME_M | DTK_M(TZ)); + *dtype = DTK_DATE; + GetCurrentTime(tm); + if (tzp != NULL) + *tzp = CTimeZone; + break; + + case DTK_YESTERDAY: + tmask = DTK_DATE_M; + *dtype = DTK_DATE; + GetCurrentTime(tm); + j2date((date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - 1), + &tm->tm_year, &tm->tm_mon, &tm->tm_mday); + tm->tm_hour = 0; + tm->tm_min = 0; + tm->tm_sec = 0; + break; + + case DTK_TODAY: + tmask = DTK_DATE_M; + *dtype = DTK_DATE; + GetCurrentTime(tm); + tm->tm_hour = 0; + tm->tm_min = 0; + tm->tm_sec = 0; + break; + + case DTK_TOMORROW: + tmask = DTK_DATE_M; + *dtype = DTK_DATE; + GetCurrentTime(tm); + j2date((date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) + 1), + &tm->tm_year, &tm->tm_mon, &tm->tm_mday); + tm->tm_hour = 0; + tm->tm_min = 0; + tm->tm_sec = 0; + break; + + case DTK_ZULU: + tmask = (DTK_TIME_M | DTK_M(TZ)); + *dtype = DTK_DATE; + tm->tm_hour = 0; + tm->tm_min = 0; + tm->tm_sec = 0; + if (tzp != NULL) + *tzp = 0; + break; + + default: + *dtype = val; + } + + break; + + case MONTH: + /* + * already have a (numeric) month? then see if we + * can substitute... + */ + if ((fmask & DTK_M(MONTH)) && (!haveTextMonth) + && (!(fmask & DTK_M(DAY))) + && ((tm->tm_mon >= 1) && (tm->tm_mon <= 31))) + { + tm->tm_mday = tm->tm_mon; + tmask = DTK_M(DAY); + } + haveTextMonth = TRUE; + tm->tm_mon = val; + break; + + case DTZMOD: + + /* + * daylight savings time modifier (solves "MET + * DST" syntax) + */ + tmask |= DTK_M(DTZ); + tm->tm_isdst = 1; + if (tzp == NULL) + return -1; + *tzp += val * 60; + break; + + case DTZ: + + /* + * set mask for TZ here _or_ check for DTZ later + * when getting default timezone + */ + tmask |= DTK_M(TZ); + tm->tm_isdst = 1; + if (tzp == NULL) + return -1; + *tzp = val * 60; + break; + + case TZ: + tm->tm_isdst = 0; + if (tzp == NULL) + return -1; + *tzp = val * 60; + + /* Swallow an immediately succeeding timezone if this is GMT + * This handles the odd case in FreeBSD of "GMT+0800" + * but note that we need to flip the sign on this too. + * Claims to be some sort of POSIX standard format :( + * - thomas 2000-01-20 + */ + if ((i < (nf-1)) && (ftype[i+1] == DTK_TZ) + && (strcmp(field[i], "gmt") == 0)) + { + i++; + if (DecodeTimezone(field[i], tzp) != 0) + return -1; + + /* flip the sign per POSIX standard */ + *tzp = -(*tzp); + } + + + break; + + case IGNORE: + break; + + case AMPM: + mer = val; + break; + + case ADBC: + bc = (val == BC); + break; + + case DOW: + tm->tm_wday = val; + break; + + default: + return -1; + } + break; + + default: + return -1; + } + + if (tmask & fmask) + return -1; + fmask |= tmask; + } + + /* there is no year zero in AD/BC notation; i.e. "1 BC" == year 0 */ + if (bc) + { + if (tm->tm_year > 0) + tm->tm_year = -(tm->tm_year - 1); + else + elog(ERROR, "Inconsistant use of year %04d and 'BC'", tm->tm_year); + } + else if (is2digits) + { + if (tm->tm_year < 70) + tm->tm_year += 2000; + else if (tm->tm_year < 100) + tm->tm_year += 1900; + } + + if ((mer != HR24) && (tm->tm_hour > 12)) + return -1; + if ((mer == AM) && (tm->tm_hour == 12)) + tm->tm_hour = 0; + else if ((mer == PM) && (tm->tm_hour != 12)) + tm->tm_hour += 12; + + /* do additional checking for full date specs... */ + if (*dtype == DTK_DATE) + { + if ((fmask & DTK_DATE_M) != DTK_DATE_M) + return ((fmask & DTK_TIME_M) == DTK_TIME_M) ? 1 : -1; + + /* + * check for valid day of month, now that we know for sure the + * month and year... + */ + if ((tm->tm_mday < 1) + || (tm->tm_mday > day_tab[isleap(tm->tm_year)][tm->tm_mon - 1])) + return -1; + + /* timezone not specified? then find local timezone if possible */ + if (((fmask & DTK_DATE_M) == DTK_DATE_M) + && (tzp != NULL) && (!(fmask & DTK_M(TZ)))) + { + + /* + * daylight savings time modifier but no standard timezone? + * then error + */ + if (fmask & DTK_M(DTZMOD)) + return -1; + + if (IS_VALID_UTIME(tm->tm_year, tm->tm_mon, tm->tm_mday)) + { #ifdef USE_POSIX_TIME - tx = localtime(&utime); - - tm->tm_year = tx->tm_year + 1900; - tm->tm_mon = tx->tm_mon + 1; - tm->tm_mday = tx->tm_mday; - tm->tm_isdst = tx->tm_isdst; + tm->tm_year -= 1900; + tm->tm_mon -= 1; + tm->tm_isdst = -1; + mktime(tm); + tm->tm_year += 1900; + tm->tm_mon += 1; #if defined(HAVE_TM_ZONE) - tm->tm_gmtoff = tx->tm_gmtoff; - tm->tm_zone = tx->tm_zone; - - /* tm_gmtoff is Sun/DEC-ism */ - *tzp = -(tm->tm_gmtoff); - if (tzn != NULL) - *tzn = (char *) tm->tm_zone; + *tzp = -(tm->tm_gmtoff); /* tm_gmtoff is + * Sun/DEC-ism */ #elif defined(HAVE_INT_TIMEZONE) #ifdef __CYGWIN__ - *tzp = (tm->tm_isdst ? (_timezone - 3600) : _timezone); + *tzp = ((tm->tm_isdst > 0) ? (_timezone - 3600) : _timezone); #else - *tzp = (tm->tm_isdst ? (timezone - 3600) : timezone); + *tzp = ((tm->tm_isdst > 0) ? (timezone - 3600) : timezone); #endif - if (tzn != NULL) - *tzn = tzname[(tm->tm_isdst > 0)]; #else #error USE_POSIX_TIME is defined but neither HAVE_TM_ZONE or HAVE_INT_TIMEZONE are defined -#endif -#else /* !USE_POSIX_TIME */ - *tzp = CTimeZone; /* V7 conventions; don't know timezone? */ - if (tzn != NULL) - *tzn = CTZName; #endif - /* otherwise, outside of timezone range so convert to GMT... */ - } - else - { - *tzp = 0; - tm->tm_isdst = 0; - if (tzn != NULL) - *tzn = NULL; +#else /* !USE_POSIX_TIME */ + *tzp = CTimeZone; +#endif + } + else + { + tm->tm_isdst = 0; + *tzp = 0; + } + } } return 0; -} /* date2tm() */ +} /* DecodeDateTime() */ -/***************************************************************************** - * Time ADT - *****************************************************************************/ - - -TimeADT * -time_in(char *str) +/* DecodeTimeOnly() + * Interpret parsed string as time fields only. + */ +int +DecodeTimeOnly(char **field, int *ftype, int nf, int *dtype, struct tm * tm, double *fsec) { - TimeADT *time; + int fmask, + tmask, + type; + int i; + int flen, + val; + int is2digits = FALSE; + int mer = HR24; + *dtype = DTK_TIME; + tm->tm_hour = 0; + tm->tm_min = 0; + tm->tm_sec = 0; + tm->tm_isdst = -1; /* don't know daylight savings time status + * apriori */ + *fsec = 0; + + fmask = DTK_DATE_M; + + for (i = 0; i < nf; i++) + { + switch (ftype[i]) + { + case DTK_TIME: + if (DecodeTime(field[i], fmask, &tmask, tm, fsec) != 0) + return -1; + break; + + case DTK_NUMBER: + flen = strlen(field[i]); + + if (DecodeNumberField(flen, field[i], fmask, &tmask, tm, fsec, &is2digits) != 0) + return -1; + break; + + case DTK_STRING: + case DTK_SPECIAL: + type = DecodeSpecial(i, field[i], &val); + if (type == IGNORE) + continue; + + tmask = DTK_M(type); + switch (type) + { + case RESERV: + switch (val) + { + case DTK_NOW: + tmask = DTK_TIME_M; + *dtype = DTK_TIME; + GetCurrentTime(tm); + break; + + case DTK_ZULU: + tmask = (DTK_TIME_M | DTK_M(TZ)); + *dtype = DTK_TIME; + tm->tm_hour = 0; + tm->tm_min = 0; + tm->tm_sec = 0; + tm->tm_isdst = 0; + break; + + default: + return -1; + } + + break; + + case IGNORE: + break; + + case AMPM: + mer = val; + break; + + default: + return -1; + } + break; + + default: + return -1; + } + + if (tmask & fmask) + return -1; + fmask |= tmask; + } + + if ((mer != HR24) && (tm->tm_hour > 12)) + return -1; + if ((mer == AM) && (tm->tm_hour == 12)) + tm->tm_hour = 0; + else if ((mer == PM) && (tm->tm_hour != 12)) + tm->tm_hour += 12; + + if (((tm->tm_hour < 0) || (tm->tm_hour > 23)) + || ((tm->tm_min < 0) || (tm->tm_min > 59)) + || ((tm->tm_sec < 0) || ((tm->tm_sec + *fsec) >= 60))) + return -1; + + if ((fmask & DTK_TIME_M) != DTK_TIME_M) + return -1; + + return 0; +} /* DecodeTimeOnly() */ + + +/* DecodeDate() + * Decode date string which includes delimiters. + * Insist on a complete set of fields. + */ +int +DecodeDate(char *str, int fmask, int *tmask, struct tm * tm) +{ double fsec; - struct tm tt, - *tm = &tt; - int nf; - char lowstr[MAXDATELEN + 1]; + int nf = 0; + int i, + len; + int bc = FALSE; + int is2digits = FALSE; + int type, + val, + dmask = 0; char *field[MAXDATEFIELDS]; - int dtype; - int ftype[MAXDATEFIELDS]; - if (!PointerIsValid(str)) - elog(ERROR, "Bad (null) time external representation"); + /* parse this string... */ + while ((*str != '\0') && (nf < MAXDATEFIELDS)) + { + /* skip field separators */ + while (!isalnum(*str)) + str++; - if ((ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf) != 0) - || (DecodeTimeOnly(field, ftype, nf, &dtype, tm, &fsec) != 0)) - elog(ERROR, "Bad time external representation '%s'", str); + field[nf] = str; + if (isdigit(*str)) + { + while (isdigit(*str)) + str++; + } + else if (isalpha(*str)) + { + while (isalpha(*str)) + str++; + } - time = palloc(sizeof(TimeADT)); + if (*str != '\0') + *str++ = '\0'; + nf++; + } - *time = ((((tm->tm_hour * 60) + tm->tm_min) * 60) + tm->tm_sec + fsec); +#if 0 + /* don't allow too many fields */ + if (nf > 3) + return -1; +#endif - return time; -} /* time_in() */ + *tmask = 0; + + /* look first for text fields, since that will be unambiguous month */ + for (i = 0; i < nf; i++) + { + if (isalpha(*field[i])) + { + type = DecodeSpecial(i, field[i], &val); + if (type == IGNORE) + continue; + + dmask = DTK_M(type); + switch (type) + { + case MONTH: + tm->tm_mon = val; + break; + + case ADBC: + bc = (val == BC); + break; + + default: + return -1; + } + if (fmask & dmask) + return -1; + + fmask |= dmask; + *tmask |= dmask; + + /* mark this field as being completed */ + field[i] = NULL; + } + } + + /* now pick up remaining numeric fields */ + for (i = 0; i < nf; i++) + { + if (field[i] == NULL) + continue; + + if ((len = strlen(field[i])) <= 0) + return -1; + + if (DecodeNumber(len, field[i], fmask, &dmask, tm, &fsec, &is2digits) != 0) + return -1; + + if (fmask & dmask) + return -1; + + fmask |= dmask; + *tmask |= dmask; + } + + if ((fmask & ~(DTK_M(DOY) | DTK_M(TZ))) != DTK_DATE_M) + return -1; + + /* there is no year zero in AD/BC notation; i.e. "1 BC" == year 0 */ + if (bc) + { + if (tm->tm_year > 0) + tm->tm_year = -(tm->tm_year - 1); + else + elog(ERROR, "Inconsistant use of year %04d and 'BC'", tm->tm_year); + } + else if (is2digits) + { + if (tm->tm_year < 70) + tm->tm_year += 2000; + else if (tm->tm_year < 100) + tm->tm_year += 1900; + } + + return 0; +} /* DecodeDate() */ -char * -time_out(TimeADT *time) +/* DecodeTime() + * Decode time string which includes delimiters. + * Only check the lower limit on hours, since this same code + * can be used to represent time spans. + */ +int +DecodeTime(char *str, int fmask, int *tmask, struct tm * tm, double *fsec) { - char *result; - struct tm tt, - *tm = &tt; + char *cp; - double fsec; - char buf[MAXDATELEN + 1]; + *tmask = DTK_TIME_M; - if (!PointerIsValid(time)) - return NULL; + tm->tm_hour = strtol(str, &cp, 10); + if (*cp != ':') + return -1; + str = cp + 1; + tm->tm_min = strtol(str, &cp, 10); + if (*cp == '\0') + { + tm->tm_sec = 0; + *fsec = 0; - tm->tm_hour = (*time / (60 * 60)); - tm->tm_min = (((int) (*time / 60)) % 60); - tm->tm_sec = (((int) *time) % 60); + } + else if (*cp != ':') + { + return -1; - fsec = 0; + } + else + { + str = cp + 1; + tm->tm_sec = strtol(str, &cp, 10); + if (*cp == '\0') + *fsec = 0; + else if (*cp == '.') + { + str = cp; + *fsec = strtod(str, &cp); + if (cp == str) + return -1; + } + else + return -1; + } - EncodeTimeOnly(tm, fsec, DateStyle, buf); + /* do a sanity check */ + if ((tm->tm_hour < 0) + || (tm->tm_min < 0) || (tm->tm_min > 59) + || (tm->tm_sec < 0) || (tm->tm_sec > 59)) + return -1; - result = palloc(strlen(buf) + 1); - - strcpy(result, buf); - - return result; -} /* time_out() */ + return 0; +} /* DecodeTime() */ -bool -time_eq(TimeADT *time1, TimeADT *time2) +/* DecodeNumber() + * Interpret numeric field as a date value in context. + */ +int +DecodeNumber(int flen, char *str, int fmask, + int *tmask, struct tm * tm, double *fsec, int *is2digits) { - if (!PointerIsValid(time1) || !PointerIsValid(time2)) - return FALSE; + int val; + char *cp; - return *time1 == *time2; -} /* time_eq() */ + *tmask = 0; -bool -time_ne(TimeADT *time1, TimeADT *time2) + val = strtol(str, &cp, 10); + if (cp == str) + return -1; + if (*cp == '.') + { + *fsec = strtod(cp, &cp); + if (*cp != '\0') + return -1; + } + + /* Special case day of year? */ + if ((flen == 3) && (fmask & DTK_M(YEAR)) + && ((val >= 1) && (val <= 366))) + { + *tmask = (DTK_M(DOY) | DTK_M(MONTH) | DTK_M(DAY)); + tm->tm_yday = val; + j2date((date2j(tm->tm_year, 1, 1) + tm->tm_yday - 1), + &tm->tm_year, &tm->tm_mon, &tm->tm_mday); + + } + + /* + * Enough digits to be unequivocal year? Used to test for 4 digits or + * more, but we now test first for a three-digit doy so anything + * bigger than two digits had better be an explicit year. - thomas + * 1999-01-09 + */ + else if (flen > 2) + { + *tmask = DTK_M(YEAR); + + /* already have a year? then see if we can substitute... */ + if ((fmask & DTK_M(YEAR)) && (!(fmask & DTK_M(DAY))) + && ((tm->tm_year >= 1) && (tm->tm_year <= 31))) + { + tm->tm_mday = tm->tm_year; + *tmask = DTK_M(DAY); + } + + tm->tm_year = val; + } + /* already have year? then could be month */ + else if ((fmask & DTK_M(YEAR)) && (!(fmask & DTK_M(MONTH))) + && ((val >= 1) && (val <= 12))) + { + *tmask = DTK_M(MONTH); + tm->tm_mon = val; + /* no year and EuroDates enabled? then could be day */ + } + else if ((EuroDates || (fmask & DTK_M(MONTH))) + && (!(fmask & DTK_M(YEAR)) && !(fmask & DTK_M(DAY))) + && ((val >= 1) && (val <= 31))) + { + *tmask = DTK_M(DAY); + tm->tm_mday = val; + } + else if ((!(fmask & DTK_M(MONTH))) + && ((val >= 1) && (val <= 12))) + { + *tmask = DTK_M(MONTH); + tm->tm_mon = val; + } + else if ((!(fmask & DTK_M(DAY))) + && ((val >= 1) && (val <= 31))) + { + *tmask = DTK_M(DAY); + tm->tm_mday = val; + } + else if (!(fmask & DTK_M(YEAR))) + { + *tmask = DTK_M(YEAR); + tm->tm_year = val; + + /* adjust ONLY if exactly two digits... */ + *is2digits = (flen == 2); + } + else + return -1; + + return 0; +} /* DecodeNumber() */ + + +/* DecodeNumberField() + * Interpret numeric string as a concatenated date field. + */ +int +DecodeNumberField(int len, char *str, int fmask, + int *tmask, struct tm * tm, double *fsec, int *is2digits) { - if (!PointerIsValid(time1) || !PointerIsValid(time2)) - return FALSE; + char *cp; - return *time1 != *time2; -} /* time_eq() */ + /* yyyymmdd? */ + if (len == 8) + { + *tmask = DTK_DATE_M; -bool -time_lt(TimeADT *time1, TimeADT *time2) + tm->tm_mday = atoi(str + 6); + *(str + 6) = '\0'; + tm->tm_mon = atoi(str + 4); + *(str + 4) = '\0'; + tm->tm_year = atoi(str + 0); + /* yymmdd or hhmmss? */ + } + else if (len == 6) + { + if (fmask & DTK_DATE_M) + { + *tmask = DTK_TIME_M; + tm->tm_sec = atoi(str + 4); + *(str + 4) = '\0'; + tm->tm_min = atoi(str + 2); + *(str + 2) = '\0'; + tm->tm_hour = atoi(str + 0); + } + else + { + *tmask = DTK_DATE_M; + tm->tm_mday = atoi(str + 4); + *(str + 4) = '\0'; + tm->tm_mon = atoi(str + 2); + *(str + 2) = '\0'; + tm->tm_year = atoi(str + 0); + *is2digits = TRUE; + } + + } + else if ((len == 5) && !(fmask & DTK_DATE_M)) + { + *tmask = DTK_DATE_M; + tm->tm_mday = atoi(str + 2); + *(str + 2) = '\0'; + tm->tm_mon = 1; + tm->tm_year = atoi(str + 0); + *is2digits = TRUE; + } + else if (strchr(str, '.') != NULL) + { + *tmask = DTK_TIME_M; + tm->tm_sec = strtod((str + 4), &cp); + if (cp == (str + 4)) + return -1; + if (*cp == '.') + *fsec = strtod(cp, NULL); + *(str + 4) = '\0'; + tm->tm_min = strtod((str + 2), &cp); + *(str + 2) = '\0'; + tm->tm_hour = strtod((str + 0), &cp); + + } + else + return -1; + + return 0; +} /* DecodeNumberField() */ + + +/* DecodeTimezone() + * Interpret string as a numeric timezone. + */ +int +DecodeTimezone(char *str, int *tzp) { - if (!PointerIsValid(time1) || !PointerIsValid(time2)) - return FALSE; + int tz; + int hr, + min; + char *cp; + int len; - return *time1 < *time2; -} /* time_eq() */ + /* assume leading character is "+" or "-" */ + hr = strtol((str + 1), &cp, 10); -bool -time_le(TimeADT *time1, TimeADT *time2) + /* explicit delimiter? */ + if (*cp == ':') + { + min = strtol((cp + 1), &cp, 10); + + /* otherwise, might have run things together... */ + } + else if ((*cp == '\0') && ((len = strlen(str)) > 3)) + { + min = strtol((str + len - 2), &cp, 10); + *(str + len - 2) = '\0'; + hr = strtol((str + 1), &cp, 10); + + } + else + min = 0; + + tz = (hr * 60 + min) * 60; + if (*str == '-') + tz = -tz; + + *tzp = -tz; + return *cp != '\0'; +} /* DecodeTimezone() */ + + +/* DecodeSpecial() + * Decode text string using lookup table. + * Implement a cache lookup since it is likely that dates + * will be related in format. + */ +int +DecodeSpecial(int field, char *lowtoken, int *val) { - if (!PointerIsValid(time1) || !PointerIsValid(time2)) - return FALSE; + int type; + datetkn *tp; - return *time1 <= *time2; -} /* time_eq() */ +#if USE_DATE_CACHE + if ((datecache[field] != NULL) + && (strncmp(lowtoken, datecache[field]->token, TOKMAXLEN) == 0)) + tp = datecache[field]; + else + { +#endif + tp = datebsearch(lowtoken, datetktbl, szdatetktbl); +#if USE_DATE_CACHE + } + datecache[field] = tp; +#endif + if (tp == NULL) + { + type = IGNORE; + *val = 0; + } + else + { + type = tp->type; + switch (type) + { + case TZ: + case DTZ: + case DTZMOD: + *val = FROMVAL(tp); + break; -bool -time_gt(TimeADT *time1, TimeADT *time2) + default: + *val = tp->value; + break; + } + } + + return type; +} /* DecodeSpecial() */ + + +/* DecodeDateDelta() + * Interpret previously parsed fields for general time interval. + * Return 0 if decoded and -1 if problems. + * + * Allow "date" field DTK_DATE since this could be just + * an unsigned floating point number. - thomas 1997-11-16 + * + * Allow ISO-style time span, with implicit units on number of days + * preceeding an hh:mm:ss field. - thomas 1998-04-30 + */ +int +DecodeDateDelta(char **field, int *ftype, int nf, int *dtype, struct tm * tm, double *fsec) { - if (!PointerIsValid(time1) || !PointerIsValid(time2)) - return FALSE; + int is_before = FALSE; - return *time1 > *time2; -} /* time_eq() */ + char *cp; + int fmask = 0, + tmask, + type; + int i; + int flen, + val; + double fval; + double sec; -bool -time_ge(TimeADT *time1, TimeADT *time2) + *dtype = DTK_DELTA; + + type = DTK_SECOND; + tm->tm_year = 0; + tm->tm_mon = 0; + tm->tm_mday = 0; + tm->tm_hour = 0; + tm->tm_min = 0; + tm->tm_sec = 0; + *fsec = 0; + + /* read through list backwards to pick up units before values */ + for (i = nf - 1; i >= 0; i--) + { + switch (ftype[i]) + { + case DTK_TIME: + if (DecodeTime(field[i], fmask, &tmask, tm, fsec) != 0) + return -1; + type = DTK_DAY; + break; + + case DTK_TZ: + + /* + * Timezone is a token with a leading sign character and + * otherwise the same as a non-signed numeric field + */ + case DTK_DATE: + case DTK_NUMBER: + val = strtol(field[i], &cp, 10); + if (*cp == '.') + { + fval = strtod(cp, &cp); + if (*cp != '\0') + return -1; + + if (val < 0) + fval = -(fval); + } + else if (*cp == '\0') + fval = 0; + else + return -1; + + flen = strlen(field[i]); + tmask = 0; /* DTK_M(type); */ + + switch (type) + { + case DTK_MICROSEC: + *fsec += ((val + fval) * 1e-6); + break; + + case DTK_MILLISEC: + *fsec += ((val + fval) * 1e-3); + break; + + case DTK_SECOND: + tm->tm_sec += val; + *fsec += fval; + tmask = DTK_M(SECOND); + break; + + case DTK_MINUTE: + tm->tm_min += val; + if (fval != 0) + tm->tm_sec += (fval * 60); + tmask = DTK_M(MINUTE); + break; + + case DTK_HOUR: + tm->tm_hour += val; + if (fval != 0) + tm->tm_sec += (fval * 3600); + tmask = DTK_M(HOUR); + break; + + case DTK_DAY: + tm->tm_mday += val; + if (fval != 0) + tm->tm_sec += (fval * 86400); + tmask = ((fmask & DTK_M(DAY)) ? 0 : DTK_M(DAY)); + break; + + case DTK_WEEK: + tm->tm_mday += val * 7; + if (fval != 0) + tm->tm_sec += (fval * (7 * 86400)); + tmask = ((fmask & DTK_M(DAY)) ? 0 : DTK_M(DAY)); + break; + + case DTK_MONTH: + tm->tm_mon += val; + if (fval != 0) + tm->tm_sec += (fval * (30 * 86400)); + tmask = DTK_M(MONTH); + break; + + case DTK_YEAR: + tm->tm_year += val; + if (fval != 0) + tm->tm_mon += (fval * 12); + tmask = ((fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR)); + break; + + case DTK_DECADE: + tm->tm_year += val * 10; + if (fval != 0) + tm->tm_mon += (fval * 120); + tmask = ((fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR)); + break; + + case DTK_CENTURY: + tm->tm_year += val * 100; + if (fval != 0) + tm->tm_mon += (fval * 1200); + tmask = ((fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR)); + break; + + case DTK_MILLENIUM: + tm->tm_year += val * 1000; + if (fval != 0) + tm->tm_mon += (fval * 12000); + tmask = ((fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR)); + break; + + default: + return -1; + } + break; + + case DTK_STRING: + case DTK_SPECIAL: + type = DecodeUnits(i, field[i], &val); + if (type == IGNORE) + continue; + + tmask = 0; /* DTK_M(type); */ + switch (type) + { + case UNITS: + type = val; + break; + + case AGO: + is_before = TRUE; + type = val; + break; + + case RESERV: + tmask = (DTK_DATE_M || DTK_TIME_M); + *dtype = val; + break; + + default: + return -1; + } + break; + + default: + return -1; + } + + if (tmask & fmask) + return -1; + fmask |= tmask; + } + + if (*fsec != 0) + { + TMODULO(*fsec, sec, 1e0); + tm->tm_sec += sec; + } + + if (is_before) + { + *fsec = -(*fsec); + tm->tm_sec = -(tm->tm_sec); + tm->tm_min = -(tm->tm_min); + tm->tm_hour = -(tm->tm_hour); + tm->tm_mday = -(tm->tm_mday); + tm->tm_mon = -(tm->tm_mon); + tm->tm_year = -(tm->tm_year); + } + + /* ensure that at least one time field has been found */ + return (fmask != 0) ? 0 : -1; +} /* DecodeDateDelta() */ + + +/* DecodeUnits() + * Decode text string using lookup table. + * This routine supports time interval decoding. + */ +int +DecodeUnits(int field, char *lowtoken, int *val) { - if (!PointerIsValid(time1) || !PointerIsValid(time2)) - return FALSE; + int type; + datetkn *tp; - return *time1 >= *time2; -} /* time_eq() */ +#if USE_DATE_CACHE + if ((deltacache[field] != NULL) + && (strncmp(lowtoken, deltacache[field]->token, TOKMAXLEN) == 0)) + tp = deltacache[field]; + else + { +#endif + tp = datebsearch(lowtoken, deltatktbl, szdeltatktbl); +#if USE_DATE_CACHE + } + deltacache[field] = tp; +#endif + if (tp == NULL) + { + type = IGNORE; + *val = 0; + } + else + { + type = tp->type; + if ((type == TZ) || (type == DTZ)) + *val = FROMVAL(tp); + else + *val = tp->value; + } + + return type; +} /* DecodeUnits() */ + + +/* datebsearch() + * Binary search -- from Knuth (6.2.1) Algorithm B. Special case like this + * is WAY faster than the generic bsearch(). + */ +datetkn * +datebsearch(char *key, datetkn *base, unsigned int nel) +{ + datetkn *last = base + nel - 1, + *position; + int result; + + while (last >= base) + { + position = base + ((last - base) >> 1); + result = key[0] - position->token[0]; + if (result == 0) + { + result = strncmp(key, position->token, TOKMAXLEN); + if (result == 0) + return position; + } + if (result < 0) + last = position - 1; + else + base = position + 1; + } + return NULL; +} + + +/* EncodeDateOnly() + * Encode date as local time. + */ +int +EncodeDateOnly(struct tm * tm, int style, char *str) +{ + if ((tm->tm_mon < 1) || (tm->tm_mon > 12)) + return -1; + + switch (style) + { + /* compatible with ISO date formats */ + case USE_ISO_DATES: + if (tm->tm_year > 0) + sprintf(str, "%04d-%02d-%02d", + tm->tm_year, tm->tm_mon, tm->tm_mday); + else + sprintf(str, "%04d-%02d-%02d %s", + -(tm->tm_year - 1), tm->tm_mon, tm->tm_mday, "BC"); + break; + + /* compatible with Oracle/Ingres date formats */ + case USE_SQL_DATES: + if (EuroDates) + sprintf(str, "%02d/%02d", tm->tm_mday, tm->tm_mon); + else + sprintf(str, "%02d/%02d", tm->tm_mon, tm->tm_mday); + if (tm->tm_year > 0) + sprintf((str + 5), "/%04d", tm->tm_year); + else + sprintf((str + 5), "/%04d %s", -(tm->tm_year - 1), "BC"); + break; + + /* German-style date format */ + case USE_GERMAN_DATES: + sprintf(str, "%02d.%02d", tm->tm_mday, tm->tm_mon); + if (tm->tm_year > 0) + sprintf((str + 5), ".%04d", tm->tm_year); + else + sprintf((str + 5), ".%04d %s", -(tm->tm_year - 1), "BC"); + break; + + /* traditional date-only style for Postgres */ + case USE_POSTGRES_DATES: + default: + if (EuroDates) + sprintf(str, "%02d-%02d", tm->tm_mday, tm->tm_mon); + else + sprintf(str, "%02d-%02d", tm->tm_mon, tm->tm_mday); + if (tm->tm_year > 0) + sprintf((str + 5), "-%04d", tm->tm_year); + else + sprintf((str + 5), "-%04d %s", -(tm->tm_year - 1), "BC"); + break; + } + + return TRUE; +} /* EncodeDateOnly() */ + + +/* EncodeTimeOnly() + * Encode time fields only. + */ +int +EncodeTimeOnly(struct tm * tm, double fsec, int style, char *str) +{ + double sec; + + if ((tm->tm_hour < 0) || (tm->tm_hour > 24)) + return -1; + + sec = (tm->tm_sec + fsec); + + sprintf(str, "%02d:%02d:", tm->tm_hour, tm->tm_min); + sprintf((str + 6), ((fsec != 0) ? "%05.2f" : "%02.0f"), sec); + + return TRUE; +} /* EncodeTimeOnly() */ + + +/* EncodeDateTime() + * Encode date and time interpreted as local time. + * Support several date styles: + * Postgres - day mon hh:mm:ss yyyy tz + * SQL - mm/dd/yyyy hh:mm:ss.ss tz + * ISO - yyyy-mm-dd hh:mm:ss+/-tz + * German - dd.mm/yyyy hh:mm:ss tz + * Variants (affects order of month and day for Postgres and SQL styles): + * US - mm/dd/yyyy + * European - dd/mm/yyyy + */ +int +EncodeDateTime(struct tm * tm, double fsec, int *tzp, char **tzn, int style, char *str) +{ + int day, + hour, + min; + double sec; + + if ((tm->tm_mon < 1) || (tm->tm_mon > 12)) + return -1; + + sec = (tm->tm_sec + fsec); + + switch (style) + { + /* compatible with ISO date formats */ + + case USE_ISO_DATES: + if (tm->tm_year > 0) + { + sprintf(str, "%04d-%02d-%02d %02d:%02d:", + tm->tm_year, tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min); + sprintf((str + 17), ((fsec != 0) ? "%05.2f" : "%02.0f"), sec); + + if ((*tzn != NULL) && (tm->tm_isdst >= 0)) + { + if (tzp != NULL) + { + hour = -(*tzp / 3600); + min = ((abs(*tzp) / 60) % 60); + } + else + { + hour = 0; + min = 0; + } + sprintf((str + strlen(str)), ((min != 0) ? "%+03d:%02d" : "%+03d"), hour, min); + } + + } + else + { + if (tm->tm_hour || tm->tm_min) + sprintf(str, "%04d-%02d-%02d %02d:%02d %s", + -(tm->tm_year - 1), tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min, "BC"); + else + sprintf(str, "%04d-%02d-%02d %s", + -(tm->tm_year - 1), tm->tm_mon, tm->tm_mday, "BC"); + } + break; + + /* compatible with Oracle/Ingres date formats */ + case USE_SQL_DATES: + if (EuroDates) + sprintf(str, "%02d/%02d", tm->tm_mday, tm->tm_mon); + else + sprintf(str, "%02d/%02d", tm->tm_mon, tm->tm_mday); + + if (tm->tm_year > 0) + { + sprintf((str + 5), "/%04d %02d:%02d:%05.2f", + tm->tm_year, tm->tm_hour, tm->tm_min, sec); + + if ((*tzn != NULL) && (tm->tm_isdst >= 0)) + { + strcpy((str + 22), " "); + strcpy((str + 23), *tzn); + } + + } + else + sprintf((str + 5), "/%04d %02d:%02d %s", + -(tm->tm_year - 1), tm->tm_hour, tm->tm_min, "BC"); + break; + + /* German variant on European style */ + case USE_GERMAN_DATES: + sprintf(str, "%02d.%02d", tm->tm_mday, tm->tm_mon); + if (tm->tm_year > 0) + { + sprintf((str + 5), ".%04d %02d:%02d:%05.2f", + tm->tm_year, tm->tm_hour, tm->tm_min, sec); + + if ((*tzn != NULL) && (tm->tm_isdst >= 0)) + { + strcpy((str + 22), " "); + strcpy((str + 23), *tzn); + } + + } + else + sprintf((str + 5), ".%04d %02d:%02d %s", + -(tm->tm_year - 1), tm->tm_hour, tm->tm_min, "BC"); + break; + + /* backward-compatible with traditional Postgres abstime dates */ + case USE_POSTGRES_DATES: + default: + day = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday); + tm->tm_wday = j2day(day); + + strncpy(str, days[tm->tm_wday], 3); + strcpy((str + 3), " "); + + if (EuroDates) + sprintf((str + 4), "%02d %3s", tm->tm_mday, months[tm->tm_mon - 1]); + else + sprintf((str + 4), "%3s %02d", months[tm->tm_mon - 1], tm->tm_mday); + + if (tm->tm_year > 0) + { + sprintf((str + 10), " %02d:%02d", tm->tm_hour, tm->tm_min); + if (fsec != 0) + { + sprintf((str + 16), ":%05.2f %04d", sec, tm->tm_year); + if ((*tzn != NULL) && (tm->tm_isdst >= 0)) + { + strcpy((str + 27), " "); + strncpy((str + 28), *tzn, MAXTZLEN); + } + } + else + { + sprintf((str + 16), ":%02.0f %04d", sec, tm->tm_year); + if ((*tzn != NULL) && (tm->tm_isdst >= 0)) + { + strcpy((str + 24), " "); + strncpy((str + 25), *tzn, MAXTZLEN); + } + } + + } + else + { + sprintf((str + 10), " %02d:%02d %04d %s", + tm->tm_hour, tm->tm_min, -(tm->tm_year - 1), "BC"); + } + break; + } + + return TRUE; +} /* EncodeDateTime() */ + + +/* EncodeTimeSpan() + * Interpret time structure as a delta time and convert to string. + * + * Support "traditional Postgres" and ISO-8601 styles. + * Actually, afaik ISO does not address time interval formatting, + * but this looks similar to the spec for absolute date/time. + * - thomas 1998-04-30 + */ +int +EncodeTimeSpan(struct tm * tm, double fsec, int style, char *str) +{ + int is_before = FALSE; + int is_nonzero = FALSE; + char *cp = str; + + switch (style) + { + /* compatible with ISO date formats */ + case USE_ISO_DATES: + break; + + default: + strcpy(cp, "@ "); + cp += strlen(cp); + break; + } + + if (tm->tm_year != 0) + { + is_before |= (tm->tm_year < 0); + sprintf(cp, "%d year%s", + abs(tm->tm_year), ((abs(tm->tm_year) != 1) ? "s" : "")); + cp += strlen(cp); + is_nonzero = TRUE; + } + + if (tm->tm_mon != 0) + { + is_before |= (tm->tm_mon < 0); + sprintf(cp, "%s%d mon%s", (is_nonzero ? " " : ""), + abs(tm->tm_mon), ((abs(tm->tm_mon) != 1) ? "s" : "")); + cp += strlen(cp); + is_nonzero = TRUE; + } + + switch (style) + { + /* compatible with ISO date formats */ + case USE_ISO_DATES: + if (tm->tm_mday != 0) + { + is_before |= (tm->tm_mday < 0); + sprintf(cp, "%s%d", (is_nonzero ? " " : ""), abs(tm->tm_mday)); + cp += strlen(cp); + is_nonzero = TRUE; + } + is_before |= ((tm->tm_hour < 0) || (tm->tm_min < 0)); + sprintf(cp, "%s%02d:%02d", (is_nonzero ? " " : ""), + abs(tm->tm_hour), abs(tm->tm_min)); + cp += strlen(cp); + if ((tm->tm_hour != 0) || (tm->tm_min != 0)) + is_nonzero = TRUE; + + /* fractional seconds? */ + if (fsec != 0) + { + fsec += tm->tm_sec; + is_before |= (fsec < 0); + sprintf(cp, ":%05.2f", fabs(fsec)); + cp += strlen(cp); + is_nonzero = TRUE; + + /* otherwise, integer seconds only? */ + } + else if (tm->tm_sec != 0) + { + is_before |= (tm->tm_sec < 0); + sprintf(cp, ":%02d", abs(tm->tm_sec)); + cp += strlen(cp); + is_nonzero = TRUE; + } + break; + + case USE_POSTGRES_DATES: + default: + if (tm->tm_mday != 0) + { + is_before |= (tm->tm_mday < 0); + sprintf(cp, "%s%d day%s", (is_nonzero ? " " : ""), + abs(tm->tm_mday), ((abs(tm->tm_mday) != 1) ? "s" : "")); + cp += strlen(cp); + is_nonzero = TRUE; + } + if (tm->tm_hour != 0) + { + is_before |= (tm->tm_hour < 0); + sprintf(cp, "%s%d hour%s", (is_nonzero ? " " : ""), + abs(tm->tm_hour), ((abs(tm->tm_hour) != 1) ? "s" : "")); + cp += strlen(cp); + is_nonzero = TRUE; + } + + if (tm->tm_min != 0) + { + is_before |= (tm->tm_min < 0); + sprintf(cp, "%s%d min%s", (is_nonzero ? " " : ""), + abs(tm->tm_min), ((abs(tm->tm_min) != 1) ? "s" : "")); + cp += strlen(cp); + is_nonzero = TRUE; + } + + /* fractional seconds? */ + if (fsec != 0) + { + fsec += tm->tm_sec; + is_before |= (fsec < 0); + sprintf(cp, "%s%.2f secs", (is_nonzero ? " " : ""), fabs(fsec)); + cp += strlen(cp); + is_nonzero = TRUE; + + /* otherwise, integer seconds only? */ + } + else if (tm->tm_sec != 0) + { + is_before |= (tm->tm_sec < 0); + sprintf(cp, "%s%d sec%s", (is_nonzero ? " " : ""), + abs(tm->tm_sec), ((abs(tm->tm_sec) != 1) ? "s" : "")); + cp += strlen(cp); + is_nonzero = TRUE; + } + break; + } + + /* identically zero? then put in a unitless zero... */ + if (!is_nonzero) + { + strcat(cp, "0"); + cp += strlen(cp); + } + + if (is_before) + { + strcat(cp, " ago"); + cp += strlen(cp); + } + + return 0; +} /* EncodeTimeSpan() */ + + +#if defined(linux) && defined(__powerpc__) int -time_cmp(TimeADT *time1, TimeADT *time2) +timestamp_is_epoch(double j) { - return (*time1 < *time2) ? -1 : (((*time1 > *time2) ? 1 : 0)); -} /* time_cmp() */ - - -/* datetime_time() - * Convert datetime to time data type. - */ -TimeADT * -datetime_time(DateTime *datetime) -{ - TimeADT *result; - struct tm tt, - *tm = &tt; - int tz; - double fsec; - char *tzn; - - if (!PointerIsValid(datetime)) - elog(ERROR, "Unable to convert null datetime to date"); - - if (DATETIME_NOT_FINITE(*datetime)) - elog(ERROR, "Unable to convert datetime to date"); - - if (DATETIME_IS_EPOCH(*datetime)) + static union { - datetime2tm(SetDateTime(*datetime), NULL, tm, &fsec, NULL); + double epoch; + unsigned char c[8]; + } u; - } - else if (DATETIME_IS_CURRENT(*datetime)) - { - datetime2tm(SetDateTime(*datetime), &tz, tm, &fsec, &tzn); + u.c[0] = 0x80; /* sign bit */ + u.c[1] = 0x10; /* DBL_MIN */ - } - else - { - if (datetime2tm(*datetime, &tz, tm, &fsec, &tzn) != 0) - elog(ERROR, "Unable to convert datetime to date"); - } - - result = palloc(sizeof(TimeADT)); - - *result = ((((tm->tm_hour * 60) + tm->tm_min) * 60) + tm->tm_sec + fsec); - - return result; -} /* datetime_time() */ - - -/* datetime_datetime() - * Convert date and time to datetime data type. - */ -DateTime * -datetime_datetime(DateADT date, TimeADT *time) -{ - DateTime *result; - - if (!PointerIsValid(time)) - { - result = palloc(sizeof(DateTime)); - DATETIME_INVALID(*result); - } - else - { - result = date_datetime(date); - *result += *time; - } - - return result; -} /* datetime_datetime() */ - - -int32 /* RelativeTime */ -int4reltime(int32 timevalue) -{ - return timevalue; + return j == u.epoch; } +int +timestamp_is_current(double j) +{ + static union + { + double current; + unsigned char c[8]; + } u; + + u.c[1] = 0x10; /* DBL_MIN */ + + return j == u.current; +} + +#endif