INTERVAL vs TIMESTAMP (or similar) - postgresql

I am wondering: Do I have any advantages when using INTERVAL instead of using to TIMESTAMP attributes from and to?
I am asking because I am having a table time_period that is supposed to be function as an abstract way of storing time interval information on a day and/or hour level:
CREATE TABLE time_period (
-- PRIMARY KEY
id BIGSERIAL PRIMARY KEY,
-- ATTRIBUTES
valid_for_days INT NOT NULL,
day_from TIMESTAMP WITH TIME ZONE NOT NULL,
day_to TIMESTAMP WITH TIME ZONE NOT NULL,
time_from TIMESTAMP WITH TIME ZONE NOT NULL,
time_to TIMESTAMP WITH TIME ZONE NOT NULL
);
Would I be better off using INTERVAL here?

The difference between two timestamps is an interval:
select now() - '2015-01-01';
?column?
--------------------------
370 days 18:45:20.312403
You only need two timestamps to have an interval not four.
If you store that as an interval you will lose the timestamp reference.

Related

Postgres: grouping by date for a specific time zone

Let us say we have a Postgres table:
CREATE TABLE public.observations (
id integer NOT NULL,
utc_created_at timestamp without time zone NOT NULL
)
If we are interested in counting the observations made at a specific UTC date, we could:
SELECT COUNT(observations.id),
DATE(utc_created_at) AS date_utc_created_at
GROUP BY DATE(date_utc_created_at);
But now let us say that I would like to group observations by Pacific Standard Time dates. Is there a way without altering the schema ?
If there is no way and I have now instead:
CREATE TABLE public.observations (
id integer NOT NULL,
utc_created_at timestamp with time zone NOT NULL
)
can I make the described query ?
Would the given solution also work with EXTRACT to say group by Pacific Standard Time year/month ?
Since the timestamp is a timestamp without time zone in UTC, you need to:
Tell Postgres what time zone to interpret the time stamp in
Tell Postgres what time zone you want it in.
After having done that, you can reliably and reproducibly convert to a string in whatever format desired.
To see what I mean, try running:
select
utc_created_at as original_timestamp
--correct conversion to PST
, (utc_created_at at time zone'Z') at time zone 'PST' as timestamp_in_pst
, date( (utc_created_at at time zone'Z') at time zone 'PST' ) as date_from_pst
--will correctly convert it to a timestamp with timezone, but Postgres will consider your session timezone in conversions
, utc_created_at at time zone'Z' as timestamp_with_timezone_displayed_in_session_time_zone
, date(utc_created_at at time zone'Z') as date_depends_on_session
--incorrect, will interpret it as if the original timestamp is in PST
, utc_created_at at time zone'PST' as timestamp_considered_with_PST_incorrect
, date( utc_created_at at time zone 'PST') as incorrect_date
from public.observations
You know that, for example, if it's 00:01 on Jan 1 UTC, it should actually be December 31 of the previous year in PST, which you can see in this fiddle: http://sqlfiddle.com/#!17/e6d3c/10

Properly handle TIME WITH TIME ZONE in PostgreSQL

