How to select timestamptz with offset with AT TIME ZONE - sql

I've fiddeling around with timestamps in PostgreSQL, reading a interesting Blog about this behavior, but have no clue to solve following problem:
How to select a 'timestamp with time zone' column with AT TIME ZONE from another column, including the offset to UTC in one step?
I've a sample table in PostgreSQL 10:
CREATE TABLE public.test_tz
(
id serial,
in_zone_timestamp timestamp without time zone,
in_zone character varying COLLATE pg_catalog."default",
CONSTRAINT test_tz_pkey PRIMARY KEY (id)
)
WITH (
OIDS = FALSE
)
TABLESPACE pg_default;
PostgreSQL's setting is
timezone=localtime
and the server runs at 'Europe/Berlin'
$ date --> Fri Mar 9 12:15:23 CET 2018
The timestamp column should store with this messy syntax:
INSERT INTO public.test_tz ( in_zone_timestamp, in_zone) VALUES
('2017-02-01 00:00:00' AT TIME ZONE current_setting('TIMEZONE') AT TIME ZONE 'Asia/Singapore', 'Asia/Singapore' );
If someone knows a better way, please don't hold back! The other solution is to use '2017-02-01 00:00:00-08' as the value, but I don't know the offset value.
I want to store the local timestamp like: 'The user hit a key at 2017-02-01 00:00:00 in Singapore'.
If I ask the database: Which time it was here in Europe, when the user in Singapore is hitting a key, I got:
SELECT in_zone_timestamp FROM public.test_tz;
--> '2017-01-31 17:00:00+01'
This seems OK, because Singapore has a offset of +8 hours.
If I want to know, which time it was in Singapore, I use:
SELECT in_zone_timestamp AT TIME ZONE in_zone FROM public.test_tz;
--> '2017-02-01 00:00:00'
That's OK too, but however, it doesn't return the offset to 'UTC', so I can't see that this timestamp is not in my local time!
I try some combinations of AT TIME ZONE or converting to timestamptz, but the results are not what I want. I expected a result like:
--> '2017-02-01 00:00:00+08'
At here, I only see one solution, to manually concat/convert/manipulate the result and add the offset by hand, but is this the only way?
Sorry if I explain this question a little bit too comlicated and hope someone can follow my thoughts.
Thanks in advance

The only way to have PostgreSQL convert a timestamp to a string like 2017-02-01 00:00:00+08 automatically is to change the session time zone:
SET timezone = 'Asia/Singapore';
You can use SET LOCAL to change the setting only for the duration of a transaction.
If you don't want that, you can get the offset with a query like this:
SELECT TIMESTAMP '2000-01-01 00:00:00' AT TIME ZONE 'UTC'
- TIMESTAMP '2000-01-01 00:00:00' AT TIME ZONE 'Asia/Singapore';
?column?
----------
08:00:00
(1 row)

Thanks for clarify, but in my opinion that PostgreSQL has a few shortcomings on this topic.
A 'not so perfect' solution for this problem is really to manually concat the parts needed.
SELECT (test_tz.in_zone_timestamp AT TIME ZONE in_zone) || (SELECT abbrev FROM pg_timezone_names WHERE name = in_zone) FROM public.test_tz;
--> 2017-02-01 00:00:00+08
This provides an acceptable solution, with small imperfections.
In some cases there is the timezone short text in the abbrev-column, like 'CET' or 'PDT' and not the +/- numeric value. This produces results (eg. for 'Europe/Berlin') like:
--> 2017-02-01 02:00:00CET
A better value gives the column 'utc_offset' of pg-timezone-names, but this requires a more complex text manipulation, I don't want at this point.
Second solution can be the manipulation of the output by application and formatting the text to whatever you need.
Hope that helps others to solve such problem without searching the internet for a not available support from the database.
Bye

Related

Convert UTC? timezone to EST in Oracle on select

