Constructors for interval, timestamp, timestamptz
authorAlvaro Herrera <alvherre@alvh.no-ip.org>
Tue, 4 Mar 2014 18:09:43 +0000 (15:09 -0300)
committerAlvaro Herrera <alvherre@alvh.no-ip.org>
Tue, 4 Mar 2014 18:09:43 +0000 (15:09 -0300)
Author: Pavel Stěhule, editorialized somewhat by Álvaro Herrera
Reviewed-by: Tomáš Vondra, Marko Tiikkaja
With input from Fabrízio de Royes Mello, Jim Nasby

14 files changed:
doc/src/sgml/func.sgml
src/backend/catalog/system_views.sql
src/backend/utils/adt/datetime.c
src/backend/utils/adt/timestamp.c
src/include/catalog/catversion.h
src/include/catalog/pg_proc.h
src/include/utils/datetime.h
src/include/utils/timestamp.h
src/test/regress/expected/interval.out
src/test/regress/expected/timestamp.out
src/test/regress/expected/timestamptz.out
src/test/regress/sql/interval.sql
src/test/regress/sql/timestamp.sql
src/test/regress/sql/timestamptz.sql

index d344b3a894a7e72cd94ebf3b5cd8b6b51685ff8f..a1f627cbd6cf9d5bbb71abea93380b3fd79bf912 100644 (file)
@@ -6720,6 +6720,32 @@ SELECT SUBSTRING('XY1234Z', 'Y*?([0-9]{1,3})');
         <entry><literal>2013-07-15</literal></entry>
        </row>
 
+       <row>
+        <entry>
+         <indexterm>
+          <primary>make_interval</primary>
+         </indexterm>
+         <literal>
+          <function>
+           make_interval(<parameter>years</parameter> <type>int</type> DEFAULT 0,
+           <parameter>months</parameter> <type>int</type> DEFAULT 0,
+           <parameter>weeks</parameter> <type>int</type> DEFAULT 0,
+           <parameter>days</parameter> <type>int</type> DEFAULT 0,
+           <parameter>hours</parameter> <type>int</type> DEFAULT 0,
+           <parameter>mins</parameter> <type>int</type> DEFAULT 0,
+           <parameter>secs</parameter> <type>double precision</type> DEFAULT 0.0)
+          </function>
+         </literal>
+        </entry>
+        <entry><type>interval</type></entry>
+        <entry>
+         Create interval from years, months, weeks, days, hours, minutes and
+         seconds fields
+        </entry>
+        <entry><literal>make_interval(days := 10)</literal></entry>
+        <entry><literal>10 days</literal></entry>
+       </row>
+
        <row>
         <entry>
          <indexterm>
@@ -6741,6 +6767,57 @@ SELECT SUBSTRING('XY1234Z', 'Y*?([0-9]{1,3})');
         <entry><literal>08:15:23.5</literal></entry>
        </row>
 
+       <row>
+        <entry>
+         <indexterm>
+          <primary>make_timestamp</primary>
+         </indexterm>
+         <literal>
+          <function>
+           make_timestamp(<parameter>year</parameter> <type>int</type>,
+           <parameter>month</parameter> <type>int</type>,
+           <parameter>day</parameter> <type>int</type>,
+           <parameter>hour</parameter> <type>int</type>,
+           <parameter>min</parameter> <type>int</type>,
+           <parameter>sec</parameter> <type>double precision</type>)
+          </function>
+         </literal>
+        </entry>
+        <entry><type>timestamp</type></entry>
+        <entry>
+         Create timestamp from year, month, day, hour, minute and seconds fields
+        </entry>
+        <entry><literal>make_timestamp(1-23, 7, 15, 8, 15, 23.5)</literal></entry>
+        <entry><literal>2013-07-15 08:15:23.5</literal></entry>
+       </row>
+
+       <row>
+        <entry>
+         <indexterm>
+          <primary>make_timestamptz</primary>
+         </indexterm>
+         <literal>
+          <function>
+           make_timestamptz(<parameter>year</parameter> <type>int</type>,
+           <parameter>month</parameter> <type>int</type>,
+           <parameter>day</parameter> <type>int</type>,
+           <parameter>hour</parameter> <type>int</type>,
+           <parameter>min</parameter> <type>int</type>,
+           <parameter>sec</parameter> <type>double precision</type>,
+           <optional> <parameter>timezone</parameter> <type>text</type> </optional>)
+          </function>
+         </literal>
+        </entry>
+        <entry><type>timestamp with time zone</type></entry>
+        <entry>
+         Create timestamp with time zone from year, month, day, hour, minute
+         and seconds fields. When <parameter>timezone</parameter> is not specified,
+         then current time zone is used.
+        </entry>
+        <entry><literal>make_timestamp(1-23, 7, 15, 8, 15, 23.5)</literal></entry>
+        <entry><literal>2013-07-15 08:15:23.5+01</literal></entry>
+       </row>
+
        <row>
         <entry>
          <indexterm>
