]> granicus.if.org Git - postgresql/commitdiff
Allow to_timestamp(float8) to convert float infinity to timestamp infinity.
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 29 Mar 2016 21:09:21 +0000 (17:09 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 29 Mar 2016 21:09:29 +0000 (17:09 -0400)
With the original SQL-function implementation, such cases failed because
we don't support infinite intervals.  Converting the function to C lets
us bypass the interval representation, which should be a bit faster as
well as more flexible.

Vitaly Burovoy, reviewed by Anastasia Lubennikova

doc/src/sgml/func.sgml
src/backend/utils/adt/timestamp.c
src/include/catalog/catversion.h
src/include/catalog/pg_proc.h
src/include/utils/timestamp.h
src/test/regress/expected/timestamptz.out
src/test/regress/sql/timestamptz.sql

index e6c4ee52ee12ca061085eb201d974ec6300b0f50..1bc9fbc4f518c33808a79d3040f53bb3245f60e6 100644 (file)
@@ -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&nbsp;Dec&nbsp;2000', 'DD&nbsp;Mon&nbsp;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>
 
index c9e5270a9ebe8e1e0b6878b745bd7c504d8f814a..b9c26b68544de0aca2c04e8b9adb56c6ab6e3f23 100644 (file)
@@ -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.
  */
index 92ea48da8957dfb1c76d68818877d0f936ac61bf..d9d62d0d74420b9a6f28b98dc3ab8e33c96615af 100644 (file)
@@ -53,6 +53,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     201603291
+#define CATALOG_VERSION_NO     201603292
 
 #endif
index a59532732c0433a95bc35fe06ae1dc828389564e..7619c40eb3bcfdb0a7a9e8db8ffa54f2e6f2845b 100644 (file)
@@ -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");
index fbead3a168cc55a6fbb3f84dc0915ba1e797adf4..865e993e8abe4e2269423138d0eddc8e8175d24d 100644 (file)
@@ -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);
index 271873d326de3240f6484653634f1836f2dda57a..9627aa367157322fa5d2d17d2bb611425f62b52e 100644 (file)
@@ -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          
index 5ec92e55c8ff07cae29e717f23886b959be936d1..35470ce7c9e3189e508878e386e2a1bb5f00f1d8 100644 (file)
@@ -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;