]> granicus.if.org Git - postgresql/blobdiff - src/backend/utils/adt/date.c
Update copyright for the year 2010.
[postgresql] / src / backend / utils / adt / date.c
index 619a099b654d09e4ec3355a81a16826f72cda2ba..c722e47e352e18ae2bb378b37b122c91461360ed 100644 (file)
@@ -3,12 +3,12 @@
  * date.c
  *       implements DATE and TIME data types specified in SQL-92 standard
  *
- * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2010, 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.122 2005/10/15 02:49:28 momjian Exp $
+ *       $PostgreSQL: pgsql/src/backend/utils/adt/date.c,v 1.150 2010/01/02 16:57:53 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
 #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/nabstime.h"
-#include "utils/timestamp.h"
 
 /*
  * gcc's -ffast-math switch breaks routines that expect exact results from
 #endif
 
 
+static void EncodeSpecialDate(DateADT dt, char *str);
 static int     time2tm(TimeADT time, struct pg_tm * tm, fsec_t *fsec);
 static int     timetz2tm(TimeTzADT *time, struct pg_tm * tm, fsec_t *fsec, int *tzp);
 static int     tm2time(struct pg_tm * tm, fsec_t fsec, TimeADT *result);
 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           typmod;
+       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")));
+
+       if (*tl < 0)
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                errmsg("TIME(%d)%s precision must not be negative",
+                                               *tl, (istz ? " WITH TIME ZONE" : ""))));
+       if (*tl > MAX_TIME_PRECISION)
+       {
+               ereport(WARNING,
+                               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                errmsg("TIME(%d)%s precision reduced to maximum allowed, %d",
+                                               *tl, (istz ? " WITH TIME ZONE" : ""),
+                                               MAX_TIME_PRECISION)));
+               typmod = MAX_TIME_PRECISION;
+       }
+       else
+               typmod = *tl;
+
+       return typmod;
+}
+
+/* common code for timetypmodout and timetztypmodout */
+static char *
+anytime_typmodout(bool istz, int32 typmod)
+{
+       char       *res = (char *) palloc(64);
+       const char *tz = istz ? " with time zone" : " without time zone";
+
+       if (typmod >= 0)
+               snprintf(res, 64, "(%d)%s", (int) typmod, tz);
+       else
+               snprintf(res, 64, "%s", tz);
+       return res;
+}
+
+
 /*****************************************************************************
  *      Date ADT
  *****************************************************************************/
@@ -92,11 +148,24 @@ date_in(PG_FUNCTION_ARGS)
                        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;
        }
 
+       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;
 
        PG_RETURN_DATEADT(date);
@@ -114,10 +183,14 @@ date_out(PG_FUNCTION_ARGS)
                           *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);
@@ -130,8 +203,18 @@ Datum
 date_recv(PG_FUNCTION_ARGS)
 {
        StringInfo      buf = (StringInfo) PG_GETARG_POINTER(0);
+       DateADT result;
+
+       result = (DateADT) pq_getmsgint(buf, sizeof(DateADT));
 
-       PG_RETURN_DATEADT((DateADT) pq_getmsgint(buf, sizeof(DateADT)));
+       /* Limit to the same range that date_in() accepts. */
+       if (result < -POSTGRES_EPOCH_JDATE ||
+               result >= JULIAN_MAX - POSTGRES_EPOCH_JDATE)
+               ereport(ERROR,
+                               (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                errmsg("date out of range")));
+
+       PG_RETURN_DATEADT(result);
 }
 
 /*
@@ -148,6 +231,20 @@ date_send(PG_FUNCTION_ARGS)
        PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
 }
 
+/*
+ * Convert reserved date values to string.
+ */
+static 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");
+}
+
 
 /*
  * Comparison functions for dates
@@ -220,6 +317,14 @@ date_cmp(PG_FUNCTION_ARGS)
        PG_RETURN_INT32(0);
 }
 
+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)
 {
@@ -246,6 +351,11 @@ date_mi(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));
 }
 
@@ -258,6 +368,9 @@ date_pli(PG_FUNCTION_ARGS)
        DateADT         dateVal = PG_GETARG_DATEADT(0);
        int32           days = PG_GETARG_INT32(1);
 
+       if (DATE_NOT_FINITE(dateVal))
+               days = 0;                               /* can't change infinity */
+
        PG_RETURN_DATEADT(dateVal + days);
 }
 
@@ -269,6 +382,9 @@ date_mii(PG_FUNCTION_ARGS)
        DateADT         dateVal = PG_GETARG_DATEADT(0);
        int32           days = PG_GETARG_INT32(1);
 
+       if (DATE_NOT_FINITE(dateVal))
+               days = 0;                               /* can't change infinity */
+
        PG_RETURN_DATEADT(dateVal - days);
 }
 