index 0500a73e1ba2a70db284e1de1d7be8aa6fa20a67..053d7585ca45fa128cd78cc8f5866b0086c86969 100644 (file)
@@ -856,3 +856,12 @@ RETURNS SETOF RECORD
 LANGUAGE INTERNAL
 VOLATILE ROWS 1000 COST 1000
 AS 'pg_logical_slot_peek_binary_changes';
+
+CREATE OR REPLACE FUNCTION
+  make_interval(years int4 DEFAULT 0, months int4 DEFAULT 0, weeks int4 DEFAULT 0,
+                days int4 DEFAULT 0, hours int4 DEFAULT 0, mins int4 DEFAULT 0,
+                secs double precision DEFAULT 0.0)
+RETURNS interval
+LANGUAGE INTERNAL
+STRICT IMMUTABLE
+AS 'make_interval';
index 0d32428e407cf6d6063e6a7ceec3cadbbe6568f0..d200437e628a34218a52be23eb8690a8de35d5fd 100644 (file)
@@ -40,7 +40,6 @@ static int DecodeNumberField(int len, char *str,
                  struct pg_tm * tm, fsec_t *fsec, bool *is2digits);
 static int DecodeTime(char *str, int fmask, int range,
           int *tmask, struct pg_tm * tm, fsec_t *fsec);
-static int DecodeTimezone(char *str, int *tzp);
 static const datetkn *datebsearch(const char *key, const datetkn *base, int nel);
 static int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
           struct pg_tm * tm);
@@ -2075,6 +2074,9 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
    else if (mer == PM && tm->tm_hour != HOURS_PER_DAY / 2)
        tm->tm_hour += HOURS_PER_DAY / 2;
 
+   /*
+    * This should match the checks in make_timestamp_internal
+    */
    if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_min > MINS_PER_HOUR - 1 ||
        tm->tm_sec < 0 || tm->tm_sec > SECS_PER_MINUTE ||
        tm->tm_hour > HOURS_PER_DAY ||