Our company's Oracle server is hosted in the east coast of the US and I believe follows a default timezone of EST. I have a stored procedure which logs messages but puts the current timestamp into a field declared as timestamp.
describe log_messages;
Name Null? Type
--------- -------- --------------
ENTRY_ID NOT NULL NUMBER
SEVERITY VARCHAR2(1)
DATE_TIME TIMESTAMP(6)
MESSAGE VARCHAR2(2048)
Usually, I do something like
insert into log_messages(severity,date_time,message)
values('I',current_timestamp,'some message');
If I do select * from log_messages, the time stamps look like this:
28-MAY-20 01.50.15.747963000 AM
However, the above time is actually 4 hours later than my current timezone.
select entry_id,severity,cast(date_time as timestamp with time zone) as date_time, date_time AT TIME ZONE 'EST' AS est,message
from log_messages
order by date_time desc;
In the above, it thinks the timestamp in the timestamp field is in EST time, and then when it converts the other, it subtracts 4 hours from it. Effectively, it is as though it writes the time in UTC, but then reads it in thinking it is EST, and subtracts 4 hours from it. How can I get the real time in my local EST time?
CURRENT_TIMESTAMP returns the current date and time in your session time zone, i.e. location or time zone settings of your Oracle Database server is not relevant.
You can interrogate your session time zone with select SESSIONTIMEZONE from dual; and change it with ALTER SESSION SET TIME_ZONE=...
However, as the data type is TIMESTAMP(6), the time zone information is lost as soon as the data is inserted.
When you run cast(date_time as timestamp with time zone) then Oracle takes your SESSIONTIMEZONE for conversion, actually Oracle runs cast(FROM_TZ(date_time, SESSIONTIMEZONE) as timestamp with time zone)
Unless you don't alter your session time zone, the two queries should return the same value..
See also How to handle Day Light Saving in Oracle database

How do I prevent Redshift INSERT datetime from dropping the timezone?

I have a String in this format: 2018-11-01T00:00:00-07:00 and I would like to convert it to a TIMESTAMP and insert it into a TIMESTAMP column. However, when I insert it, it drops the -07:00 without first converting it to -00:00. How do I ensure that it is converted and stored in Redshift properly?
Here is an example:
select ORIGINAL_DATE, TO_TIMESTAMP(ORIGINAL_DATE,'YYYY-MM-DD HH24:MI:SS') FROM CDW_LANDING.X where id = XXXXXX;
=> 2018-11-01T00:00:00-07:00 2018-10-31 17:00:00
The TO_TIMESTAMP converts it to 2018-10-31 17:00:00 which is what I want. However, when I insert it, it becomes 2018-11-01 00:00:00 and simply drops the -07:00.
Here is the example:
insert into cdw_stage.X (ORIG_DT)
select TO_TIMESTAMP(ORIGINAL_DATE,'YYYY-MM-DD HH24:MI:SS')
from CDW_LANDING.INVOICE where id = XXXXXX;
But when I query it with select ORIG_DT from cdw_landing.X;, it displays 2018-11-01 00:00:00. What I would like to see is 2018-10-31 17:00:00 which is what the TO_TIMESTAMP function should do.
The ORIG_DT in Redshift is in TIMESTAMP format. The input date is in VARCHAR.
How do I get Redshift to save this correctly? I also added postgres tag because Redshift is based off of postgres. Thank you so much!!!
2018-11-01T00:00:00-07:00 is not a timestamp (timestamp without time zone) literal, strictly speaking. It is a timestamptz (timestamp with time zone) literal. This is the root of all pain in your question. The wrong cast to timestamp ignores the offset. The Postgres manual:
In a literal that has been determined to be timestamp without time zone, PostgreSQL will silently ignore any time zone indication. That
is, the resulting value is derived from the date/time fields in the
input value, and is not adjusted for time zone.
Bold emphasis mine.
The use of TO_TIMESTAMP() can't save you. The Redshift manual:
Formats that include a time zone (TZ, tz, or OF) are not supported as input.
(The same is true in Postgres.)
Solution
Cast to timestamptz (or use a column of that type to begin with), the rest should fall in place:
SELECT cast('2018-11-01T00:00:00-07:00' AS timestamptz);
Or:
SELECT '2018-11-01T00:00:00-07:00'::timestamptz;
The manual about casting in Redshift.
When an actual timestamptz is assigned to a timestamp column it is converted according to the current timezone setting of the session automatically. If you want a different target timezone, use the AT TIME ZONE construct. Details:
Ignoring time zones altogether in Rails and PostgreSQL
The related answer is for Postgres, but timestamp handling in Redshift (while differing in many other aspects!) is the same. The Redshift manual:
When converting DATE or TIMESTAMP to TIMESTAMPTZ, DATE or TIMESTAMP
are assumed to use the current session time zone. The session time
zone is UTC by default. For more information about setting the session
time zone, see timezone.

