diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 000489d..47219bb 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -7358,12 +7358,9 @@ SELECT EXTRACT(EPOCH FROM INTERVAL '5 days 3 hours'); stamp: -SELECT TIMESTAMP WITH TIME ZONE 'epoch' + 982384720.12 * INTERVAL '1 second'; +SELECT to_timestamp(982384720.12); +Result: 2001-02-17 04:38:40.12+00 - - (The to_timestamp function encapsulates the above - conversion.) - diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index 3f013e3..9dad256 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -476,6 +476,74 @@ timestamptz_in(PG_FUNCTION_ARGS) PG_RETURN_TIMESTAMPTZ(result); } +/* to_timestamp(double precision) + * Convert UNIX epoch to timestamptz. + */ +Datum +unixtime_timestamptz(PG_FUNCTION_ARGS) +{ + float8 seconds = PG_GETARG_FLOAT8(0); + TimestampTz result; + + int is_inf = is_infinite(seconds); + + static const float8 epoch_lbound = (float8)SECS_PER_DAY * -UNIX_EPOCH_JDATE; + static const float8 epoch_ubound = (float8)SECS_PER_DAY * + (TIMESTAMP_END_JULIAN - UNIX_EPOCH_JDATE); + + if(isnan(seconds)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for UNIX epoch \"%f\"", seconds), + errhint("Valid units for this parameter are finite floats, \"Infinity\" and \"-Infinity\""))); + + if (is_inf < 0) + TIMESTAMP_NOBEGIN(result); + + else if (is_inf > 0) + TIMESTAMP_NOEND(result); + + else + { + if (seconds < epoch_lbound) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("UNIX epoch out of range: \"%lf\"", seconds))); + + if (seconds >= epoch_ubound) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("UNIX epoch out of range: \"%lf\"", seconds))); + + /* Convert UNIX epoch to Postgres epoch */ + seconds -= ((POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY); + +#ifdef HAVE_INT64_TIMESTAMP + /* + * Here should be just "result = seconds * USECS_PER_SEC;", + * but for big values it leads loosing precision. + * Do as many operations as possible as integers. + */ + { + int64 seconds_int = (int64)seconds; + fsec_t msec = (seconds - seconds_int) * USECS_PER_SEC; + + result = (seconds_int * USECS_PER_SEC) + msec; + } +#else + result = seconds; +#endif + /* final range check catches just-out-of-range timestamps */ + if (!IS_VALID_TIMESTAMP(result)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("UNIX epoch out of range: \"%lf\"", + PG_GETARG_FLOAT8(0)))); + } + + PG_RETURN_TIMESTAMP(result); +} + /* * Try to parse a timezone specification, and return its timezone offset value * if it's acceptable. Otherwise, an error is thrown. diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index ceb8129..d7b94e8 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_ unixtime_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 fbead3a..e18211d 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 unixtime_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 fffcaf4..e3aab6a 100644 --- a/src/test/regress/expected/timestamptz.out +++ b/src/test/regress/expected/timestamptz.out @@ -2256,6 +2256,54 @@ 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: UNIX epoch out of range +ERROR: UNIX epoch out of range: "-100000000000000000000.000000" +SELECT to_timestamp(-210866803200.0625); -- Error: UNIX epoch out of range +ERROR: UNIX epoch out of range: "-210866803200.062500" +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: UNIX epoch out of range +ERROR: UNIX epoch out of range: "185331707078400.000000" +-- 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: invalid value for UNIX epoch "nan" +HINT: Valid units for this parameter are finite floats, "Infinity" and "-Infinity" 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 03dbc05..71c1657 100644 --- a/src/test/regress/sql/timestamptz.sql +++ b/src/test/regress/sql/timestamptz.sql @@ -386,6 +386,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: UNIX epoch out of range +SELECT to_timestamp(-210866803200.0625); -- Error: UNIX epoch 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: UNIX epoch 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;