We have a table that is filled with data from a legacy report of another system. The columns of that table reflect the same structure of the report.
Here are a abbreviated structure of the table:
CREATE TABLE IF NOT EXISTS LEGACY_TABLE (
REPORT_DATE DATE NOT NULL,
EVENT_ID BIGINT PRIMARY KEY NOT NULL,
START_HOUR TIMESTAMP WITHOUT TIME ZONE,
END_HOUR TIME WITHOUT TIME ZONE,
EXPECTED_HOUR TIME WITHOUT TIME ZONE
);
We are refactoring this table to deal with different time zones of different clients. The new structure would be something like:
CREATE TABLE IF NOT EXISTS LEGACY_TABLE (
REPORT_DATE DATE NOT NULL,
EVENT_ID BIGINT PRIMARY KEY NOT NULL,
START_HOUR TIMESTAMP WITH TIME ZONE,
END_HOUR TIME WITH TIME ZONE,
EXPECTED_HOUR TIME WITH TIME ZONE
);
These hour fields represents a specific point in time during the day represented by the REPORT_DATE column. What I mean by that is that every TIME column represents a moment during the day specified in REPORT_DATE.
Some other points to consider:
We don't know why the START_HOUR is in TIMESTAMP format in the report we receive from the legacy system. But we import the data the way it comes to us.
The fields in the report are formatted according to the timezone of the client, so to refactor this table we need to combine the timezone of the client (we have this info) to properly insert the timestamps/times in UTC.
But now to the problem. The value of these columns are used to compute another values multiple times in our system, something like the following:
START_HOUR - END_HOUR (the result of this operation is currently being casted to TIME WITHOUT TIME ZONE)
START_HOUR < END_HOUR
START_HOUR + EXPECTED_HOUR
EXPECTED_HOUR - END_HOUR
EXPECTED_HOUR < '05:00'
After some research I found that is not recommended to use the type TIME WITH TIME ZONE (Postgres time with time zone equality) and now I'm a bit confused about what is the best way to refactor this table to deal with different time zones and handle the different column operations that we need to.
Besides that, I already know that is safe to subtract two columns of type TIMESTAMP WITH TIME ZONE. This subtraction operation is taking into account DST changes (Subtracting two columns of type timestamp with time zone) but how about the others? And the one subtracting a TIME from a TIMESTAMP?.
And about the table refactoring, should we use TIME WITH TIME ZONE anyways? Should we continue using TIME WITHOUT TIME ZONE? Or is better to forget the type TIME altogether and combine the DATE with the TIME and change the columns to TIMESTAMP WITH TIME ZONE?
I think these questions are related because the new column types we choose to use, will define how we operate with the columns.
You asserted that:
every TIME column represents a moment during the day specified in REPORT_DATE.
So you never cross the a dateline within the same row. I suggest to save 1x date 3x time and the time zone (as text or FK column):
CREATE TABLE legacy_table (
event_id bigint PRIMARY KEY NOT NULL
, report_date date NOT NULL
, start_hour time
, end_hour time
, expected_hour time
, tz text -- time zone
);
Like you already found, timetz (time with time zone) should generally be avoided. It cannot deal with DST rules properly (daylight saving time).
So basically what you already had. Just drop the date component from start_hour, that's dead freight. Cast timestamp to time to cut off the date. Like: (timestamp '2018-03-25 1:00:00')::time
tz can be any string accepted by the AT TIME ZONE construct, but to deal with different time zones reliably, it's best to use time zone names exclusively. Any name you find in the system catalog pg_timezone_names.
To optimize storage, you could collect allowed time zone names in a small lookup table and replace tz text with tz_id int REFERENCES my_tz_table.
Two example rows with and without DST:
INSERT INTO legacy_table VALUES
(1, '2018-03-25', '1:00', '3:00', '2:00', 'Europe/Vienna') -- sadly, with DST
, (2, '2018-03-25', '1:00', '3:00', '2:00', 'Europe/Moscow'); -- Russians got rid of DST
For representation purposes or calculations you can do things like:
SELECT (report_date + start_hour) AT TIME ZONE tz AT TIME ZONE 'UTC' AS start_utc
, (report_date + end_hour) AT TIME ZONE tz AT TIME ZONE 'UTC' AS end_utc
, (report_date + expected_hour) AT TIME ZONE tz AT TIME ZONE 'UTC' AS expected_utc
-- START_HOUR - END_HOUR
, (report_date + start_hour) AT TIME ZONE tz
- (report_date + end_hour) AT TIME ZONE tz AS start_minus_end
FROM legacy_table;
You might create one or more views to readily display strings as needed. The table is for storing the information you need.
Note the parentheses! Else the operator + would bind before AT TIME ZONE due to operator precedence.
And behold the results:
db<>fiddle here
Since the time is manipulated in Vienna (like any place where silly DST rules apply), you get "surprising" results.
Related:
Accounting for DST in Postgres, when selecting scheduled items
Ignoring time zones altogether in Rails and PostgreSQL

Postgresql Create Table Fieldname Timestamp with Time Zone UTC