@@ -2707,7 +2709,7 @@ DecodeNumberField(int len, char *str, int fmask,
  *
  * NB: this must *not* ereport on failure; see commands/variable.c.
  */
-static int
+int
 DecodeTimezone(char *str, int *tzp)
 {
    int         tz;
index cf6982b95dd835e9da93b04107cdf08675efc8af..f21bbae3696c7fdfd574d75b242ae1142095fe1b 100644 (file)
@@ -483,6 +483,234 @@ timestamptz_in(PG_FUNCTION_ARGS)
    PG_RETURN_TIMESTAMPTZ(result);
 }
 
+/*
+ * Try to parse a timezone specification, and return its timezone offset value
+ * if it's acceptable.  Otherwise, an error is thrown.
+ */
+static int
+parse_sane_timezone(struct pg_tm *tm, text *zone)
+{
+   char        tzname[TZ_STRLEN_MAX + 1];
+   int         rt;
+   int         tz;
+
+   text_to_cstring_buffer(zone, tzname, sizeof(tzname));
+
+   /*
+    * Look up the requested timezone.  First we try to interpret it as a
+    * numeric timezone specification; if DecodeTimezone decides it doesn't
+    * like the format, we look in the date token table (to handle cases like
+    * "EST"), and if that also 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.)
+    *
+    * Note pg_tzset happily parses numeric input that DecodeTimezone would
+    * reject.  To avoid having it accept input that would otherwise be seen
+    * as invalid, it's enough to disallow having a digit in the first
+    * position of our input string.
+    */
+   if (isdigit(*tzname))
+       ereport(ERROR,
+               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                errmsg("invalid input syntax for numeric time zone: \"%s\"",
+                       tzname),
+                errhint("Numeric time zones must have \"-\" or \"+\" as first character.")));
+
+   rt = DecodeTimezone(tzname, &tz);
+   if (rt != 0)
+   {
+       char       *lowzone;
+       int         type,
+                   val;
+
+       if (rt == DTERR_TZDISP_OVERFLOW)
+           ereport(ERROR,
+                   (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                    errmsg("numeric time zone \"%s\" out of range", tzname)));
+       else if (rt != DTERR_BAD_FORMAT)
+           ereport(ERROR,
+                   (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                    errmsg("time zone \"%s\" not recognized", tzname)));
+
+       lowzone = downcase_truncate_identifier(tzname,
+                                              strlen(tzname),
+                                              false);
+       type = DecodeSpecial(0, lowzone, &val);
+
+       if (type == TZ || type == DTZ)
+           tz = val * MINS_PER_HOUR;
+       else
+       {
+           pg_tz      *tzp;
+
+           tzp = pg_tzset(tzname);
+
+           if (tzp)
+               tz = DetermineTimeZoneOffset(tm, tzp);
+           else
+               ereport(ERROR,
+                       (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                        errmsg("time zone \"%s\" not recognized", tzname)));
+       }
+   }
+
+   return tz;
+}
+
+/*
+ * make_timestamp_internal
+ *     workhorse for make_timestamp and make_timestamptz
+ */
+static Timestamp
+make_timestamp_internal(int year, int month, int day,
+                       int hour, int min, double sec)
+{
+   struct pg_tm tm;
+   TimeOffset  date;
+   TimeOffset  time;
+   int         dterr;
+   Timestamp   result;
+
+   tm.tm_year = year;
+   tm.tm_mon = month;
+   tm.tm_mday = day;
+
+   /*
+    * Note: we'll reject zero or negative year values.  Perhaps negatives
+    * should be allowed to represent BC years?
+    */
+   dterr = ValidateDate(DTK_DATE_M, false, false, false, &tm);
+
+   if (dterr != 0)
+       ereport(ERROR,
+               (errcode(ERRCODE_DATETIME_FIELD_OVERFLOW),
+                errmsg("date field value out of range: %d-%02d-%02d",
+                       year, month, day)));
+
+   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",
+                       year, month, day)));
+
+   date = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) - POSTGRES_EPOCH_JDATE;
+
+   /* This should match the checks in DecodeTimeOnly */
+   if (hour < 0 || min < 0 || min > MINS_PER_HOUR - 1 ||
+       sec < 0 || sec > SECS_PER_MINUTE ||
+       hour > HOURS_PER_DAY ||
+   /* test for > 24:00:00 */
+       (hour == HOURS_PER_DAY && (min > 0 || sec > 0)))
+       ereport(ERROR,
+               (errcode(ERRCODE_DATETIME_FIELD_OVERFLOW),
+                errmsg("time field value out of range: %d:%02d:%02g",
+                       hour, min, sec)));
+
+   /* This should match tm2time */
+#ifdef HAVE_INT64_TIMESTAMP
+   time = (((hour * MINS_PER_HOUR + min) * SECS_PER_MINUTE)
+           * USECS_PER_SEC) + rint(sec * USECS_PER_SEC);
+
+   result = date * USECS_PER_DAY + time;
+   /* check for major overflow */
+   if ((result - time) / USECS_PER_DAY != date)
+       ereport(ERROR,
+               (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                errmsg("timestamp out of range: %d-%02d-%02d %d:%02d:%02g",
+                       year, month, day,
+                       hour, min, sec)));
+
+   /* check for just-barely overflow (okay except time-of-day wraps) */
+   /* caution: we want to allow 1999-12-31 24:00:00 */
+   if ((result < 0 && date > 0) ||
+       (result > 0 && date < -1))
+       ereport(ERROR,
+               (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                errmsg("timestamp out of range: %d-%02d-%02d %d:%02d:%02g",
+                       year, month, day,
+                       hour, min, sec)));
+#else
+   time = ((hour * MINS_PER_HOUR + min) * SECS_PER_MINUTE) + sec;
+   result = date * SECS_PER_DAY + time;
+#endif
+
+   return result;
+}
+
+/*
+ * make_timestamp() - timestamp constructor
+ */
+Datum
+make_timestamp(PG_FUNCTION_ARGS)
+{
+   int32       year = PG_GETARG_INT32(0);
+   int32       month = PG_GETARG_INT32(1);
+   int32       mday = PG_GETARG_INT32(2);
+   int32       hour = PG_GETARG_INT32(3);
+   int32       min = PG_GETARG_INT32(4);
+   float8      sec = PG_GETARG_FLOAT8(5);
+   Timestamp   result;
+
+   result = make_timestamp_internal(year, month, mday,
+                                    hour, min, sec);
+
+   PG_RETURN_TIMESTAMP(result);
+}
+
+/*
+ * make_timestamptz() - timestamp with time zone constructor
+ */
+Datum
+make_timestamptz(PG_FUNCTION_ARGS)
+{
+   int32       year = PG_GETARG_INT32(0);
+   int32       month = PG_GETARG_INT32(1);
+   int32       mday = PG_GETARG_INT32(2);
+   int32       hour = PG_GETARG_INT32(3);
+   int32       min = PG_GETARG_INT32(4);
+   float8      sec = PG_GETARG_FLOAT8(5);
+   Timestamp   result;
+
+   result = make_timestamp_internal(year, month, mday,
+                                    hour, min, sec);
+
+   PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(result));
+}
+
+/*
+ * Construct a timestamp with time zone.
+ *     As above, but the time zone is specified as seventh argument.
+ */
+Datum
+make_timestamptz_at_timezone(PG_FUNCTION_ARGS)
+{
+   int32       year = PG_GETARG_INT32(0);
+   int32       month = PG_GETARG_INT32(1);
+   int32       mday = PG_GETARG_INT32(2);
+   int32       hour = PG_GETARG_INT32(3);
+   int32       min = PG_GETARG_INT32(4);
+   float8      sec = PG_GETARG_FLOAT8(5);
+   text       *zone = PG_GETARG_TEXT_PP(6);
+   Timestamp   timestamp;
+   struct pg_tm tt;
+   int         tz;
+   fsec_t      fsec;
+
+   timestamp = make_timestamp_internal(year, month, mday,
+                                       hour, min, sec);
+
+   if (timestamp2tm(timestamp, NULL, &tt, &fsec, NULL, NULL) != 0)
+       ereport(ERROR,
+               (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                errmsg("timestamp out of range")));
+
+   tz = parse_sane_timezone(&tt, zone);
+
+   PG_RETURN_TIMESTAMPTZ((TimestampTz) dt2local(timestamp, -tz));
+}
+
 /* timestamptz_out()
  * Convert a timestamp to external form.
  */