@@ -277,15 +393,33 @@ date_mii(PG_FUNCTION_ARGS)
  * time zone
  */
 
+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
+       {
 #ifdef HAVE_INT64_TIMESTAMP
-/* date is days since 2000, timestamp is microseconds since same... */
-#define date2timestamp(dateVal) \
-       ((Timestamp) ((dateVal) * USECS_PER_DAY))
+               /* date is days since 2000, timestamp is microseconds since same... */
+               result = dateVal * USECS_PER_DAY;
+               /* Date's range is wider than timestamp's, so check for overflow */
+               if (result / USECS_PER_DAY != dateVal)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                        errmsg("date out of range for timestamp")));
 #else
-/* date is days since 2000, timestamp is seconds since same... */
-#define date2timestamp(dateVal) \
-       ((Timestamp) ((dateVal) * (double)SECS_PER_DAY))
+               /* date is days since 2000, timestamp is seconds since same... */
+               result = dateVal * (double) SECS_PER_DAY;
 #endif
+       }
+
+       return result;
+}
 
 static TimestampTz
 date2timestamptz(DateADT dateVal)
@@ -295,19 +429,30 @@ date2timestamptz(DateADT dateVal)
                           *tm = &tt;
        int                     tz;
 
-       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, global_timezone);
+       if (DATE_IS_NOBEGIN(dateVal))
+               TIMESTAMP_NOBEGIN(result);
+       else if (DATE_IS_NOEND(dateVal))
+               TIMESTAMP_NOEND(result);
+       else
+       {
+               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;
+               result = dateVal * USECS_PER_DAY + tz * USECS_PER_SEC;
+               /* Date's range is wider than timestamp's, so check for overflow */
+               if ((result - tz * USECS_PER_SEC) / USECS_PER_DAY != dateVal)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                        errmsg("date out of range for timestamp")));
 #else
-       result = dateVal * (double) SECS_PER_DAY + tz;
+               result = dateVal * (double) SECS_PER_DAY + tz;
 #endif
+       }
 
        return result;
 }
@@ -721,15 +866,19 @@ timestamp_date(PG_FUNCTION_ARGS)
                           *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);
 }
@@ -764,15 +913,19 @@ timestamptz_date(PG_FUNCTION_ARGS)
        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, &tzn, 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);
 }
@@ -793,16 +946,19 @@ abstime_date(PG_FUNCTION_ARGS)
        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")));
+                       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);
@@ -814,64 +970,6 @@ abstime_date(PG_FUNCTION_ARGS)
 }
 
 
-/* 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
  *****************************************************************************/
@@ -927,8 +1025,10 @@ tm2time(struct pg_tm * tm, fsec_t fsec, TimeADT *result)
 
 /* 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)
@@ -998,8 +1098,18 @@ time_recv(PG_FUNCTION_ARGS)
 
 #ifdef HAVE_INT64_TIMESTAMP
        result = pq_getmsgint64(buf);
+
+       if (result < INT64CONST(0) || result > USECS_PER_DAY)
+               ereport(ERROR,
+                               (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                errmsg("time out of range")));
 #else
        result = pq_getmsgfloat8(buf);
+
+       if (result < 0 || result > (double) SECS_PER_DAY)
+               ereport(ERROR,
+                               (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                errmsg("time out of range")));
 #endif
 
        AdjustTimeForTypmod(&result, typmod);
@@ -1025,6 +1135,22 @@ time_send(PG_FUNCTION_ARGS)
        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));
+}
+
 
 /* time_scale()
  * Adjust time type for specified scale factor.
@@ -1180,6 +1306,17 @@ time_cmp(PG_FUNCTION_ARGS)
        PG_RETURN_INT32(0);
 }
 
+Datum
+time_hash(PG_FUNCTION_ARGS)
+{
+       /* We can use either hashint8 or hashfloat8 directly */
+#ifdef HAVE_INT64_TIMESTAMP
+       return hashint8(fcinfo);
+#else
+       return hashfloat8(fcinfo);
+#endif
+}
+
 Datum
 time_larger(PG_FUNCTION_ARGS)
 {
@@ -1405,9 +1542,9 @@ datetime_timestamp(PG_FUNCTION_ARGS)
        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;
 
        PG_RETURN_TIMESTAMP(result);
 }
@@ -1541,80 +1678,21 @@ time_mi_interval(PG_FUNCTION_ARGS)
 }
 
 
