diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index d344b3a894a7e72cd94ebf3b5cd8b6b51685ff8f..a1f627cbd6cf9d5bbb71abea93380b3fd79bf912 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -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> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 0500a73e1ba2a70db284e1de1d7be8aa6fa20a67..053d7585ca45fa128cd78cc8f5866b0086c86969 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -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'; diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c index 0d32428e407cf6d6063e6a7ceec3cadbbe6568f0..d200437e628a34218a52be23eb8690a8de35d5fd 100644 --- a/src/backend/utils/adt/datetime.c +++ b/src/backend/utils/adt/datetime.c @@ -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; diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index cf6982b95dd835e9da93b04107cdf08675efc8af..f21bbae3696c7fdfd574d75b242ae1142095fe1b 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -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. diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 22dd0fc58e83cc089c40b6d3a65aa71dbeca137a..047039a4176fc311be5fd8fa04148cc15366bb9d 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 201403032 +#define CATALOG_VERSION_NO 201403041 #endif diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index c570651872237a4707e343ff12140e48796270ef..bde018d9578d18d8d1739037f3889e11b2bcda28 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -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_ )); diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h index b10648269f6a6654f5589c459fb60e6f8cf59b67..fc3a1f611da6fb9dedb4e474a29fc2c7e50a821a 100644 --- a/src/include/utils/datetime.h +++ b/src/include/utils/datetime.h @@ -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); diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h index 47fb8661f9a1654ae793e0af4eb6ea21eb33d79b..2731c6a25f4f58cf8b696ccebb395916c816d15b 100644 --- a/src/include/utils/timestamp.h +++ b/src/include/utils/timestamp.h @@ -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); diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out index 99fd0ca4900c8208f66fff236ec77ef1ed3d26a7..7b4e60b57155e0b29d8445906df34658ed46c8e4 100644 --- a/src/test/regress/expected/interval.out +++ b/src/test/regress/expected/interval.out @@ -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) + diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out index db2cfe6033320c89e6dbf25acf2fc1dd0ec79689..a092fc25eb7db04d11207ae1905ced54fb62563a 100644 --- a/src/test/regress/expected/timestamp.out +++ b/src/test/regress/expected/timestamp.out @@ -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) + diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out index 9f4f7a495a02cf73ac4208fe2e5cfb0a647e34c8..33dfa5916e69086a24786be24cc07da1830492fb 100644 --- a/src/test/regress/expected/timestamptz.out +++ b/src/test/regress/expected/timestamptz.out @@ -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; diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql index 7cee2864def15f0619319ce8de5e30b81f093e85..684811da0bcb7cc6b982fae60f3c6486963e61d2 100644 --- a/src/test/regress/sql/interval.sql +++ b/src/test/regress/sql/interval.sql @@ -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); diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql index c4ed4eee3beb64548ea5aa263ee3e4d6ff1ff633..b22cd4871e34f559e063261977e6c3c2aeca2cab 100644 --- a/src/test/regress/sql/timestamp.sql +++ b/src/test/regress/sql/timestamp.sql @@ -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); diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql index 4eef62e99afa7c52938b7630204e603d24a4ee7c..3ddf38c985009dfb10e70d2d92a9843eea5c4f4f 100644 --- a/src/test/regress/sql/timestamptz.sql +++ b/src/test/regress/sql/timestamptz.sql @@ -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;