@@ -1220,6 +1448,42 @@ AdjustIntervalForTypmod(Interval *interval, int32 typmod)
    }
 }
 
+/*
+ * make_interval - numeric Interval constructor
+ */
+Datum
+make_interval(PG_FUNCTION_ARGS)
+{
+   int32       years = PG_GETARG_INT32(0);
+   int32       months = PG_GETARG_INT32(1);
+   int32       weeks = PG_GETARG_INT32(2);
+   int32       days = PG_GETARG_INT32(3);
+   int32       hours = PG_GETARG_INT32(4);
+   int32       mins = PG_GETARG_INT32(5);
+   double      secs = PG_GETARG_FLOAT8(6);
+   Interval   *result;
+
+   result = (Interval *) palloc(sizeof(Interval));
+   result->month = years * MONTHS_PER_YEAR + months;
+   result->day = weeks * 7 + days;
+
+#ifdef HAVE_INT64_TIMESTAMP
+   result->time = ((((hours * INT64CONST(60)) +
+                     mins) * INT64CONST(60)) +
+                   secs) * USECS_PER_SEC;
+#else
+   result->time = (((hours * (double) MINS_PER_HOUR) +
+                    mins) * (double) SECS_PER_MINUTE) +
+       secs;
+#endif
+
+#ifdef NOT_USED
+   /* this is a no-op for negative typmods */
+   AdjustIntervalForTypmod(result, -1);
+#endif
+
+   PG_RETURN_INTERVAL_P(result);
+}
 
 /* EncodeSpecialTimestamp()
  * Convert reserved timestamp data type to string.
index 22dd0fc58e83cc089c40b6d3a65aa71dbeca137a..047039a4176fc311be5fd8fa04148cc15366bb9d 100644 (file)
@@ -53,6 +53,6 @@
  */
 
 /*                         yyyymmddN */
-#define CATALOG_VERSION_NO 201403032
+#define CATALOG_VERSION_NO 201403041
 
 #endif