Selecting where timestamp greater than with timezone

Suppose I have this table structure:
Table "test"
Column | Type | Modifiers
----------------+-----------------------------+-----------
uuid | uuid | not null
created | timestamp without time zone | not null
How would I select records after a certain date? But also factor in a specific timezone?
Since created is timestamp without zone, we can assume it's UTC
Example query:
select uuid from test where created >= '2017-07-20'
This would return all events that happend on, or after 2017-07-20 00:00:00.000 UTC How would I query for events that happend after say, 2017-07-20 00:00:00.000 GMT+2 ? Without having to add hours to my argument in created > arg
select uuid
from test
where created > '2017-07-20'::timestamp with time zone at time zone '+02';
I think one approach would be to compare the number of seconds since the epoch between your UTC timestamp in the table and some other input:
select uuid
from test
where
extract(epoch from created) >
extract(epoch from timestamp with time zone
'2017-07-19 23:59:59.999'::timestamp with time zone at time zone 'GMT')
The above syntax is verbose, and there may be a shorter way of writing this query. As a general rule, when you find yourself jumping through hoops to answer simple queries, it might mean you need to change your design. If you were storing your timestamps in UTC and running queries from an application, you would probably be passing in local timestamps already converted to UTC, which would make things much simpler.

Insert time with timezone daylight savings

I would like to insert time data type in postgresql that includes the timezone and is aware of daylight savings time. This is what I have done:
CREATE TABLE mytable(
...
start_time time(0) with time zone,
end_time time(0) with time zone
)
INSERT INTO mytable(start_time, end_time)
VALUES(TIME '08:00:00 MST7MDT', TIME '18:00:00 MST7MDT')
I get the following error:
invalid input syntax for type time: "08:00:00 MST7MDT"
It works if I use 'MST' instead of 'MST7MDT', but I need it to be aware of DST. I also tried using 'America/Edmonton' as the timezone, but I got the same error.
What is the proper way to insert a time value (not timestamp) with timezone and DST?
EDIT:
I would actually like to use the 'America/Edmonton' syntax
The proper way is not to use time with time zone (note the space between time and zone) at all, since it is broken by design. It is in the SQL standard, so Postgres supports the type - but advises not to use it. More in this related answer:
Accounting for DST in Postgres, when selecting scheduled items
Since you are having problems with DST, timetz (short name) is a particularly bad choice. It is ill-equipped to deal with DST. It's impossible to tell whether 8:00:00 is in winter or summer time.
Use timestamp with time zone (timstamptz) instead. You can always discard the date part. Simply use start_time::time to get the local time from a timestamptz. Or use AT TIME ZONE to transpose to your time zone.
Generally, to take DST into account automatically, use a time zone name instead of a time zone abbreviation. More explanation in this related question & answer:
Time zone names with identical properties yield different result when applied to timestamp
In your particular case, you could probably use America/Los_Angeles (example with timestamptz):
INSERT INTO mytable(start_time, end_time)
VALUES
('1970-01-01 08:00:00 America/Los_Angeles'
, '1970-01-01 18:00:00 America/Los_Angeles')
I found this by checking:
SELECT * FROM pg_timezone_names
WHERE utc_offset = '-07:00'
AND is_dst;
Basics about time zone handling:
Ignoring time zones altogether in Rails and PostgreSQL
How about this?
INSERT INTO mytable(start_time, end_time)
VALUES('08:00:00'::time at time zone 'MST7MDT', '18:00:00'::time at time zone 'MST7MDT')