What is the syntax for creating a table with a field with the UTC time zone?
I have for the fields:
(id INT PRIMARY KEY NOT NULL,
bravo timestamp without time zone DEFAULT now(),
charlie timestamp with time zone UTC DEFAULT now()
)
This last field named charlie is not taking for some reason. I was hoping it was easy as just telling it had a time zone, then shoving UTC in there, and having the db figure out now() during input.
I think you want this:
charlie timestamp without time zone NOT NULL
DEFAULT (current_timestamp AT TIME ZONE 'UTC')

Postgres timestamp to unix time in milliseconds as a bigint

How can I get the following snippet to work in postgres:
ALTER TABLE mytable
ADD COLUMN create_time_utc bigint not null
DEFAULT (now() at time zone 'utc');
I want the new column create_time_utc to be the unix time in milliseconds (i.e number of milliseconds since Unix epoch January 1 1970).
I know I need to convert the postgres timestamp to a bigint, but I'm not sure how to do that.
extract(epoch
alter table mytable
add column create_time_utc bigint not null
default (extract(epoch from now()) * 1000);
http://www.postgresql.org/docs/current/static/functions-datetime.html#FUNCTIONS-DATETIME-EXTRACT

Using current time in UTC as default value in PostgreSQL

I have a column of the TIMESTAMP WITHOUT TIME ZONE type and would like to have that default to the current time in UTC. Getting the current time in UTC is easy:
postgres=# select now() at time zone 'utc';
timezone
----------------------------
2013-05-17 12:52:51.337466
(1 row)
As is using the current timestamp for a column:
postgres=# create temporary table test(id int, ts timestamp without time zone default current_timestamp);
CREATE TABLE
postgres=# insert into test values (1) returning ts;
ts
----------------------------
2013-05-17 14:54:33.072725
(1 row)
But that uses local time. Trying to force that to UTC results in a syntax error:
postgres=# create temporary table test(id int, ts timestamp without time zone default now() at time zone 'utc');
ERROR: syntax error at or near "at"
LINE 1: ...int, ts timestamp without time zone default now() at time zo...
A function is not even needed. Just put parentheses around the default expression:
create temporary table test(
id int,
ts timestamp without time zone default (now() at time zone 'utc')
);
Still another solution:
timezone('utc', now())
Wrap it in a function:
create function now_utc() returns timestamp as $$
select now() at time zone 'utc';
$$ language sql;
create temporary table test(
id int,
ts timestamp without time zone default now_utc()
);
What about
now()::timestamp
If your other timestamp are without time zone then this cast will yield the matching type "timestamp without time zone" for the current time.
I would like to read what others think about that option, though. I still don't trust in my understanding of this "with/without" time zone stuff.
EDIT:
Adding Michael Ekoka's comment here because it clarifies an important point:
Caveat. The question is about generating default timestamp in UTC for
a timestamp column that happens to not store the time zone (perhaps
because there's no need to store the time zone if you know that all
your timestamps share the same). What your solution does is to
generate a local timestamp (which for most people will not necessarily
be set to UTC) and store it as a naive timestamp (one that does not
specify its time zone).
These are 2 equivalent solutions:
(in the following code, you should substitute 'UTC' for zone and now() for timestamp)
timestamp AT TIME ZONE zone - SQL-standard-conforming
timezone(zone, timestamp) - arguably more readable
The function timezone(zone, timestamp) is equivalent to the SQL-conforming construct timestamp AT TIME ZONE zone.
Explanation:
zone can be specified either as a text string (e.g., 'UTC') or as an interval (e.g., INTERVAL '-08:00') - here is a list of all available time zones
timestamp can be any value of type timestamp
now() returns a value of type timestamp (just what we need) with your database's default time zone attached (e.g. 2018-11-11T12:07:22.3+05:00).
timezone('UTC', now()) turns our current time (of type timestamp with time zone) into the timezonless equivalent in UTC.
E.g., SELECT timestamp with time zone '2020-03-16 15:00:00-05' AT TIME ZONE 'UTC' will return 2020-03-16T20:00:00Z.
Docs: timezone()
Function already exists:
timezone('UTC'::text, now())