index c570651872237a4707e343ff12140e48796270ef..bde018d9578d18d8d1739037f3889e11b2bcda28 100644 (file)
@@ -4718,11 +4718,19 @@ DESCR("int8range constructor");
 DATA(insert OID = 3946 (  int8range PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 3926 "20 20 25" _null_ _null_ _null_ _null_ range_constructor3 _null_ _null_ _null_ ));
 DESCR("int8range constructor");
 
-/* date, time constructors */
+/* date, time, timestamp constructors */
 DATA(insert OID = 3846 ( make_date PGNSP PGUID 12 1 0 0 0 f f f f t f i 3 0 1082 "23 23 23" _null_ _null_ "{year,month,day}" _null_ make_date _null_ _null_ _null_ ));
 DESCR("construct date");
 DATA(insert OID = 3847 ( make_time PGNSP PGUID 12 1 0 0 0 f f f f t f i 3 0 1083 "23 23 701" _null_ _null_ "{hour,min,sec}" _null_ make_time _null_ _null_ _null_ ));
 DESCR("construct time");
+DATA(insert OID = 3461 ( make_timestamp    PGNSP PGUID 12 1 0 0 0 f f f f t f i 6 0 1114 "23 23 23 23 23 701" _null_ _null_ "{year,month,mday,hour,min,sec}" _null_ make_timestamp _null_ _null_ _null_ ));
+DESCR("construct timestamp");
+DATA(insert OID = 3462 ( make_timestamptz  PGNSP PGUID 12 1 0 0 0 f f f f t f s 6 0 1184 "23 23 23 23 23 701" _null_ _null_ "{year,month,mday,hour,min,sec}" _null_ make_timestamptz _null_ _null_ _null_ ));
+DESCR("construct timestamp with time zone");
+DATA(insert OID = 3463 ( make_timestamptz  PGNSP PGUID 12 1 0 0 0 f f f f t f s 7 0 1184 "23 23 23 23 23 701 25" _null_ _null_ "{year,month,mday,hour,min,sec,timezone}" _null_ make_timestamptz_at_timezone _null_ _null_ _null_ ));
+DESCR("construct timestamp with time zone");
+DATA(insert OID = 3464 ( make_interval PGNSP PGUID 12 1 0 0 0 f f f f t f i 7 0 1186 "23 23 23 23 23 23 701" _null_ _null_ "{years,months,weeks,days,hours,mins,secs}" _null_ make_interval _null_ _null_ _null_ ));
+DESCR("construct interval");
 
 /* spgist support functions */
 DATA(insert OID = 4001 (  spggettuple     PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 16 "2281 2281" _null_ _null_ _null_ _null_  spggettuple _null_ _null_ _null_ ));