-/* 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);
@@ -1633,7 +1711,7 @@ time_part(PG_FUNCTION_ARGS)
                {
                        case DTK_MICROSEC:
 #ifdef HAVE_INT64_TIMESTAMP
-                               result = tm->tm_sec * USECS_PER_SEC + fsec;
+                               result = tm->tm_sec * 1000000.0 + fsec;
 #else
                                result = (tm->tm_sec + fsec) * 1000000;
 #endif
@@ -1641,7 +1719,7 @@ time_part(PG_FUNCTION_ARGS)
 
                        case DTK_MILLISEC:
 #ifdef HAVE_INT64_TIMESTAMP
-                               result = tm->tm_sec * INT64CONST(1000) + fsec / INT64CONST(1000);
+                               result = tm->tm_sec * 1000.0 + fsec / 1000.0;
 #else
                                result = (tm->tm_sec + fsec) * 1000;
 #endif
@@ -1649,7 +1727,7 @@ time_part(PG_FUNCTION_ARGS)
 
                        case DTK_SECOND:
 #ifdef HAVE_INT64_TIMESTAMP
-                               result = tm->tm_sec + fsec / USECS_PER_SEC;
+                               result = tm->tm_sec + fsec / 1000000.0;
 #else
                                result = tm->tm_sec + fsec;
 #endif
@@ -1673,13 +1751,12 @@ time_part(PG_FUNCTION_ARGS)
                        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;
                }
        }
@@ -1696,8 +1773,7 @@ time_part(PG_FUNCTION_ARGS)
                ereport(ERROR,
                                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                                 errmsg("\"time\" units \"%s\" not recognized",
-                                               DatumGetCString(DirectFunctionCall1(textout,
-                                                                                                PointerGetDatum(units))))));
+                                               lowunits)));
                result = 0;
        }
 
@@ -1797,11 +1873,29 @@ timetz_recv(PG_FUNCTION_ARGS)
 
 #ifdef HAVE_INT64_TIMESTAMP
        result->time = pq_getmsgint64(buf);
+
+       if (result->time < INT64CONST(0) || result->time > USECS_PER_DAY)
+               ereport(ERROR,
+                               (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                errmsg("time out of range")));
 #else
        result->time = pq_getmsgfloat8(buf);
+
+       if (result->time < 0 || result->time > (double) SECS_PER_DAY)
+               ereport(ERROR,
+                               (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                errmsg("time out of range")));
 #endif
+
        result->zone = pq_getmsgint(buf, sizeof(result->zone));
 
+       /* we allow GMT displacements up to 14:59:59, cf DecodeTimezone() */
+       if (result->zone <= -15 * SECS_PER_HOUR ||
+               result->zone >= 15 * SECS_PER_HOUR)
+               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);
@@ -1826,6 +1920,22 @@ timetz_send(PG_FUNCTION_ARGS)
        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.
@@ -1833,9 +1943,9 @@ timetz_send(PG_FUNCTION_ARGS)
 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;
 
+#ifdef HAVE_INT64_TIMESTAMP
        tm->tm_hour = trem / USECS_PER_HOUR;
        trem -= tm->tm_hour * USECS_PER_HOUR;
        tm->tm_min = trem / USECS_PER_MINUTE;
@@ -1843,8 +1953,6 @@ timetz2tm(TimeTzADT *time, struct pg_tm * tm, fsec_t *fsec, int *tzp)
        tm->tm_sec = trem / USECS_PER_SEC;
        *fsec = trem - tm->tm_sec * USECS_PER_SEC;
 #else
-       double          trem = time->time;
-
 recalc:
        TMODULO(trem, tm->tm_hour, (double) SECS_PER_HOUR);
        TMODULO(trem, tm->tm_min, (double) SECS_PER_MINUTE);
@@ -1890,17 +1998,14 @@ timetz_scale(PG_FUNCTION_ARGS)
 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 */
+#ifdef HAVE_INT64_TIMESTAMP
        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
@@ -1985,20 +2090,27 @@ timetz_cmp(PG_FUNCTION_ARGS)
        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.  This also provides a convenient
+        * framework for dealing with the fact that the time field might be either
+        * double or int64.
         */
-       return hash_any((unsigned char *) key, sizeof(key->time) + sizeof(key->zone));
+#ifdef HAVE_INT64_TIMESTAMP
+       thash = DatumGetUInt32(DirectFunctionCall1(hashint8,
+                                                                                          Int64GetDatumFast(key->time)));
+#else
+       thash = DatumGetUInt32(DirectFunctionCall1(hashfloat8,
+                                                                                        Float8GetDatumFast(key->time)));
+#endif
+       thash ^= DatumGetUInt32(hash_uint32(key->zone));
+       PG_RETURN_UINT32(thash);
 }
 
 Datum
@@ -2246,7 +2358,7 @@ time_timetz(PG_FUNCTION_ARGS)
 
        GetCurrentDateTime(tm);
        time2tm(time, tm, &fsec);
-       tz = DetermineTimeZoneOffset(tm, global_timezone);
+       tz = DetermineTimeZoneOffset(tm, session_timezone);
 
        result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
 
@@ -2300,90 +2412,38 @@ datetimetz_timestamptz(PG_FUNCTION_ARGS)
        TimeTzADT  *time = PG_GETARG_TIMETZADT_P(1);
        TimestampTz result;
 
