/*-------------------------------------------------------------------------
*
* date.c
- * implements DATE and TIME data types specified in SQL-92 standard
+ * implements DATE and TIME data types specified in SQL standard
*
- * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
* Portions Copyright (c) 1994-5, Regents of the University of California
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/adt/date.c,v 1.116 2005/07/21 18:06:12 momjian Exp $
+ * src/backend/utils/adt/date.c
*
*-------------------------------------------------------------------------
*/
#include <ctype.h>
#include <limits.h>
#include <float.h>
-#include <time.h>
+#include <time.h>
#include "access/hash.h"
+#include "access/xact.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
#include "parser/scansup.h"
+#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/date.h"
+#include "utils/datetime.h"
#include "utils/nabstime.h"
-#include "utils/timestamp.h"
+#include "utils/sortsupport.h"
/*
* gcc's -ffast-math switch breaks routines that expect exact results from
static int tm2timetz(struct pg_tm * tm, fsec_t fsec, int tz, TimeTzADT *result);
static void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
+
+/* common code for timetypmodin and timetztypmodin */
+static int32
+anytime_typmodin(bool istz, ArrayType *ta)
+{
+ int32 *tl;
+ int n;
+
+ tl = ArrayGetIntegerTypmods(ta, &n);
+
+ /*
+ * we're not too tense about good error message here because grammar
+ * shouldn't allow wrong number of modifiers for TIME
+ */
+ if (n != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid type modifier")));
+
+ return anytime_typmod_check(istz, tl[0]);
+}
+
+/* exported so parse_expr.c can use it */
+int32
+anytime_typmod_check(bool istz, int32 typmod)
+{
+ if (typmod < 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("TIME(%d)%s precision must not be negative",
+ typmod, (istz ? " WITH TIME ZONE" : ""))));
+ if (typmod > MAX_TIME_PRECISION)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("TIME(%d)%s precision reduced to maximum allowed, %d",
+ typmod, (istz ? " WITH TIME ZONE" : ""),
+ MAX_TIME_PRECISION)));
+ typmod = MAX_TIME_PRECISION;
+ }
+
+ return typmod;
+}
+
+/* common code for timetypmodout and timetztypmodout */
+static char *
+anytime_typmodout(bool istz, int32 typmod)
+{
+ const char *tz = istz ? " with time zone" : " without time zone";
+
+ if (typmod >= 0)
+ return psprintf("(%d)%s", (int) typmod, tz);
+ else
+ return psprintf("%s", tz);
+}
+
+
/*****************************************************************************
* Date ADT
*****************************************************************************/
date_in(PG_FUNCTION_ARGS)
{
char *str = PG_GETARG_CSTRING(0);
- DateADT date;
+ DateADT date;
fsec_t fsec;
struct pg_tm tt,
*tm = &tt;
case DTK_CURRENT:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("date/time value \"current\" is no longer supported")));
+ errmsg("date/time value \"current\" is no longer supported")));
GetCurrentDateTime(tm);
break;
GetEpochTime(tm);
break;
+ case DTK_LATE:
+ DATE_NOEND(date);
+ PG_RETURN_DATEADT(date);
+
+ case DTK_EARLY:
+ DATE_NOBEGIN(date);
+ PG_RETURN_DATEADT(date);
+
default:
DateTimeParseError(DTERR_BAD_FORMAT, str, "date");
break;
}
+ /* Prevent overflow in Julian-day routines */
+ if (!IS_VALID_JULIAN(tm->tm_year, tm->tm_mon, tm->tm_mday))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range: \"%s\"", str)));
+
date = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - POSTGRES_EPOCH_JDATE;
+ /* Now check for just-out-of-range dates */
+ if (!IS_VALID_DATE(date))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range: \"%s\"", str)));
+
PG_RETURN_DATEADT(date);
}
Datum
date_out(PG_FUNCTION_ARGS)
{
- DateADT date = PG_GETARG_DATEADT(0);
+ DateADT date = PG_GETARG_DATEADT(0);
char *result;
struct pg_tm tt,
*tm = &tt;
char buf[MAXDATELEN + 1];
- j2date(date +POSTGRES_EPOCH_JDATE,
- &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday));
-
- EncodeDateOnly(tm, DateStyle, buf);
+ if (DATE_NOT_FINITE(date))
+ EncodeSpecialDate(date, buf);
+ else
+ {
+ j2date(date + POSTGRES_EPOCH_JDATE,
+ &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday));
+ EncodeDateOnly(tm, DateStyle, buf);
+ }
result = pstrdup(buf);
PG_RETURN_CSTRING(result);
date_recv(PG_FUNCTION_ARGS)
{
StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
+ DateADT result;
+
+ result = (DateADT) pq_getmsgint(buf, sizeof(DateADT));
+
+ /* Limit to the same range that date_in() accepts. */
+ if (DATE_NOT_FINITE(result))
+ /* ok */ ;
+ else if (!IS_VALID_DATE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range")));
- PG_RETURN_DATEADT((DateADT) pq_getmsgint(buf, sizeof(DateADT)));
+ PG_RETURN_DATEADT(result);
}
/*
Datum
date_send(PG_FUNCTION_ARGS)
{
- DateADT date = PG_GETARG_DATEADT(0);
+ DateADT date = PG_GETARG_DATEADT(0);
StringInfoData buf;
pq_begintypsend(&buf);
PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
}
+/*
+ * make_date - date constructor
+ */
+Datum
+make_date(PG_FUNCTION_ARGS)
+{
+ struct pg_tm tm;
+ DateADT date;
+ int dterr;
+ bool bc = false;
+
+ tm.tm_year = PG_GETARG_INT32(0);
+ tm.tm_mon = PG_GETARG_INT32(1);
+ tm.tm_mday = PG_GETARG_INT32(2);
+
+ /* Handle negative years as BC */
+ if (tm.tm_year < 0)
+ {
+ bc = true;
+ tm.tm_year = -tm.tm_year;
+ }
+
+ dterr = ValidateDate(DTK_DATE_M, false, false, bc, &tm);
+
+ if (dterr != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_FIELD_OVERFLOW),
+ errmsg("date field value out of range: %d-%02d-%02d",
+ tm.tm_year, tm.tm_mon, tm.tm_mday)));
+
+ /* Prevent overflow in Julian-day routines */
+ if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range: %d-%02d-%02d",
+ tm.tm_year, tm.tm_mon, tm.tm_mday)));
+
+ date = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) - POSTGRES_EPOCH_JDATE;
+
+ /* Now check for just-out-of-range dates */
+ if (!IS_VALID_DATE(date))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range: %d-%02d-%02d",
+ tm.tm_year, tm.tm_mon, tm.tm_mday)));
+
+ PG_RETURN_DATEADT(date);
+}
+
+/*
+ * Convert reserved date values to string.
+ */
+void
+EncodeSpecialDate(DateADT dt, char *str)
+{
+ if (DATE_IS_NOBEGIN(dt))
+ strcpy(str, EARLY);
+ else if (DATE_IS_NOEND(dt))
+ strcpy(str, LATE);
+ else /* shouldn't happen */
+ elog(ERROR, "invalid argument for EncodeSpecialDate");
+}
+
+
+/*
+ * GetSQLCurrentDate -- implements CURRENT_DATE
+ */
+DateADT
+GetSQLCurrentDate(void)
+{
+ TimestampTz ts;
+ struct pg_tm tt,
+ *tm = &tt;
+ fsec_t fsec;
+ int tz;
+
+ ts = GetCurrentTransactionStartTimestamp();
+
+ if (timestamp2tm(ts, &tz, tm, &fsec, NULL, NULL) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+
+ return date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - POSTGRES_EPOCH_JDATE;
+}
+
+/*
+ * GetSQLCurrentTime -- implements CURRENT_TIME, CURRENT_TIME(n)
+ */
+TimeTzADT *
+GetSQLCurrentTime(int32 typmod)
+{
+ TimeTzADT *result;
+ TimestampTz ts;
+ struct pg_tm tt,
+ *tm = &tt;
+ fsec_t fsec;
+ int tz;
+
+ ts = GetCurrentTransactionStartTimestamp();
+
+ if (timestamp2tm(ts, &tz, tm, &fsec, NULL, NULL) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+
+ result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
+ tm2timetz(tm, fsec, tz, result);
+ AdjustTimeForTypmod(&(result->time), typmod);
+ return result;
+}
+
+/*
+ * GetSQLLocalTime -- implements LOCALTIME, LOCALTIME(n)
+ */
+TimeADT
+GetSQLLocalTime(int32 typmod)
+{
+ TimeADT result;
+ TimestampTz ts;
+ struct pg_tm tt,
+ *tm = &tt;
+ fsec_t fsec;
+ int tz;
+
+ ts = GetCurrentTransactionStartTimestamp();
+
+ if (timestamp2tm(ts, &tz, tm, &fsec, NULL, NULL) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+
+ tm2time(tm, fsec, &result);
+ AdjustTimeForTypmod(&result, typmod);
+ return result;
+}
+
/*
* Comparison functions for dates
PG_RETURN_INT32(0);
}
+static int
+date_fastcmp(Datum x, Datum y, SortSupport ssup)
+{
+ DateADT a = DatumGetDateADT(x);
+ DateADT b = DatumGetDateADT(y);
+
+ if (a < b)
+ return -1;
+ else if (a > b)
+ return 1;
+ return 0;
+}
+
+Datum
+date_sortsupport(PG_FUNCTION_ARGS)
+{
+ SortSupport ssup = (SortSupport) PG_GETARG_POINTER(0);
+
+ ssup->comparator = date_fastcmp;
+ PG_RETURN_VOID();
+}
+
+Datum
+date_finite(PG_FUNCTION_ARGS)
+{
+ DateADT date = PG_GETARG_DATEADT(0);
+
+ PG_RETURN_BOOL(!DATE_NOT_FINITE(date));
+}
+
Datum
date_larger(PG_FUNCTION_ARGS)
{
DateADT dateVal1 = PG_GETARG_DATEADT(0);
DateADT dateVal2 = PG_GETARG_DATEADT(1);
+ if (DATE_NOT_FINITE(dateVal1) || DATE_NOT_FINITE(dateVal2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite dates")));
+
PG_RETURN_INT32((int32) (dateVal1 - dateVal2));
}
{
DateADT dateVal = PG_GETARG_DATEADT(0);
int32 days = PG_GETARG_INT32(1);
+ DateADT result;
+
+ if (DATE_NOT_FINITE(dateVal))
+ PG_RETURN_DATEADT(dateVal); /* can't change infinity */
- PG_RETURN_DATEADT(dateVal + days);
+ result = dateVal + days;
+
+ /* Check for integer overflow and out-of-allowed-range */
+ if ((days >= 0 ? (result < dateVal) : (result > dateVal)) ||
+ !IS_VALID_DATE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range")));
+
+ PG_RETURN_DATEADT(result);
}
/* Subtract a number of days from a date, giving a new date.
{
DateADT dateVal = PG_GETARG_DATEADT(0);
int32 days = PG_GETARG_INT32(1);
+ DateADT result;
+
+ if (DATE_NOT_FINITE(dateVal))
+ PG_RETURN_DATEADT(dateVal); /* can't change infinity */
- PG_RETURN_DATEADT(dateVal - days);
+ result = dateVal - days;
+
+ /* Check for integer overflow and out-of-allowed-range */
+ if ((days >= 0 ? (result > dateVal) : (result < dateVal)) ||
+ !IS_VALID_DATE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range")));
+
+ PG_RETURN_DATEADT(result);
}
/*
* time zone
*/
-#ifdef HAVE_INT64_TIMESTAMP
-/* date is days since 2000, timestamp is microseconds since same... */
-#define date2timestamp(dateVal) \
- ((Timestamp) ((dateVal) * USECS_PER_DAY))
-#else
-/* date is days since 2000, timestamp is seconds since same... */
-#define date2timestamp(dateVal) \
- ((Timestamp) ((dateVal) * (double)SECS_PER_DAY))
-#endif
+static Timestamp
+date2timestamp(DateADT dateVal)
+{
+ Timestamp result;
+
+ if (DATE_IS_NOBEGIN(dateVal))
+ TIMESTAMP_NOBEGIN(result);
+ else if (DATE_IS_NOEND(dateVal))
+ TIMESTAMP_NOEND(result);
+ else
+ {
+ /*
+ * Date's range is wider than timestamp's, so check for boundaries.
+ * Since dates have the same minimum values as timestamps, only upper
+ * boundary need be checked for overflow.
+ */
+ if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range for timestamp")));
+
+ /* date is days since 2000, timestamp is microseconds since same... */
+ result = dateVal * USECS_PER_DAY;
+ }
+
+ return result;
+}
static TimestampTz
date2timestamptz(DateADT dateVal)
*tm = &tt;
int tz;
- j2date(dateVal + POSTGRES_EPOCH_JDATE,
- &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday));
+ if (DATE_IS_NOBEGIN(dateVal))
+ TIMESTAMP_NOBEGIN(result);
+ else if (DATE_IS_NOEND(dateVal))
+ TIMESTAMP_NOEND(result);
+ else
+ {
+ /*
+ * Date's range is wider than timestamp's, so check for boundaries.
+ * Since dates have the same minimum values as timestamps, only upper
+ * boundary need be checked for overflow.
+ */
+ if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range for timestamp")));
- tm->tm_hour = 0;
- tm->tm_min = 0;
- tm->tm_sec = 0;
- tz = DetermineLocalTimeZone(tm);
+ j2date(dateVal + POSTGRES_EPOCH_JDATE,
+ &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday));
+ tm->tm_hour = 0;
+ tm->tm_min = 0;
+ tm->tm_sec = 0;
+ tz = DetermineTimeZoneOffset(tm, session_timezone);
-#ifdef HAVE_INT64_TIMESTAMP
- result = dateVal * USECS_PER_DAY + tz * USECS_PER_SEC;
-#else
- result = dateVal * (double)SECS_PER_DAY + tz;
-#endif
+ result = dateVal * USECS_PER_DAY + tz * USECS_PER_SEC;
+
+ /*
+ * Since it is possible to go beyond allowed timestamptz range because
+ * of time zone, check for allowed timestamp range after adding tz.
+ */
+ if (!IS_VALID_TIMESTAMP(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range for timestamp")));
+ }
+
+ return result;
+}
+
+/*
+ * date2timestamp_no_overflow
+ *
+ * This is chartered to produce a double value that is numerically
+ * equivalent to the corresponding Timestamp value, if the date is in the
+ * valid range of Timestamps, but in any case not throw an overflow error.
+ * We can do this since the numerical range of double is greater than
+ * that of non-erroneous timestamps. The results are currently only
+ * used for statistical estimation purposes.
+ */
+double
+date2timestamp_no_overflow(DateADT dateVal)
+{
+ double result;
+
+ if (DATE_IS_NOBEGIN(dateVal))
+ result = -DBL_MAX;
+ else if (DATE_IS_NOEND(dateVal))
+ result = DBL_MAX;
+ else
+ {
+ /* date is days since 2000, timestamp is microseconds since same... */
+ result = dateVal * (double) USECS_PER_DAY;
+ }
return result;
}
PG_RETURN_TIMESTAMP(result);
}
-
/* timestamp_date()
* Convert timestamp to date data type.
*/
Datum
timestamp_date(PG_FUNCTION_ARGS)
{
- Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
+ Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
struct pg_tm tt,
*tm = &tt;
fsec_t fsec;
- if (TIMESTAMP_NOT_FINITE(timestamp))
- PG_RETURN_NULL();
-
- if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
+ if (TIMESTAMP_IS_NOBEGIN(timestamp))
+ DATE_NOBEGIN(result);
+ else if (TIMESTAMP_IS_NOEND(timestamp))
+ DATE_NOEND(result);
+ else
+ {
+ if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
- result = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - POSTGRES_EPOCH_JDATE;
+ result = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - POSTGRES_EPOCH_JDATE;
+ }
PG_RETURN_DATEADT(result);
}
*tm = &tt;
fsec_t fsec;
int tz;
- char *tzn;
- if (TIMESTAMP_NOT_FINITE(timestamp))
- PG_RETURN_NULL();
-
- if (timestamp2tm(timestamp, &tz, tm, &fsec, &tzn, NULL) != 0)
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
+ if (TIMESTAMP_IS_NOBEGIN(timestamp))
+ DATE_NOBEGIN(result);
+ else if (TIMESTAMP_IS_NOEND(timestamp))
+ DATE_NOEND(result);
+ else
+ {
+ if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
- result = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - POSTGRES_EPOCH_JDATE;
+ result = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - POSTGRES_EPOCH_JDATE;
+ }
PG_RETURN_DATEADT(result);
}
switch (abstime)
{
case INVALID_ABSTIME:
- case NOSTART_ABSTIME:
- case NOEND_ABSTIME:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot convert reserved abstime value to date")));
+ errmsg("cannot convert reserved abstime value to date")));
+ result = 0; /* keep compiler quiet */
+ break;
- /*
- * pretend to drop through to make compiler think that result
- * will be set
- */
+ case NOSTART_ABSTIME:
+ DATE_NOBEGIN(result);
+ break;
+
+ case NOEND_ABSTIME:
+ DATE_NOEND(result);
+ break;
default:
abstime2tm(abstime, &tz, tm, NULL);
+ /* Prevent overflow in Julian-day routines */
+ if (!IS_VALID_JULIAN(tm->tm_year, tm->tm_mon, tm->tm_mday))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("abstime out of range for date")));
result = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - POSTGRES_EPOCH_JDATE;
+ /* Now check for just-out-of-range dates */
+ if (!IS_VALID_DATE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("abstime out of range for date")));
break;
}
}
-/* date_text()
- * Convert date to text data type.
- */
-Datum
-date_text(PG_FUNCTION_ARGS)
-{
- /* Input is a Date, but may as well leave it in Datum form */
- Datum date = PG_GETARG_DATUM(0);
- text *result;
- char *str;
- int len;
-
- str = DatumGetCString(DirectFunctionCall1(date_out, date));
-
- len = strlen(str) + VARHDRSZ;
-
- result = palloc(len);
-
- VARATT_SIZEP(result) = len;
- memmove(VARDATA(result), str, (len - VARHDRSZ));
-
- pfree(str);
-
- PG_RETURN_TEXT_P(result);
-}
-
-
-/* text_date()
- * Convert text string to date.
- * Text type is not null terminated, so use temporary string
- * then call the standard input routine.
- */
-Datum
-text_date(PG_FUNCTION_ARGS)
-{
- text *str = PG_GETARG_TEXT_P(0);
- int i;
- char *sp,
- *dp,
- dstr[MAXDATELEN + 1];
-
- if (VARSIZE(str) - VARHDRSZ > MAXDATELEN)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
- errmsg("invalid input syntax for type date: \"%s\"",
- VARDATA(str))));
-
- sp = VARDATA(str);
- dp = dstr;
- for (i = 0; i < (VARSIZE(str) - VARHDRSZ); i++)
- *dp++ = *sp++;
- *dp = '\0';
-
- return DirectFunctionCall1(date_in,
- CStringGetDatum(dstr));
-}
-
-
/*****************************************************************************
* Time ADT
*****************************************************************************/
static int
tm2time(struct pg_tm * tm, fsec_t fsec, TimeADT *result)
{
-#ifdef HAVE_INT64_TIMESTAMP
*result = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec)
- * USECS_PER_SEC) + fsec;
-#else
- *result = ((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec + fsec;
-#endif
+ * USECS_PER_SEC) + fsec;
return 0;
}
/* time2tm()
* Convert time data type to POSIX time structure.
- * For dates within the system-supported time_t range, convert to the
- * local time zone. If out of this range, leave as GMT. - tgl 97/05/27
+ *
+ * For dates within the range of pg_time_t, convert to the local time zone.
+ * If out of this range, leave as UTC (in practice that could only happen
+ * if pg_time_t is just 32 bits) - thomas 97/05/27
*/
static int
time2tm(TimeADT time, struct pg_tm * tm, fsec_t *fsec)
{
-#ifdef HAVE_INT64_TIMESTAMP
tm->tm_hour = time / USECS_PER_HOUR;
time -= tm->tm_hour * USECS_PER_HOUR;
tm->tm_min = time / USECS_PER_MINUTE;
tm->tm_sec = time / USECS_PER_SEC;
time -= tm->tm_sec * USECS_PER_SEC;
*fsec = time;
-#else
- double trem;
-
- trem = time;
- TMODULO(trem, tm->tm_hour, (double)SECS_PER_HOUR);
- TMODULO(trem, tm->tm_min, (double)SECS_PER_MINUTE);
- TMODULO(trem, tm->tm_sec, 1.0);
- *fsec = trem;
-#endif
-
return 0;
}
char buf[MAXDATELEN + 1];
time2tm(time, tm, &fsec);
- EncodeTimeOnly(tm, fsec, NULL, DateStyle, buf);
+ EncodeTimeOnly(tm, fsec, false, 0, DateStyle, buf);
result = pstrdup(buf);
PG_RETURN_CSTRING(result);
/*
* time_recv - converts external binary format to time
- *
- * We make no attempt to provide compatibility between int and float
- * time representations ...
*/
Datum
time_recv(PG_FUNCTION_ARGS)
{
StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
+
#ifdef NOT_USED
Oid typelem = PG_GETARG_OID(1);
#endif
int32 typmod = PG_GETARG_INT32(2);
TimeADT result;
-#ifdef HAVE_INT64_TIMESTAMP
result = pq_getmsgint64(buf);
-#else
- result = pq_getmsgfloat8(buf);
-#endif
+
+ if (result < INT64CONST(0) || result > USECS_PER_DAY)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time out of range")));
AdjustTimeForTypmod(&result, typmod);
StringInfoData buf;
pq_begintypsend(&buf);
-#ifdef HAVE_INT64_TIMESTAMP
pq_sendint64(&buf, time);
-#else
- pq_sendfloat8(&buf, time);
-#endif
PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
}
+Datum
+timetypmodin(PG_FUNCTION_ARGS)
+{
+ ArrayType *ta = PG_GETARG_ARRAYTYPE_P(0);
+
+ PG_RETURN_INT32(anytime_typmodin(false, ta));
+}
+
+Datum
+timetypmodout(PG_FUNCTION_ARGS)
+{
+ int32 typmod = PG_GETARG_INT32(0);
+
+ PG_RETURN_CSTRING(anytime_typmodout(false, typmod));
+}
+
+/*
+ * make_time - time constructor
+ */
+Datum
+make_time(PG_FUNCTION_ARGS)
+{
+ int tm_hour = PG_GETARG_INT32(0);
+ int tm_min = PG_GETARG_INT32(1);
+ double sec = PG_GETARG_FLOAT8(2);
+ TimeADT time;
+
+ /* This should match the checks in DecodeTimeOnly */
+ if (tm_hour < 0 || tm_min < 0 || tm_min > MINS_PER_HOUR - 1 ||
+ sec < 0 || sec > SECS_PER_MINUTE ||
+ tm_hour > HOURS_PER_DAY ||
+ /* test for > 24:00:00 */
+ (tm_hour == HOURS_PER_DAY && (tm_min > 0 || sec > 0)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_FIELD_OVERFLOW),
+ errmsg("time field value out of range: %d:%02d:%02g",
+ tm_hour, tm_min, sec)));
+
+ /* This should match tm2time */
+ time = (((tm_hour * MINS_PER_HOUR + tm_min) * SECS_PER_MINUTE)
+ * USECS_PER_SEC) + rint(sec * USECS_PER_SEC);
+
+ PG_RETURN_TIMEADT(time);
+}
+
+
+/* time_transform()
+ * Flatten calls to time_scale() and timetz_scale() that solely represent
+ * increases in allowed precision.
+ */
+Datum
+time_transform(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_POINTER(TemporalTransform(MAX_TIME_PRECISION,
+ (Node *) PG_GETARG_POINTER(0)));
+}
/* time_scale()
* Adjust time type for specified scale factor.
static void
AdjustTimeForTypmod(TimeADT *time, int32 typmod)
{
-#ifdef HAVE_INT64_TIMESTAMP
static const int64 TimeScales[MAX_TIME_PRECISION + 1] = {
INT64CONST(1000000),
INT64CONST(100000),
INT64CONST(0)
};
-#else
- /* note MAX_TIME_PRECISION differs in this case */
- static const double TimeScales[MAX_TIME_PRECISION + 1] = {
- 1.0,
- 10.0,
- 100.0,
- 1000.0,
- 10000.0,
- 100000.0,
- 1000000.0,
- 10000000.0,
- 100000000.0,
- 1000000000.0,
- 10000000000.0
- };
-#endif
-
if (typmod >= 0 && typmod <= MAX_TIME_PRECISION)
{
- /*
- * Note: this round-to-nearest code is not completely consistent
- * about rounding values that are exactly halfway between integral
- * values. On most platforms, rint() will implement
- * round-to-nearest-even, but the integer code always rounds up
- * (away from zero). Is it worth trying to be consistent?
- */
-#ifdef HAVE_INT64_TIMESTAMP
if (*time >= INT64CONST(0))
- {
*time = ((*time + TimeOffsets[typmod]) / TimeScales[typmod]) *
- TimeScales[typmod];
- }
+ TimeScales[typmod];
else
- {
*time = -((((-*time) + TimeOffsets[typmod]) / TimeScales[typmod]) *
- TimeScales[typmod]);
- }
-#else
- *time = rint((double)*time * TimeScales[typmod])
- / TimeScales[typmod];
-#endif
+ TimeScales[typmod]);
}
}
PG_RETURN_INT32(0);
}
+Datum
+time_hash(PG_FUNCTION_ARGS)
+{
+ return hashint8(fcinfo);
+}
+
Datum
time_larger(PG_FUNCTION_ARGS)
{
PG_RETURN_TIMEADT((time1 < time2) ? time1 : time2);
}
-/* overlaps_time() --- implements the SQL92 OVERLAPS operator.
+/* overlaps_time() --- implements the SQL OVERLAPS operator.
*
- * Algorithm is per SQL92 spec. This is much harder than you'd think
+ * Algorithm is per SQL spec. This is much harder than you'd think
* because the spec requires us to deliver a non-null answer in some cases
* where some of the inputs are null.
*/
overlaps_time(PG_FUNCTION_ARGS)
{
/*
- * The arguments are TimeADT, but we leave them as generic Datums to
- * avoid dereferencing nulls (TimeADT is pass-by-reference!)
+ * The arguments are TimeADT, but we leave them as generic Datums to avoid
+ * dereferencing nulls (TimeADT is pass-by-reference!)
*/
Datum ts1 = PG_GETARG_DATUM(0);
Datum te1 = PG_GETARG_DATUM(1);
(DatumGetTimeADT(t1) < DatumGetTimeADT(t2))
/*
- * If both endpoints of interval 1 are null, the result is null
- * (unknown). If just one endpoint is null, take ts1 as the non-null
- * one. Otherwise, take ts1 as the lesser endpoint.
+ * If both endpoints of interval 1 are null, the result is null (unknown).
+ * If just one endpoint is null, take ts1 as the non-null one. Otherwise,
+ * take ts1 as the lesser endpoint.
*/
if (ts1IsNull)
{
if (TIMEADT_GT(ts1, ts2))
{
/*
- * This case is ts1 < te2 OR te1 < te2, which may look redundant
- * but in the presence of nulls it's not quite completely so.
+ * This case is ts1 < te2 OR te1 < te2, which may look redundant but
+ * in the presence of nulls it's not quite completely so.
*/
if (te2IsNull)
PG_RETURN_NULL();
PG_RETURN_NULL();
/*
- * If te1 is not null then we had ts1 <= te1 above, and we just
- * found ts1 >= te2, hence te1 >= te2.
+ * If te1 is not null then we had ts1 <= te1 above, and we just found
+ * ts1 >= te2, hence te1 >= te2.
*/
PG_RETURN_BOOL(false);
}
PG_RETURN_NULL();
/*
- * If te2 is not null then we had ts2 <= te2 above, and we just
- * found ts2 >= te1, hence te2 >= te1.
+ * If te2 is not null then we had ts2 <= te2 above, and we just found
+ * ts2 >= te1, hence te2 >= te1.
*/
PG_RETURN_BOOL(false);
}
{
/*
* For ts1 = ts2 the spec says te1 <> te2 OR te1 = te2, which is a
- * rather silly way of saying "true if both are nonnull, else
- * null".
+ * rather silly way of saying "true if both are nonnull, else null".
*/
if (te1IsNull || te2IsNull)
PG_RETURN_NULL();
Datum
timestamp_time(PG_FUNCTION_ARGS)
{
- Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
+ Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
TimeADT result;
struct pg_tm tt,
*tm = &tt;
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
-#ifdef HAVE_INT64_TIMESTAMP
-
/*
* Could also do this with time = (timestamp / USECS_PER_DAY *
* USECS_PER_DAY) - timestamp;
*/
result = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec) *
- USECS_PER_SEC) + fsec;
-#else
- result = ((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec + fsec;
-#endif
+ USECS_PER_SEC) + fsec;
PG_RETURN_TIMEADT(result);
}
*tm = &tt;
int tz;
fsec_t fsec;
- char *tzn;
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_NULL();
- if (timestamp2tm(timestamp, &tz, tm, &fsec, &tzn, NULL) != 0)
+ if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
-#ifdef HAVE_INT64_TIMESTAMP
-
/*
* Could also do this with time = (timestamp / USECS_PER_DAY *
* USECS_PER_DAY) - timestamp;
*/
result = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec) *
- USECS_PER_SEC) + fsec;
-#else
- result = ((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec + fsec;
-#endif
+ USECS_PER_SEC) + fsec;
PG_RETURN_TIMEADT(result);
}
Datum
datetime_timestamp(PG_FUNCTION_ARGS)
{
- DateADT date = PG_GETARG_DATEADT(0);
+ DateADT date = PG_GETARG_DATEADT(0);
TimeADT time = PG_GETARG_TIMEADT(1);
Timestamp result;
- result = DatumGetTimestamp(DirectFunctionCall1(date_timestamp,
- DateADTGetDatum(date)));
- result += time;
+ result = date2timestamp(date);
+ if (!TIMESTAMP_NOT_FINITE(result))
+ {
+ result += time;
+ if (!IS_VALID_TIMESTAMP(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+ }
PG_RETURN_TIMESTAMP(result);
}
* Convert interval to time data type.
*
* This is defined as producing the fractional-day portion of the interval.
- * Therefore, we can just ignore the months field. It is not real clear
+ * Therefore, we can just ignore the months field. It is not real clear
* what to do with negative intervals, but we choose to subtract the floor,
* so that, say, '-2 hours' becomes '22:00:00'.
*/
{
Interval *span = PG_GETARG_INTERVAL_P(0);
TimeADT result;
-
-#ifdef HAVE_INT64_TIMESTAMP
int64 days;
result = span->time;
days = (-result + USECS_PER_DAY - 1) / USECS_PER_DAY;
result += days * USECS_PER_DAY;
}
-#else
- result = span->time;
- if (result >= (double)SECS_PER_DAY || result < 0)
- result -= floor(result / (double)SECS_PER_DAY) * (double)SECS_PER_DAY;
-#endif
PG_RETURN_TIMEADT(result);
}
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
-#ifdef HAVE_INT64_TIMESTAMP
result = time + span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
result += USECS_PER_DAY;
-#else
- TimeADT time1;
-
- result = time + span->time;
- TMODULO(result, time1, (double)SECS_PER_DAY);
- if (result < 0)
- result += SECS_PER_DAY;
-#endif
PG_RETURN_TIMEADT(result);
}
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
-#ifdef HAVE_INT64_TIMESTAMP
result = time - span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
result += USECS_PER_DAY;
-#else
- TimeADT time1;
-
- result = time - span->time;
- TMODULO(result, time1, (double)SECS_PER_DAY);
- if (result < 0)
- result += SECS_PER_DAY;
-#endif
PG_RETURN_TIMEADT(result);
}
-/* time_text()
- * Convert time to text data type.
- */
-Datum
-time_text(PG_FUNCTION_ARGS)
-{
- /* Input is a Time, but may as well leave it in Datum form */
- Datum time = PG_GETARG_DATUM(0);
- text *result;
- char *str;
- int len;
-
- str = DatumGetCString(DirectFunctionCall1(time_out, time));
-
- len = strlen(str) + VARHDRSZ;
-
- result = palloc(len);
-
- VARATT_SIZEP(result) = len;
- memmove(VARDATA(result), str, (len - VARHDRSZ));
-
- pfree(str);
-
- PG_RETURN_TEXT_P(result);
-}
-
-
-/* text_time()
- * Convert text string to time.
- * Text type is not null terminated, so use temporary string
- * then call the standard input routine.
- */
-Datum
-text_time(PG_FUNCTION_ARGS)
-{
- text *str = PG_GETARG_TEXT_P(0);
- int i;
- char *sp,
- *dp,
- dstr[MAXDATELEN + 1];
-
- if (VARSIZE(str) - VARHDRSZ > MAXDATELEN)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
- errmsg("invalid input syntax for type time: \"%s\"",
- VARDATA(str))));
-
- sp = VARDATA(str);
- dp = dstr;
- for (i = 0; i < (VARSIZE(str) - VARHDRSZ); i++)
- *dp++ = *sp++;
- *dp = '\0';
-
- return DirectFunctionCall3(time_in,
- CStringGetDatum(dstr),
- ObjectIdGetDatum(InvalidOid),
- Int32GetDatum(-1));
-}
-
/* time_part()
* Extract specified field from time type.
*/
Datum
time_part(PG_FUNCTION_ARGS)
{
- text *units = PG_GETARG_TEXT_P(0);
+ text *units = PG_GETARG_TEXT_PP(0);
TimeADT time = PG_GETARG_TIMEADT(1);
float8 result;
int type,
val;
char *lowunits;
- lowunits = downcase_truncate_identifier(VARDATA(units),
- VARSIZE(units) - VARHDRSZ,
+ lowunits = downcase_truncate_identifier(VARDATA_ANY(units),
+ VARSIZE_ANY_EXHDR(units),
false);
type = DecodeUnits(0, lowunits, &val);
switch (val)
{
case DTK_MICROSEC:
-#ifdef HAVE_INT64_TIMESTAMP
- result = tm->tm_sec * USECS_PER_SEC + fsec;
-#else
- result = (tm->tm_sec + fsec) * 1000000;
-#endif
+ result = tm->tm_sec * 1000000.0 + fsec;
break;
case DTK_MILLISEC:
-#ifdef HAVE_INT64_TIMESTAMP
- result = tm->tm_sec * INT64CONST(1000) + fsec / INT64CONST(1000);
-#else
- result = (tm->tm_sec + fsec) * 1000;
-#endif
+ result = tm->tm_sec * 1000.0 + fsec / 1000.0;
break;
case DTK_SECOND:
-#ifdef HAVE_INT64_TIMESTAMP
- result = tm->tm_sec + fsec / USECS_PER_SEC;
-#else
- result = tm->tm_sec + fsec;
-#endif
+ result = tm->tm_sec + fsec / 1000000.0;
break;
case DTK_MINUTE:
case DTK_DECADE:
case DTK_CENTURY:
case DTK_MILLENNIUM:
+ case DTK_ISOYEAR:
default:
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("\"time\" units \"%s\" not recognized",
- DatumGetCString(DirectFunctionCall1(textout,
- PointerGetDatum(units))))));
-
+ lowunits)));
result = 0;
}
}
else if (type == RESERV && val == DTK_EPOCH)
{
-#ifdef HAVE_INT64_TIMESTAMP
result = time / 1000000.0;
-#else
- result = time;
-#endif
}
else
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("\"time\" units \"%s\" not recognized",
- DatumGetCString(DirectFunctionCall1(textout,
- PointerGetDatum(units))))));
+ lowunits)));
result = 0;
}
static int
tm2timetz(struct pg_tm * tm, fsec_t fsec, int tz, TimeTzADT *result)
{
-#ifdef HAVE_INT64_TIMESTAMP
result->time = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec) *
USECS_PER_SEC) + fsec;
-#else
- result->time = ((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec + fsec;
-#endif
result->zone = tz;
return 0;
char buf[MAXDATELEN + 1];
timetz2tm(time, tm, &fsec, &tz);
- EncodeTimeOnly(tm, fsec, &tz, DateStyle, buf);
+ EncodeTimeOnly(tm, fsec, true, tz, DateStyle, buf);
result = pstrdup(buf);
PG_RETURN_CSTRING(result);
timetz_recv(PG_FUNCTION_ARGS)
{
StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
+
#ifdef NOT_USED
Oid typelem = PG_GETARG_OID(1);
#endif
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
-#ifdef HAVE_INT64_TIMESTAMP
result->time = pq_getmsgint64(buf);
-#else
- result->time = pq_getmsgfloat8(buf);
-#endif
+
+ if (result->time < INT64CONST(0) || result->time > USECS_PER_DAY)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time out of range")));
+
result->zone = pq_getmsgint(buf, sizeof(result->zone));
+ /* Check for sane GMT displacement; see notes in datatype/timestamp.h */
+ if (result->zone <= -TZDISP_LIMIT || result->zone >= TZDISP_LIMIT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TIME_ZONE_DISPLACEMENT_VALUE),
+ errmsg("time zone displacement out of range")));
+
AdjustTimeForTypmod(&(result->time), typmod);
PG_RETURN_TIMETZADT_P(result);
StringInfoData buf;
pq_begintypsend(&buf);
-#ifdef HAVE_INT64_TIMESTAMP
pq_sendint64(&buf, time->time);
-#else
- pq_sendfloat8(&buf, time->time);
-#endif
pq_sendint(&buf, time->zone, sizeof(time->zone));
PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
}
+Datum
+timetztypmodin(PG_FUNCTION_ARGS)
+{
+ ArrayType *ta = PG_GETARG_ARRAYTYPE_P(0);
+
+ PG_RETURN_INT32(anytime_typmodin(true, ta));
+}
+
+Datum
+timetztypmodout(PG_FUNCTION_ARGS)
+{
+ int32 typmod = PG_GETARG_INT32(0);
+
+ PG_RETURN_CSTRING(anytime_typmodout(true, typmod));
+}
+
/* timetz2tm()
* Convert TIME WITH TIME ZONE data type to POSIX time structure.
static int
timetz2tm(TimeTzADT *time, struct pg_tm * tm, fsec_t *fsec, int *tzp)
{
-#ifdef HAVE_INT64_TIMESTAMP
- int64 trem = time->time;
+ TimeOffset trem = time->time;
tm->tm_hour = trem / USECS_PER_HOUR;
trem -= tm->tm_hour * USECS_PER_HOUR;
trem -= tm->tm_min * USECS_PER_MINUTE;
tm->tm_sec = trem / USECS_PER_SEC;
*fsec = trem - tm->tm_sec * USECS_PER_SEC;
-#else
- double trem = time->time;
-
- TMODULO(trem, tm->tm_hour, (double)SECS_PER_HOUR);
- TMODULO(trem, tm->tm_min, (double)SECS_PER_MINUTE);
- TMODULO(trem, tm->tm_sec, 1.0);
- *fsec = trem;
-#endif
if (tzp != NULL)
*tzp = time->zone;
static int
timetz_cmp_internal(TimeTzADT *time1, TimeTzADT *time2)
{
- /* Primary sort is by true (GMT-equivalent) time */
-#ifdef HAVE_INT64_TIMESTAMP
- int64 t1,
+ TimeOffset t1,
t2;
+ /* Primary sort is by true (GMT-equivalent) time */
t1 = time1->time + (time1->zone * USECS_PER_SEC);
t2 = time2->time + (time2->zone * USECS_PER_SEC);
-#else
- double t1,
- t2;
-
- t1 = time1->time + time1->zone;
- t2 = time2->time + time2->zone;
-#endif
if (t1 > t2)
return 1;
PG_RETURN_INT32(timetz_cmp_internal(time1, time2));
}
-/*
- * timetz, being an unusual size, needs a specialized hash function.
- */
Datum
timetz_hash(PG_FUNCTION_ARGS)
{
TimeTzADT *key = PG_GETARG_TIMETZADT_P(0);
+ uint32 thash;
/*
- * Specify hash length as sizeof(double) + sizeof(int4), not as
- * sizeof(TimeTzADT), so that any garbage pad bytes in the structure
- * won't be included in the hash!
+ * To avoid any problems with padding bytes in the struct, we figure the
+ * field hashes separately and XOR them.
*/
- return hash_any((unsigned char *) key, sizeof(key->time) + sizeof(key->zone));
+ thash = DatumGetUInt32(DirectFunctionCall1(hashint8,
+ Int64GetDatumFast(key->time)));
+ thash ^= DatumGetUInt32(hash_uint32(key->zone));
+ PG_RETURN_UINT32(thash);
}
Datum
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
-#ifndef HAVE_INT64_TIMESTAMP
- TimeTzADT time1;
-#endif
-
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
-#ifdef HAVE_INT64_TIMESTAMP
result->time = time->time + span->time;
result->time -= result->time / USECS_PER_DAY * USECS_PER_DAY;
if (result->time < INT64CONST(0))
result->time += USECS_PER_DAY;
-#else
- result->time = time->time + span->time;
- TMODULO(result->time, time1.time, (double)SECS_PER_DAY);
- if (result->time < 0)
- result->time += SECS_PER_DAY;
-#endif
result->zone = time->zone;
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
-#ifndef HAVE_INT64_TIMESTAMP
- TimeTzADT time1;
-#endif
-
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
-#ifdef HAVE_INT64_TIMESTAMP
result->time = time->time - span->time;
result->time -= result->time / USECS_PER_DAY * USECS_PER_DAY;
if (result->time < INT64CONST(0))
result->time += USECS_PER_DAY;
-#else
- result->time = time->time - span->time;
- TMODULO(result->time, time1.time, (double)SECS_PER_DAY);
- if (result->time < 0)
- result->time += SECS_PER_DAY;
-#endif
result->zone = time->zone;
PG_RETURN_TIMETZADT_P(result);
}
-/* overlaps_timetz() --- implements the SQL92 OVERLAPS operator.
+/* overlaps_timetz() --- implements the SQL OVERLAPS operator.
*
- * Algorithm is per SQL92 spec. This is much harder than you'd think
+ * Algorithm is per SQL spec. This is much harder than you'd think
* because the spec requires us to deliver a non-null answer in some cases
* where some of the inputs are null.
*/
overlaps_timetz(PG_FUNCTION_ARGS)
{
/*
- * The arguments are TimeTzADT *, but we leave them as generic Datums
- * for convenience of notation --- and to avoid dereferencing nulls.
+ * The arguments are TimeTzADT *, but we leave them as generic Datums for
+ * convenience of notation --- and to avoid dereferencing nulls.
*/
Datum ts1 = PG_GETARG_DATUM(0);
Datum te1 = PG_GETARG_DATUM(1);
DatumGetBool(DirectFunctionCall2(timetz_lt,t1,t2))
/*
- * If both endpoints of interval 1 are null, the result is null
- * (unknown). If just one endpoint is null, take ts1 as the non-null
- * one. Otherwise, take ts1 as the lesser endpoint.
+ * If both endpoints of interval 1 are null, the result is null (unknown).
+ * If just one endpoint is null, take ts1 as the non-null one. Otherwise,
+ * take ts1 as the lesser endpoint.
*/
if (ts1IsNull)
{
if (TIMETZ_GT(ts1, ts2))
{
/*
- * This case is ts1 < te2 OR te1 < te2, which may look redundant
- * but in the presence of nulls it's not quite completely so.
+ * This case is ts1 < te2 OR te1 < te2, which may look redundant but
+ * in the presence of nulls it's not quite completely so.
*/
if (te2IsNull)
PG_RETURN_NULL();
PG_RETURN_NULL();
/*
- * If te1 is not null then we had ts1 <= te1 above, and we just
- * found ts1 >= te2, hence te1 >= te2.
+ * If te1 is not null then we had ts1 <= te1 above, and we just found
+ * ts1 >= te2, hence te1 >= te2.
*/
PG_RETURN_BOOL(false);
}
PG_RETURN_NULL();
/*
- * If te2 is not null then we had ts2 <= te2 above, and we just
- * found ts2 >= te1, hence te2 >= te1.
+ * If te2 is not null then we had ts2 <= te2 above, and we just found
+ * ts2 >= te1, hence te2 >= te1.
*/
PG_RETURN_BOOL(false);
}
{
/*
* For ts1 = ts2 the spec says te1 <> te2 OR te1 = te2, which is a
- * rather silly way of saying "true if both are nonnull, else
- * null".
+ * rather silly way of saying "true if both are nonnull, else null".
*/
if (te1IsNull || te2IsNull)
PG_RETURN_NULL();
GetCurrentDateTime(tm);
time2tm(time, tm, &fsec);
- tz = DetermineLocalTimeZone(tm);
+ tz = DetermineTimeZoneOffset(tm, session_timezone);
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
*tm = &tt;
int tz;
fsec_t fsec;
- char *tzn;
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_NULL();
- if (timestamp2tm(timestamp, &tz, tm, &fsec, &tzn, NULL) != 0)
+ if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
Datum
datetimetz_timestamptz(PG_FUNCTION_ARGS)
{
- DateADT date = PG_GETARG_DATEADT(0);
+ DateADT date = PG_GETARG_DATEADT(0);
TimeTzADT *time = PG_GETARG_TIMETZADT_P(1);
TimestampTz result;
-#ifdef HAVE_INT64_TIMESTAMP
- result = (date * USECS_PER_DAY + time->time) + time->zone * USECS_PER_SEC;
-#else
- result = date * (double)SECS_PER_DAY + time->time + time->zone;
-#endif
-
- PG_RETURN_TIMESTAMP(result);
-}
-
-
-/* timetz_text()
- * Convert timetz to text data type.
- */
-Datum
-timetz_text(PG_FUNCTION_ARGS)
-{
- /* Input is a Timetz, but may as well leave it in Datum form */
- Datum timetz = PG_GETARG_DATUM(0);
- text *result;
- char *str;
- int len;
-
- str = DatumGetCString(DirectFunctionCall1(timetz_out, timetz));
-
- len = strlen(str) + VARHDRSZ;
-
- result = palloc(len);
-
- VARATT_SIZEP(result) = len;
- memmove(VARDATA(result), str, (len - VARHDRSZ));
+ if (DATE_IS_NOBEGIN(date))
+ TIMESTAMP_NOBEGIN(result);
+ else if (DATE_IS_NOEND(date))
+ TIMESTAMP_NOEND(result);
+ else
+ {
+ /*
+ * Date's range is wider than timestamp's, so check for boundaries.
+ * Since dates have the same minimum values as timestamps, only upper
+ * boundary need be checked for overflow.
+ */
+ if (date >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range for timestamp")));
+ result = date * USECS_PER_DAY + time->time + time->zone * USECS_PER_SEC;
- pfree(str);
+ /*
+ * Since it is possible to go beyond allowed timestamptz range because
+ * of time zone, check for allowed timestamp range after adding tz.
+ */
+ if (!IS_VALID_TIMESTAMP(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range for timestamp")));
+ }
- PG_RETURN_TEXT_P(result);
+ PG_RETURN_TIMESTAMP(result);
}
-/* text_timetz()
- * Convert text string to timetz.
- * Text type is not null terminated, so use temporary string
- * then call the standard input routine.
- */
-Datum
-text_timetz(PG_FUNCTION_ARGS)
-{
- text *str = PG_GETARG_TEXT_P(0);
- int i;
- char *sp,
- *dp,
- dstr[MAXDATELEN + 1];
-
- if (VARSIZE(str) - VARHDRSZ > MAXDATELEN)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
- errmsg("invalid input syntax for type time with time zone: \"%s\"",
- VARDATA(str))));
-
- sp = VARDATA(str);
- dp = dstr;
- for (i = 0; i < (VARSIZE(str) - VARHDRSZ); i++)
- *dp++ = *sp++;
- *dp = '\0';
-
- return DirectFunctionCall3(timetz_in,
- CStringGetDatum(dstr),
- ObjectIdGetDatum(InvalidOid),
- Int32GetDatum(-1));
-}
-
/* timetz_part()
* Extract specified field from time type.
*/
Datum
timetz_part(PG_FUNCTION_ARGS)
{
- text *units = PG_GETARG_TEXT_P(0);
+ text *units = PG_GETARG_TEXT_PP(0);
TimeTzADT *time = PG_GETARG_TIMETZADT_P(1);
float8 result;
int type,
val;
char *lowunits;
- lowunits = downcase_truncate_identifier(VARDATA(units),
- VARSIZE(units) - VARHDRSZ,
+ lowunits = downcase_truncate_identifier(VARDATA_ANY(units),
+ VARSIZE_ANY_EXHDR(units),
false);
type = DecodeUnits(0, lowunits, &val);
case DTK_TZ_MINUTE:
result = -tz;
result /= SECS_PER_MINUTE;
- FMODULO(result, dummy, (double)SECS_PER_MINUTE);
+ FMODULO(result, dummy, (double) SECS_PER_MINUTE);
break;
case DTK_TZ_HOUR:
dummy = -tz;
- FMODULO(dummy, result, (double)SECS_PER_HOUR);
+ FMODULO(dummy, result, (double) SECS_PER_HOUR);
break;
case DTK_MICROSEC:
-#ifdef HAVE_INT64_TIMESTAMP
- result = tm->tm_sec * USECS_PER_SEC + fsec;
-#else
- result = (tm->tm_sec + fsec) * 1000000;
-#endif
+ result = tm->tm_sec * 1000000.0 + fsec;
break;
case DTK_MILLISEC:
-#ifdef HAVE_INT64_TIMESTAMP
- result = tm->tm_sec * INT64CONST(1000) + fsec / INT64CONST(1000);
-#else
- result = (tm->tm_sec + fsec) * 1000;
-#endif
+ result = tm->tm_sec * 1000.0 + fsec / 1000.0;
break;
case DTK_SECOND:
-#ifdef HAVE_INT64_TIMESTAMP
- result = tm->tm_sec + fsec / USECS_PER_SEC;
-#else
- result = tm->tm_sec + fsec;
-#endif
+ result = tm->tm_sec + fsec / 1000000.0;
break;
case DTK_MINUTE:
default:
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("\"time with time zone\" units \"%s\" not recognized",
- DatumGetCString(DirectFunctionCall1(textout,
- PointerGetDatum(units))))));
-
+ errmsg("\"time with time zone\" units \"%s\" not recognized",
+ lowunits)));
result = 0;
}
}
else if (type == RESERV && val == DTK_EPOCH)
{
-#ifdef HAVE_INT64_TIMESTAMP
result = time->time / 1000000.0 + time->zone;
-#else
- result = time->time + time->zone;
-#endif
}
else
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("\"time with time zone\" units \"%s\" not recognized",
- DatumGetCString(DirectFunctionCall1(textout,
- PointerGetDatum(units))))));
-
+ errmsg("\"time with time zone\" units \"%s\" not recognized",
+ lowunits)));
result = 0;
}
Datum
timetz_zone(PG_FUNCTION_ARGS)
{
- text *zone = PG_GETARG_TEXT_P(0);
+ text *zone = PG_GETARG_TEXT_PP(0);
TimeTzADT *t = PG_GETARG_TIMETZADT_P(1);
TimeTzADT *result;
int tz;
- char tzname[TZ_STRLEN_MAX];
- int len;
+ char tzname[TZ_STRLEN_MAX + 1];
+ char *lowzone;
+ int type,
+ val;
pg_tz *tzp;
- struct pg_tm *tm;
- pg_time_t now;
-
- /* Find the specified timezone */
- len = (VARSIZE(zone)-VARHDRSZ>TZ_STRLEN_MAX) ?
- TZ_STRLEN_MAX : VARSIZE(zone) - VARHDRSZ;
- memcpy(tzname,VARDATA(zone),len);
- tzname[len]=0;
- tzp = pg_tzset(tzname);
- if (!tzp) {
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("time zone \"%s\" not recognized", tzname)));
- PG_RETURN_NULL();
+
+ /*
+ * Look up the requested timezone. First we look in the timezone
+ * abbreviation table (to handle cases like "EST"), and if that fails, we
+ * look in the timezone database (to handle cases like
+ * "America/New_York"). (This matches the order in which timestamp input
+ * checks the cases; it's important because the timezone database unwisely
+ * uses a few zone names that are identical to offset abbreviations.)
+ */
+ text_to_cstring_buffer(zone, tzname, sizeof(tzname));
+
+ /* DecodeTimezoneAbbrev requires lowercase input */
+ lowzone = downcase_truncate_identifier(tzname,
+ strlen(tzname),
+ false);
+
+ type = DecodeTimezoneAbbrev(0, lowzone, &val, &tzp);
+
+ if (type == TZ || type == DTZ)
+ {
+ /* fixed-offset abbreviation */
+ tz = -val;
}
+ else if (type == DYNTZ)
+ {
+ /* dynamic-offset abbreviation, resolve using current time */
+ pg_time_t now = (pg_time_t) time(NULL);
+ struct pg_tm *tm;
- /* Get the offset-from-GMT that is valid today for the selected zone */
- if ((now = time(NULL)) < 0 ||
- (tm = pg_localtime(&now, tzp)) == NULL) {
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("could not determine current time")));
- PG_RETURN_NULL();
+ tm = pg_localtime(&now, tzp);
+ tz = DetermineTimeZoneAbbrevOffset(tm, tzname, tzp);
+ }
+ else
+ {
+ /* try it as a full zone name */
+ tzp = pg_tzset(tzname);
+ if (tzp)
+ {
+ /* Get the offset-from-GMT that is valid today for the zone */
+ pg_time_t now = (pg_time_t) time(NULL);
+ struct pg_tm *tm;
+
+ tm = pg_localtime(&now, tzp);
+ tz = -tm->tm_gmtoff;
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("time zone \"%s\" not recognized", tzname)));
+ tz = 0; /* keep compiler quiet */
+ }
}
- result = (TimeTzADT *)palloc(sizeof(TimeTzADT));
-
- tz = -tm->tm_gmtoff;
-#ifdef HAVE_INT64_TIMESTAMP
+ result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
+
result->time = t->time + (t->zone - tz) * USECS_PER_SEC;
while (result->time < INT64CONST(0))
result->time += USECS_PER_DAY;
while (result->time >= USECS_PER_DAY)
result->time -= USECS_PER_DAY;
-#else
- result->time = t->time + (t->zone - tz);
- while (result->time < 0)
- result->time += SECS_PER_DAY;
- while (result->time >= SECS_PER_DAY)
- result->time -= SECS_PER_DAY;
-#endif
result->zone = tz;
PG_RETURN_TIMETZADT_P(result);
-} /* timetz_zone() */
+}
/* timetz_izone()
* Encode time with time zone type with specified time interval as time zone.
TimeTzADT *result;
int tz;
- if (zone->month != 0)
+ if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("\"interval\" time zone \"%s\" not valid",
- DatumGetCString(DirectFunctionCall1(interval_out,
- PointerGetDatum(zone))))));
+ errmsg("interval time zone \"%s\" must not include months or days",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
-#ifdef HAVE_INT64_TIMESTAMP
tz = -(zone->time / USECS_PER_SEC);
-#else
- tz = -(zone->time);
-#endif
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
-#ifdef HAVE_INT64_TIMESTAMP
result->time = time->time + (time->zone - tz) * USECS_PER_SEC;
while (result->time < INT64CONST(0))
result->time += USECS_PER_DAY;
while (result->time >= USECS_PER_DAY)
result->time -= USECS_PER_DAY;
-#else
- result->time = time->time + (time->zone - tz);
- while (result->time < 0)
- result->time += SECS_PER_DAY;
- while (result->time >= SECS_PER_DAY)
- result->time -= SECS_PER_DAY;
-#endif
result->zone = tz;
PG_RETURN_TIMETZADT_P(result);
-} /* timetz_izone() */
+}