in postgres, can you set the default formatting for a timestamp, by session or globally?

In Postgres, is it possible to change the default format mask for a timestamp?
right now comes back as
2012-01-03 20:27:53.611489
I would like resolution to minute like this:
2012-01-03 20:27
I know I can do this on individual columns with to_char() as or stripped down with a substr() by the receiving app, but having it formatted correctly initially would save a lot of work and reduce a lot of errors.
In PostgreSQL, The formatting of timestamps is independent of storage. One answer is to use to_char and format the timestamp to whatever format you need at the moment you need it, like this:
select to_char(current_timestamp, 'yyyy-MM-dd HH24:MI:SS.MS');
select to_timestamp('2012-10-11 12:13:14.123',
'yyyy-MM-dd HH24:MI:SS.MS')::timestamp;
But if you must set the default formatting:
Change the postgresql timestamp format globally:
Take a look at your timezone, run this as an sql query:
show timezone
Result: "US/Eastern"
So when you are printing out current_timestamp, you see this:
select current_timestamp
Result: 2012-10-23 20:58:35.422282-04
The -04 at the end is your time zone relative to UTC. You can change your timezone with:
set timezone = 'US/Pacific'
Then:
select current_timestamp
Result: 2012-10-23 18:00:38.773296-07
So notice the -07 there, that means we Pacific is 7 hours away from UTC. How do I make that unsightly timezone go away? One way is just to make a table, it defaults to a timestamp without timezone:
CREATE TABLE worse_than_fail_table
(
mykey INT unique not null,
fail_date TIMESTAMP not null
);
Then if you add a timestamp to that table and select from it
select fail_date from worse_than_fail_table
Result: 2012-10-23 21:09:39.335146
yay, no timezone on the end. But you want more control over how the timestamp shows up by default! You could do something like this:
CREATE TABLE moo (
key int PRIMARY KEY,
boo text NOT NULL DEFAULT TO_CHAR(CURRENT_TIMESTAMP,'YYYYMM')
);
It's a text field which gives you more control over how it shows up by default when you do a select somecolumns from sometable. Notice you can cast a string to timestamp:
select '2012-10-11 12:13:14.56789'::timestamp
Result: 2012-10-11 12:13:14.56789
You could cast a current_timestamp to timestamp which removes the timezone:
select current_timestamp::timestamp
Result: 2012-10-23 21:18:05.107047
You can get rid of the timezone like this:
select current_timestamp at time zone 'UTC'
Result: "2012-10-24 01:40:10.543251"
But if you really want the timezone back you can do this:
select current_timestamp::timestamp with time zone
Result: 2012-10-23 21:20:21.256478-04
You can yank out what you want with extract:
SELECT EXTRACT(HOUR FROM TIMESTAMP '2001-02-16 20:38:40');
Result: 20
And this monstrosity:
SELECT TIMESTAMP WITH TIME ZONE '2001-02-16 20:38:40-05' AT TIME ZONE 'EST';
Result: 2001-02-16 20:38:40
In postgres, you can change the default format mask for datetimes - using the set datestyle option; the available options can be found here (see 8.5.2. Date/Time Output).
Unfortunately, all the available options include the number of seconds - you will therefore need to reformat them either in the query or the application code (if applicable).
to_char() is used to create a string literal. If you want a different timestamp value, use date_trunc():
date_trunc('minute', now())
For converting literal input, use to_timestamp():
to_timestamp('2012-01-03 20:27:53.611489', 'YYYY-MM-DD HH24:MI')
This returns timestamptz. Cast to timestamp [without time zone] by appending ::timestamp (which assumes your current timezone setting), or with the AT TIME ZONE construct to define a time zone explicitly.
To my knowledge, there is no setting in PostgreSQL that would trim seconds from timestamp literals by default.