+       if (DATE_IS_NOBEGIN(date))
+               TIMESTAMP_NOBEGIN(result);
+       else if (DATE_IS_NOEND(date))
+               TIMESTAMP_NOEND(result);
+       else
+       {
 #ifdef HAVE_INT64_TIMESTAMP
-       result = date * USECS_PER_DAY + time->time + time->zone * USECS_PER_SEC;
+               result = date * USECS_PER_DAY + time->time + time->zone * USECS_PER_SEC;
 #else
-       result = date * (double) SECS_PER_DAY + time->time + time->zone;
+               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));
-
-       pfree(str);
-
-       PG_RETURN_TEXT_P(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);
@@ -2419,7 +2479,7 @@ timetz_part(PG_FUNCTION_ARGS)
 
                        case DTK_MICROSEC:
 #ifdef HAVE_INT64_TIMESTAMP
-                               result = tm->tm_sec * USECS_PER_SEC + fsec;
+                               result = tm->tm_sec * 1000000.0 + fsec;
 #else
                                result = (tm->tm_sec + fsec) * 1000000;
 #endif
@@ -2427,7 +2487,7 @@ timetz_part(PG_FUNCTION_ARGS)
 
                        case DTK_MILLISEC:
 #ifdef HAVE_INT64_TIMESTAMP
-                               result = tm->tm_sec * INT64CONST(1000) + fsec / INT64CONST(1000);
+                               result = tm->tm_sec * 1000.0 + fsec / 1000.0;
 #else
                                result = (tm->tm_sec + fsec) * 1000;
 #endif
@@ -2435,7 +2495,7 @@ timetz_part(PG_FUNCTION_ARGS)
 
                        case DTK_SECOND:
 #ifdef HAVE_INT64_TIMESTAMP
-                               result = tm->tm_sec + fsec / USECS_PER_SEC;
+                               result = tm->tm_sec + fsec / 1000000.0;
 #else
                                result = tm->tm_sec + fsec;
 #endif
@@ -2460,9 +2520,7 @@ timetz_part(PG_FUNCTION_ARGS)
                                ereport(ERROR,
                                                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                                errmsg("\"time with time zone\" units \"%s\" not recognized",
-                                          DatumGetCString(DirectFunctionCall1(textout,
-                                                                                                PointerGetDatum(units))))));
-
+                                          lowunits)));
                                result = 0;
                }
        }
@@ -2479,9 +2537,7 @@ timetz_part(PG_FUNCTION_ARGS)
                ereport(ERROR,
                                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                                 errmsg("\"time with time zone\" units \"%s\" not recognized",
-                                               DatumGetCString(DirectFunctionCall1(textout,
-                                                                                                PointerGetDatum(units))))));
-
+                                               lowunits)));
                result = 0;
        }
 
@@ -2495,46 +2551,45 @@ timetz_part(PG_FUNCTION_ARGS)
 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 + 1];
-       int                     len;
+       char       *lowzone;
+       int                     type,
+                               val;
        pg_tz      *tzp;
 
        /*
-        * Look up the requested timezone.      First we look in the timezone database
-        * (to handle cases like "America/New_York"), and if that fails, we look
-        * in the date token table (to handle cases like "EST").
+        * Look up the requested timezone.      First we look in the date token 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.)
         */
-       len = Min(VARSIZE(zone) - VARHDRSZ, TZ_STRLEN_MAX);
-       memcpy(tzname, VARDATA(zone), len);
-       tzname[len] = '\0';
-       tzp = pg_tzset(tzname);
-       if (tzp)
-       {
-               /* Get the offset-from-GMT that is valid today for the selected zone */
-               pg_time_t       now;
-               struct pg_tm *tm;
+       text_to_cstring_buffer(zone, tzname, sizeof(tzname));
+       lowzone = downcase_truncate_identifier(tzname,
+                                                                                  strlen(tzname),
+                                                                                  false);
 
-               now = time(NULL);
-               tm = pg_localtime(&now, tzp);
-               tz = -tm->tm_gmtoff;
-       }
+       type = DecodeSpecial(0, lowzone, &val);
+
+       if (type == TZ || type == DTZ)
+               tz = val * 60;
        else
        {
-               char       *lowzone;
-               int                     type,
-                                       val;
-
-               lowzone = downcase_truncate_identifier(VARDATA(zone),
-                                                                                          VARSIZE(zone) - VARHDRSZ,
-                                                                                          false);
-               type = DecodeSpecial(0, lowzone, &val);
+               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;
 
-               if (type == TZ || type == DTZ)
-                       tz = val * 60;
+                       tm = pg_localtime(&now, tzp);
+                       tz = -tm->tm_gmtoff;
+               }
                else
                {
                        ereport(ERROR,