diff options
-rw-r--r-- | doc/src/sgml/func.sgml | 50 | ||||
-rw-r--r-- | src/backend/utils/adt/timestamp.c | 58 | ||||
-rw-r--r-- | src/include/catalog/catversion.h | 2 | ||||
-rw-r--r-- | src/include/catalog/pg_proc.h | 2 | ||||
-rw-r--r-- | src/include/utils/timestamp.h | 1 | ||||
-rw-r--r-- | src/test/regress/expected/timestamptz.out | 47 | ||||
-rw-r--r-- | src/test/regress/sql/timestamptz.sql | 15 |
7 files changed, 149 insertions, 26 deletions
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index e6c4ee52ee1..1bc9fbc4f51 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -5579,15 +5579,6 @@ SELECT regexp_matches('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}'); argument is the value to be formatted and the second argument is a template that defines the output or input format. </para> - <para> - A single-argument <function>to_timestamp</function> function is also - available; it accepts a - <type>double precision</type> argument and converts from Unix epoch - (seconds since 1970-01-01 00:00:00+00) to - <type>timestamp with time zone</type>. - (<type>Integer</type> Unix epochs are implicitly cast to - <type>double precision</type>.) - </para> <table id="functions-formatting-table"> <title>Formatting Functions</title> @@ -5670,16 +5661,17 @@ SELECT regexp_matches('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}'); <entry>convert string to time stamp</entry> <entry><literal>to_timestamp('05 Dec 2000', 'DD Mon YYYY')</literal></entry> </row> - <row> - <entry><literal><function>to_timestamp(<type>double precision</type>)</function></literal></entry> - <entry><type>timestamp with time zone</type></entry> - <entry>convert Unix epoch to time stamp</entry> - <entry><literal>to_timestamp(1284352323)</literal></entry> - </row> </tbody> </tgroup> </table> + <note> + <para> + There is also a single-argument <function>to_timestamp</function> + function; see <xref linkend="functions-datetime-table">. + </para> + </note> + <para> In a <function>to_char</> output template string, there are certain patterns that are recognized and replaced with appropriately-formatted @@ -7060,8 +7052,8 @@ SELECT regexp_matches('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}'); <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. + and seconds fields; if <parameter>timezone</parameter> is not + specified, the current time zone is used </entry> <entry><literal>make_timestamptz(2013, 7, 15, 8, 15, 23.5)</literal></entry> <entry><literal>2013-07-15 08:15:23.5+01</literal></entry> @@ -7127,6 +7119,19 @@ SELECT regexp_matches('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}'); <entry></entry> <entry></entry> </row> + <row> + <entry> + <indexterm> + <primary>to_timestamp</primary> + </indexterm> + <literal><function>to_timestamp(<type>double precision</type>)</function></literal> + </entry> + <entry><type>timestamp with time zone</type></entry> + <entry>Convert Unix epoch (seconds since 1970-01-01 00:00:00+00) to + timestamp</entry> + <entry><literal>to_timestamp(1284352323)</literal></entry> + <entry><literal>2010-09-13 04:32:03+00</literal></entry> + </row> </tbody> </tgroup> </table> @@ -7377,16 +7382,13 @@ SELECT EXTRACT(EPOCH FROM INTERVAL '5 days 3 hours'); </screen> <para> - Here is how you can convert an epoch value back to a time - stamp: + You can convert an epoch value back to a time stamp + with <function>to_timestamp</>: </para> <screen> -SELECT TIMESTAMP WITH TIME ZONE 'epoch' + 982384720.12 * INTERVAL '1 second'; +SELECT to_timestamp(982384720.12); +<lineannotation>Result: </lineannotation><computeroutput>2001-02-17 04:38:40.12+00</computeroutput> </screen> - <para> - (The <function>to_timestamp</> function encapsulates the above - conversion.) - </para> </listitem> </varlistentry> diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index c9e5270a9eb..b9c26b68544 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -737,6 +737,64 @@ make_timestamptz_at_timezone(PG_FUNCTION_ARGS) PG_RETURN_TIMESTAMPTZ(result); } +/* + * to_timestamp(double precision) + * Convert UNIX epoch to timestamptz. + */ +Datum +float8_timestamptz(PG_FUNCTION_ARGS) +{ + float8 seconds = PG_GETARG_FLOAT8(0); + TimestampTz result; + + /* Deal with NaN and infinite inputs ... */ + if (isnan(seconds)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp cannot be NaN"))); + + if (isinf(seconds)) + { + if (seconds < 0) + TIMESTAMP_NOBEGIN(result); + else + TIMESTAMP_NOEND(result); + } + else + { + /* Out of range? */ + if (seconds < + (float8) SECS_PER_DAY * (DATETIME_MIN_JULIAN - UNIX_EPOCH_JDATE)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range: \"%g\"", seconds))); + + if (seconds >= + (float8) SECS_PER_DAY * (TIMESTAMP_END_JULIAN - UNIX_EPOCH_JDATE)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range: \"%g\"", seconds))); + + /* Convert UNIX epoch to Postgres epoch */ + seconds -= ((POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY); + +#ifdef HAVE_INT64_TIMESTAMP + result = seconds * USECS_PER_SEC; +#else + result = seconds; +#endif + + /* Recheck in case roundoff produces something just out of range */ + if (!IS_VALID_TIMESTAMP(result)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range: \"%g\"", + PG_GETARG_FLOAT8(0)))); + } + + PG_RETURN_TIMESTAMP(result); +} + /* timestamptz_out() * Convert a timestamp to external form. */ diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 92ea48da895..d9d62d0d744 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 201603291 +#define CATALOG_VERSION_NO 201603292 #endif diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index a59532732c0..7619c40eb3b 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -1202,7 +1202,7 @@ DATA(insert OID = 1154 ( timestamptz_lt PGNSP PGUID 12 1 0 0 0 f f f t t f i DATA(insert OID = 1155 ( timestamptz_le PGNSP PGUID 12 1 0 0 0 f f f t t f i s 2 0 16 "1184 1184" _null_ _null_ _null_ _null_ _null_ timestamp_le _null_ _null_ _null_ )); DATA(insert OID = 1156 ( timestamptz_ge PGNSP PGUID 12 1 0 0 0 f f f t t f i s 2 0 16 "1184 1184" _null_ _null_ _null_ _null_ _null_ timestamp_ge _null_ _null_ _null_ )); DATA(insert OID = 1157 ( timestamptz_gt PGNSP PGUID 12 1 0 0 0 f f f t t f i s 2 0 16 "1184 1184" _null_ _null_ _null_ _null_ _null_ timestamp_gt _null_ _null_ _null_ )); -DATA(insert OID = 1158 ( to_timestamp PGNSP PGUID 14 1 0 0 0 f f f f t f i s 1 0 1184 "701" _null_ _null_ _null_ _null_ _null_ "select (''epoch''::pg_catalog.timestamptz + $1 * ''1 second''::pg_catalog.interval)" _null_ _null_ _null_ )); +DATA(insert OID = 1158 ( to_timestamp PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 1184 "701" _null_ _null_ _null_ _null_ _null_ float8_timestamptz _null_ _null_ _null_ )); DESCR("convert UNIX epoch to timestamptz"); DATA(insert OID = 3995 ( timestamp_zone_transform PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 2281 "2281" _null_ _null_ _null_ _null_ _null_ timestamp_zone_transform _null_ _null_ _null_ )); DESCR("transform a time zone adjustment"); diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h index fbead3a168c..865e993e8ab 100644 --- a/src/include/utils/timestamp.h +++ b/src/include/utils/timestamp.h @@ -124,6 +124,7 @@ 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 float8_timestamptz(PG_FUNCTION_ARGS); extern Datum timestamptz_eq_timestamp(PG_FUNCTION_ARGS); extern Datum timestamptz_ne_timestamp(PG_FUNCTION_ARGS); diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out index 271873d326d..9627aa36715 100644 --- a/src/test/regress/expected/timestamptz.out +++ b/src/test/regress/expected/timestamptz.out @@ -2307,6 +2307,53 @@ SELECT make_timestamptz(2007, 12, 9, 3, 0, 0, 'VET'); Sun Dec 09 07:30:00 2007 UTC (1 row) +SELECT to_timestamp( 0); -- 1970-01-01 00:00:00+00 + to_timestamp +------------------------------ + Thu Jan 01 00:00:00 1970 UTC +(1 row) + +SELECT to_timestamp( 946684800); -- 2000-01-01 00:00:00+00 + to_timestamp +------------------------------ + Sat Jan 01 00:00:00 2000 UTC +(1 row) + +SELECT to_timestamp(1262349296.7890123); -- 2010-01-01 12:34:56.789012+00 + to_timestamp +------------------------------------- + Fri Jan 01 12:34:56.789012 2010 UTC +(1 row) + +-- edge cases +SELECT to_timestamp(-1e20::float8); -- error, out of range +ERROR: timestamp out of range: "-1e+20" +SELECT to_timestamp(-210866803200.0625); -- error, out of range +ERROR: timestamp out of range: "-2.10867e+11" +SELECT to_timestamp(-210866803200); -- 4714-11-24 00:00:00+00 BC + to_timestamp +--------------------------------- + Mon Nov 24 00:00:00 4714 UTC BC +(1 row) + +-- The upper boundary differs between integer and float timestamps, so check the biggest one +SELECT to_timestamp(185331707078400::float8); -- error, out of range +ERROR: timestamp out of range: "1.85332e+14" +-- nonfinite values +SELECT to_timestamp(' Infinity'::float); + to_timestamp +-------------- + infinity +(1 row) + +SELECT to_timestamp('-Infinity'::float); + to_timestamp +-------------- + -infinity +(1 row) + +SELECT to_timestamp('NaN'::float); +ERROR: timestamp cannot be NaN SET TimeZone to 'Europe/Moscow'; SELECT '2011-03-26 21:00:00 UTC'::timestamptz; timestamptz diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql index 5ec92e55c8f..35470ce7c9e 100644 --- a/src/test/regress/sql/timestamptz.sql +++ b/src/test/regress/sql/timestamptz.sql @@ -403,6 +403,21 @@ SELECT '2007-12-09 04:00:00'::timestamp AT TIME ZONE 'VET'; SELECT make_timestamptz(2007, 12, 9, 2, 0, 0, 'VET'); SELECT make_timestamptz(2007, 12, 9, 3, 0, 0, 'VET'); +SELECT to_timestamp( 0); -- 1970-01-01 00:00:00+00 +SELECT to_timestamp( 946684800); -- 2000-01-01 00:00:00+00 +SELECT to_timestamp(1262349296.7890123); -- 2010-01-01 12:34:56.789012+00 +-- edge cases +SELECT to_timestamp(-1e20::float8); -- error, out of range +SELECT to_timestamp(-210866803200.0625); -- error, out of range +SELECT to_timestamp(-210866803200); -- 4714-11-24 00:00:00+00 BC +-- The upper boundary differs between integer and float timestamps, so check the biggest one +SELECT to_timestamp(185331707078400::float8); -- error, out of range +-- nonfinite values +SELECT to_timestamp(' Infinity'::float); +SELECT to_timestamp('-Infinity'::float); +SELECT to_timestamp('NaN'::float); + + SET TimeZone to 'Europe/Moscow'; SELECT '2011-03-26 21:00:00 UTC'::timestamptz; |