index b10648269f6a6654f5589c459fb60e6f8cf59b67..fc3a1f611da6fb9dedb4e474a29fc2c7e50a821a 100644 (file)
@@ -283,6 +283,7 @@ extern int ParseDateTime(const char *timestr, char *workbuf, size_t buflen,
 extern int DecodeDateTime(char **field, int *ftype,
               int nf, int *dtype,
               struct pg_tm * tm, fsec_t *fsec, int *tzp);
+extern int DecodeTimezone(char *str, int *tzp);
 extern int DecodeTimeOnly(char **field, int *ftype,
               int nf, int *dtype,
               struct pg_tm * tm, fsec_t *fsec, int *tzp);
index 47fb8661f9a1654ae793e0af4eb6ea21eb33d79b..2731c6a25f4f58cf8b696ccebb395916c816d15b 100644 (file)
@@ -121,6 +121,10 @@ extern Datum timestamp_gt_timestamptz(PG_FUNCTION_ARGS);
 extern Datum timestamp_ge_timestamptz(PG_FUNCTION_ARGS);
 extern Datum timestamp_cmp_timestamptz(PG_FUNCTION_ARGS);
 
+extern Datum make_timestamp(PG_FUNCTION_ARGS);
+extern Datum make_timestamptz(PG_FUNCTION_ARGS);
+extern Datum make_timestamptz_at_timezone(PG_FUNCTION_ARGS);
+
 extern Datum timestamptz_eq_timestamp(PG_FUNCTION_ARGS);
 extern Datum timestamptz_ne_timestamp(PG_FUNCTION_ARGS);
 extern Datum timestamptz_lt_timestamp(PG_FUNCTION_ARGS);
@@ -151,6 +155,7 @@ extern Datum interval_larger(PG_FUNCTION_ARGS);
 extern Datum interval_justify_interval(PG_FUNCTION_ARGS);
 extern Datum interval_justify_hours(PG_FUNCTION_ARGS);
 extern Datum interval_justify_days(PG_FUNCTION_ARGS);
+extern Datum make_interval(PG_FUNCTION_ARGS);
 
 extern Datum timestamp_trunc(PG_FUNCTION_ARGS);
 extern Datum interval_trunc(PG_FUNCTION_ARGS);
index 99fd0ca4900c8208f66fff236ec77ef1ed3d26a7..7b4e60b57155e0b29d8445906df34658ed46c8e4 100644 (file)
@@ -807,3 +807,48 @@ select interval_hash('30 days'::interval) = interval_hash('1 month'::interval) a
  t
 (1 row)
 
+-- numeric constructor
+select make_interval(years := 2);
+ make_interval 
+---------------
+ @ 2 years
+(1 row)
+
+select make_interval(years := 1, months := 6);
+  make_interval  
+-----------------
+ @ 1 year 6 mons
+(1 row)
+
+select make_interval(years := 1, months := -1, weeks := 5, days := -7, hours := 25, mins := -180);
+       make_interval        
+----------------------------
+ @ 11 mons 28 days 22 hours
+(1 row)
+
+select make_interval() = make_interval(years := 0, months := 0, weeks := 0, days := 0, mins := 0, secs := 0.0);
+ ?column? 
+----------
+ t
+(1 row)
+
+select make_interval(hours := -2, mins := -10, secs := -25.3);
+          make_interval          
+---------------------------------
+ @ 2 hours 10 mins 25.3 secs ago
+(1 row)
+
+select make_interval(years := 'inf'::float::int);
+ERROR:  integer out of range
+select make_interval(months := 'NaN'::float::int);
+ERROR:  integer out of range
+select make_interval(secs := 'inf');
+ERROR:  interval out of range
+select make_interval(secs := 'NaN');
+ERROR:  interval out of range
+select make_interval(secs := 7e12);
+           make_interval            
+------------------------------------
+ @ 1944444444 hours 26 mins 40 secs
+(1 row)
+
index db2cfe6033320c89e6dbf25acf2fc1dd0ec79689..a092fc25eb7db04d11207ae1905ced54fb62563a 100644 (file)
@@ -1585,3 +1585,10 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
             | 2001 1 1 1 1 1 1
 (65 rows)
 
+-- timestamp numeric fields constructor
+SELECT make_timestamp(2014,12,28,6,30,45.887);
+        make_timestamp        
+------------------------------
+ Sun Dec 28 06:30:45.887 2014
+(1 row)
+
index 9f4f7a495a02cf73ac4208fe2e5cfb0a647e34c8..33dfa5916e69086a24786be24cc07da1830492fb 100644 (file)
@@ -1697,3 +1697,117 @@ SELECT * FROM TIMESTAMPTZ_TST ORDER BY a;
 
 --Cleanup
 DROP TABLE TIMESTAMPTZ_TST;
+-- test timestamptz constructors
+set TimeZone to 'America/Santiago';
+-- numeric timezone
+SELECT make_timestamptz(1973, 07, 15, 08, 15, 55.33);
+        make_timestamptz         
+---------------------------------
+ Sun Jul 15 08:15:55.33 1973 CLT
+(1 row)
+
+SELECT make_timestamptz(1973, 07, 15, 08, 15, 55.33, '+2');
+        make_timestamptz         
+---------------------------------
+ Sun Jul 15 02:15:55.33 1973 CLT
+(1 row)
+
+SELECT make_timestamptz(1973, 07, 15, 08, 15, 55.33, '-2');
+        make_timestamptz         
+---------------------------------
+ Sun Jul 15 06:15:55.33 1973 CLT
+(1 row)
+
+WITH tzs (tz) AS (VALUES
+    ('+1'), ('+1:'), ('+1:0'), ('+100'), ('+1:00'), ('+01:00'),
+    ('+10'), ('+1000'), ('+10:'), ('+10:0'), ('+10:00'), ('+10:00:'),
+    ('+10:00:1'), ('+10:00:01'),
+    ('+10:00:10'))
+     SELECT make_timestamptz(2010, 2, 27, 3, 45, 00, tz), tz FROM tzs;
+       make_timestamptz        |    tz     
+-------------------------------+-----------
+ Fri Feb 26 23:45:00 2010 CLST | +1
+ Fri Feb 26 23:45:00 2010 CLST | +1:
+ Fri Feb 26 23:45:00 2010 CLST | +1:0
+ Fri Feb 26 23:45:00 2010 CLST | +100
+ Fri Feb 26 23:45:00 2010 CLST | +1:00
+ Fri Feb 26 23:45:00 2010 CLST | +01:00
+ Fri Feb 26 14:45:00 2010 CLST | +10
+ Fri Feb 26 14:45:00 2010 CLST | +1000
+ Fri Feb 26 14:45:00 2010 CLST | +10:
+ Fri Feb 26 14:45:00 2010 CLST | +10:0
+ Fri Feb 26 14:45:00 2010 CLST | +10:00
+ Fri Feb 26 14:45:00 2010 CLST | +10:00:
+ Fri Feb 26 14:44:59 2010 CLST | +10:00:1
+ Fri Feb 26 14:44:59 2010 CLST | +10:00:01
+ Fri Feb 26 14:44:50 2010 CLST | +10:00:10
+(15 rows)
+
+-- these should fail
+SELECT make_timestamptz(1973, 07, 15, 08, 15, 55.33, '2');
+ERROR:  invalid input syntax for numeric time zone: "2"
+HINT:  Numeric time zones must have "-" or "+" as first character.
+SELECT make_timestamptz(2014, 12, 10, 10, 10, 10, '+16');
+ERROR:  numeric time zone "+16" out of range
+SELECT make_timestamptz(2014, 12, 10, 10, 10, 10, '-16');
+ERROR:  numeric time zone "-16" out of range
+-- should be true
+SELECT make_timestamptz(1973, 07, 15, 08, 15, 55.33, '+2') = '1973-07-15 08:15:55.33+02'::timestamptz;
+ ?column? 
+----------
+ t
+(1 row)
+
+-- full timezone name
+SELECT make_timestamptz(2014, 12, 10, 0, 0, 0, 'Europe/Prague') = timestamptz '2014-12-10 00:00:00 Europe/Prague';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT make_timestamptz(2014, 12, 10, 0, 0, 0, 'Europe/Prague') AT TIME ZONE 'UTC';
+         timezone         
+--------------------------
+ Tue Dec 09 23:00:00 2014
+(1 row)
+
+SELECT make_timestamptz(1846, 12, 10, 0, 0, 0, 'Asia/Manila') AT TIME ZONE 'UTC';
+         timezone         
+--------------------------
+ Wed Dec 09 15:56:00 1846
+(1 row)
+
+SELECT make_timestamptz(1866, 12, 10, 0, 0, 0, 'America/Metlakatla') AT TIME ZONE 'UTC';
+         timezone         
+--------------------------
+ Sun Dec 09 08:46:18 1866
+(1 row)
+
+SELECT make_timestamptz(1901, 12, 10, 0, 0, 0, 'America/Metlakatla') AT TIME ZONE 'UTC';
+         timezone         
+--------------------------
+ Tue Dec 10 08:00:00 1901
+(1 row)
+
+SELECT make_timestamptz(2014, 12, 10, 0, 0, 0, 'Mars/Mons_Olympus');
+ERROR:  time zone "Mars/Mons_Olympus" not recognized
+-- abbreviations
+SELECT make_timestamptz(2008, 12, 10, 10, 10, 10, 'CLST');
+       make_timestamptz        
+-------------------------------
+ Wed Dec 10 10:10:10 2008 CLST
+(1 row)
+
+SELECT make_timestamptz(2008, 12, 10, 10, 10, 10, 'CLT');
+       make_timestamptz        
+-------------------------------
+ Wed Dec 10 11:10:10 2008 CLST
+(1 row)
+
+SELECT make_timestamptz(2014, 12, 10, 10, 10, 10, 'PST8PDT');
+       make_timestamptz        
+-------------------------------
+ Wed Dec 10 15:10:10 2014 CLST
+(1 row)
+
+RESET TimeZone;
index 7cee2864def15f0619319ce8de5e30b81f093e85..684811da0bcb7cc6b982fae60f3c6486963e61d2 100644 (file)
@@ -261,3 +261,17 @@ select interval '0:0:0.7', interval '@ 0.70 secs', interval '0.7 seconds';
 -- check that '30 days' equals '1 month' according to the hash function
 select '30 days'::interval = '1 month'::interval as t;
 select interval_hash('30 days'::interval) = interval_hash('1 month'::interval) as t;
+
+-- numeric constructor
+select make_interval(years := 2);
+select make_interval(years := 1, months := 6);
+select make_interval(years := 1, months := -1, weeks := 5, days := -7, hours := 25, mins := -180);
+
+select make_interval() = make_interval(years := 0, months := 0, weeks := 0, days := 0, mins := 0, secs := 0.0);
+select make_interval(hours := -2, mins := -10, secs := -25.3);
+
+select make_interval(years := 'inf'::float::int);
+select make_interval(months := 'NaN'::float::int);
+select make_interval(secs := 'inf');
+select make_interval(secs := 'NaN');
+select make_interval(secs := 7e12);
index c4ed4eee3beb64548ea5aa263ee3e4d6ff1ff633..b22cd4871e34f559e063261977e6c3c2aeca2cab 100644 (file)
@@ -222,3 +222,6 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
 
 SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
    FROM TIMESTAMP_TBL;
+
+-- timestamp numeric fields constructor
+SELECT make_timestamp(2014,12,28,6,30,45.887);
index 4eef62e99afa7c52938b7630204e603d24a4ee7c..3ddf38c985009dfb10e70d2d92a9843eea5c4f4f 100644 (file)
@@ -254,3 +254,40 @@ INSERT INTO TIMESTAMPTZ_TST VALUES(4, '1000000312 23:58:48 IST');
 SELECT * FROM TIMESTAMPTZ_TST ORDER BY a;
 --Cleanup
 DROP TABLE TIMESTAMPTZ_TST;
+
+-- test timestamptz constructors
+set TimeZone to 'America/Santiago';
+
+-- numeric timezone
+SELECT make_timestamptz(1973, 07, 15, 08, 15, 55.33);
+SELECT make_timestamptz(1973, 07, 15, 08, 15, 55.33, '+2');
+SELECT make_timestamptz(1973, 07, 15, 08, 15, 55.33, '-2');
+WITH tzs (tz) AS (VALUES
+    ('+1'), ('+1:'), ('+1:0'), ('+100'), ('+1:00'), ('+01:00'),
+    ('+10'), ('+1000'), ('+10:'), ('+10:0'), ('+10:00'), ('+10:00:'),
+    ('+10:00:1'), ('+10:00:01'),
+    ('+10:00:10'))
+     SELECT make_timestamptz(2010, 2, 27, 3, 45, 00, tz), tz FROM tzs;
+
+-- these should fail
+SELECT make_timestamptz(1973, 07, 15, 08, 15, 55.33, '2');
+SELECT make_timestamptz(2014, 12, 10, 10, 10, 10, '+16');
+SELECT make_timestamptz(2014, 12, 10, 10, 10, 10, '-16');
+
+-- should be true
+SELECT make_timestamptz(1973, 07, 15, 08, 15, 55.33, '+2') = '1973-07-15 08:15:55.33+02'::timestamptz;
+
+-- full timezone name
+SELECT make_timestamptz(2014, 12, 10, 0, 0, 0, 'Europe/Prague') = timestamptz '2014-12-10 00:00:00 Europe/Prague';
+SELECT make_timestamptz(2014, 12, 10, 0, 0, 0, 'Europe/Prague') AT TIME ZONE 'UTC';
+SELECT make_timestamptz(1846, 12, 10, 0, 0, 0, 'Asia/Manila') AT TIME ZONE 'UTC';
+SELECT make_timestamptz(1866, 12, 10, 0, 0, 0, 'America/Metlakatla') AT TIME ZONE 'UTC';
+SELECT make_timestamptz(1901, 12, 10, 0, 0, 0, 'America/Metlakatla') AT TIME ZONE 'UTC';
+SELECT make_timestamptz(2014, 12, 10, 0, 0, 0, 'Mars/Mons_Olympus');
+
+-- abbreviations
+SELECT make_timestamptz(2008, 12, 10, 10, 10, 10, 'CLST');
+SELECT make_timestamptz(2008, 12, 10, 10, 10, 10, 'CLT');
+SELECT make_timestamptz(2014, 12, 10, 10, 10, 10, 'PST8PDT');
+
+RESET TimeZone;