How to convert local timestamp (of a given time zone) to UTC? - postgresql

I am working in a code where multiple time zones will be available. I need to create a function which takes the selected local timestamp as well as the local time zone. The return value of the function should the UTC time of the local time (local time for the given local time zone).
I am working on PostgreSQL 8.3

SELECT Now() AT TIME ZONE 'UTC';
This will return timestamp without time zone at specified time zone (UTC in my example) for given timestamp with time zone.
From PostgreSQL docs:
SELECT TIMESTAMP '2001-02-16 20:38:40' AT TIME ZONE 'MST';
Result: 2001-02-16 19:38:40-08
SELECT TIMESTAMP WITH TIME ZONE '2001-02-16 20:38:40-05' AT TIME ZONE 'MST';
Result: 2001-02-16 18:38:40
You can get list of time zones from pg_timezone_names:
select * from pg_timezone_names limit 5
name;abbrev;utc_offset;is_dst
----------------------
PRC;CST;08:00:00;f
Asia/Brunei;BNT;08:00:00;f
Asia/Ujung_Pandang;CIT;08:00:00;f
Asia/Ust-Nera;VLAT;11:00:00;f
Asia/Phnom_Penh;ICT;07:00:00;f

Related

Postgresql select output date with missing timezone offset

I've got a hard time with postgresql and timezones.
The Postgresql server is in UTC time zone.
I want to perform a query that returns the date in another timezone with the timezone offset.
I can perform a query that returns the date in a timezone but I'm missing the timezone offset
base=# SET timezone to 'utc';
SET
base=# select now();
now
------------------------------
2018-04-20 14:58:22.68038+00
(1 row)
base=# SET timezone to 'Europe/Paris';
SET
base=# select now();
now
-------------------------------
2018-04-20 16:58:29.614383+02
(1 row)
base=# SET timezone to 'utc';
SET
base=# select now() AT TIME ZONE 'Europe/Paris';
timezone
----------------------------
2018-04-20 16:59:03.146917 -- missing timezone offset here
(1 row)
Expected result
base=# SET timezone to 'utc';
SET
base=# select now() AT TIME ZONE 'Europe/Paris'; --I'm missing something here, I guess
timezone
----------------------------
2018-04-20 16:59:03.146917+02 -- That's what I want
(1 row)
Do you have any idea how to do it?
Thanks
If you use AT TIME ZONE, PG returns a timestamp without time zone. If you cast it to back to a timestamp with time zone, you get your expected result:
SELECT (NOW() AT TIME ZONE 'Europe/Paris')::TIMESTAMPTZ;
Result:
2018-04-20 18:15:26.165+02
How do you get the +02 without setting the timezone? Here's the problem: NOW() AT TIME ZONE 'Europe/Paris' returns the current time in the specified time zone (without an offset), and casting it back to TIMESTAMPTZ gives you the offset of the current time zone (of your PG session) based on UTC. Since 'Europe/Paris' is +02 from UTC, if you've set your time zone to 'Europe/Paris', the offset you get in a TIMESTAMPTZ is +02. If your current time zone setting is UTC, your offset is +00.
How to get the offset no matter what your PG session's time zone setting, without explicitly setting the time zone? I would like to think there's a better way to get the offset, but without knowing one, here's one way: calculate the offset, format it, append it to the timestamp without time zone.
SELECT (NOW() AT TIME ZONE 'Europe/Paris')::TEXT || TO_CHAR(EXTRACT(hour FROM NOW() AT TIME ZONE 'Europe/Paris' - NOW() AT TIME ZONE 'UTC'), 'FMSG00')
Result: 2018-04-21 15:12:42.658+02, and I get the same result no matter the current timezone.
Some timezone have an offset in 30 or 45 minutes.
Using TO_CHAR(EXTRACT(hour FROM NOW() AT TIME ZONE 'Europe/Paris' - NOW() AT TIME ZONE 'UTC') exclude this use case.
Did you consider to use SET LOCAL TIME ZONE 'Europe/Paris'; in a dedicated function ? According to the documentation :
If SET LOCAL is used within a function that has a SET option for the same variable (see CREATE FUNCTION), the effects of the SET LOCAL command disappear at function exit
This other reply seem's fitting your requirement : How to convert timestamp field to ISO 8601 string in a given time zone?

What is difference between 4 ways convert timezone in postgresql

I don't know What is difference between 4 ways convert timezone in postgresql:
SELECT (timestamp '2018-01-20 00:00:00' at time zone 'Asia/Saigon') at time zone 'UTC';
SELECT CAST('2018-01-20 00:00:00' as timestamp without time zone) at time zone 'Asia/Saigon' at time zone 'UTC'
SELECT (TO_TIMESTAMP('2018-01-20 00:00:00', 'YYYY-MM-DD HH24:MI:SS') at time zone 'Asia/Saigon') at time zone 'UTC'
SELECT ('2018-01-20 00:00:00' at time zone 'Asia/Saigon') at time zone 'UTC';
The results are different. Why?
The first two statements do the same thing.
The difference is the way in which a constant of type timestamp without time zone is created, but the result is the same in both cases.
The third statement creates a timestamp with time zone using to_timestamp, where the string is interpreted in your session time zone. This is then converted to a timestamp without time zone as the wall clock in Saigon would show, and then converted to a timestamp with time zone imagining the wall clock were teleported to UTC.
The fourth statement does the same as the third, because the string is implicitly cast to timestamp with time zone. There is an ambiguity here because AT TIME ZONE can also be applied to timestamp without time zone, but in case of doubt the preferred type of its category is used, which is timestamp with time zone.
The SQL standard differentiates timestamp without time zone and timestamp with time zone literals by the presence of a "+" or "-" symbol and time zone offset after the time. Hence, according to the standard
Also you can see below articles:
Section 8.5.1.3. Time Stamps
Time zone

How do I insert a universally coordinated time into a PostgreSQL TIMESTAMP column?

My database and client are currently in America/New_York, but could be moved anywhere.
SHOW TIMEZONE; -- America/New_York
CREATE TABLE times (
t TIMESTAMP WITHOUT TIME ZONE
);
INSERT INTO times VALUES
(NOW()),
(NOW() AT TIME ZONE 'UTC');
SELECT t FROM times;
-- 2017-06-13 14:53:17.766969
-- 2017-06-13 18:53:17.766969
This is unexpected. I thought my SELECT would return the same value for both records.
When I insert the current time into column t, I want it to mean the current time in any time zone (whether the database stores the underlying value as current time UTC or not). This way, regardless of the time zone the database is operating in, or the time zone the client is operating in, everybody can agree on the universally coordinated fixed point in time.
What is the proper way to INSERT a record so that everyone in the world knows I'm referencing a 'UTC' time?
First, it seems like SELECT assumes that timestamp without time zone fields are stored in the local time zone. Try SET TIME ZONE 'UTC+<n>. The output of SELECT does not change (i.e., it's not as if timestamp means UTC if no timezone is stored!)
When a field it of type timestamp with time zone, SELECT correctly converts the output to the current time zone.
So when you say AT TIME ZONE 'UTC' you convert the time to UTC, then discard the timezone making Postgres think you are dealing with local time.
You can re-introduce the UTC time zone by adding another AT TIME ZONE 'UTC' clause:
# select pg_typeof(now() at time zone 'utc');
timestamp without time zone
# select pg_typeof(now() at time zone 'utc' at time zone 'utc');
timestamp with time zone
Bottom line: I would either always use a TIME WITH TIME ZONE or always have the database time zone set to UTC if you insist on working without time zones. The storage size of both types seems identical (64 bit).

How do I specify “the start of today” in a specific time zone?

I have a table with a “timestamp with time zone” column. I would like to find all of the rows whose timestamp is earlier than today, where “today” is determined in a specific time zone.
I know how to use at time zone to interpret a literal timestamp as being in some particular time zone, and I know how to use date_trunc to get the beginning of this day. But I’m not sure how to combine them to get what I need. I tried
select date_trunc('day', current_date at time zone 'cst');
which gave me “2015-03-16 00:00:00”, but it’s unclear to me what time zone is used for this result (or whether it has one at all). How can I select the beginning of the current day according to a specific time zone?
It helped me to reframe the question as follows: what are the current date and time in the Central time zone? Then, what I want is the midnight at the beginning of that day [in the Central time zone]. I found that I could write this as
current_date::timestamp AT TIME ZONE 'cst'
This example may be useful to understand PostgreSQL timezone functionality:
SELECT now() as utc_now, -- 2021-08-29 11:38:08.552247 +00:00
pg_typeof(now()) as type_of_utc_now, -- timestamp with time zone
tehran_local_now, -- 2021-08-29 16:08:08.552247
pg_typeof(tehran_local_now), -- timestamp without time zone
utc_calculated_from_tehran_local_now, -- 2021-08-29 11:38:08.552247 +00:00
pg_typeof(utc_calculated_from_tehran_local_now), -- timestamp with time zone
tehran_local_start_of_today, -- 2021-08-29 00:00:00.000000
pg_typeof(tehran_local_start_of_today), -- timestamp without time zone
utc_start_of_today_at_tehran_timezone, -- 2021-08-28 19:30:00.000000 +00:00
pg_typeof(utc_start_of_today_at_tehran_timezone) -- timestamp with time zone
FROM (SELECT now() at time zone 'Asia/Tehran' as tehran_local_now) as tln
CROSS JOIN
(SELECT (now() at time zone 'Asia/Tehran') at time zone 'Asia/Tehran' as utc_calculated_from_tehran_local_now) as ucftln
CROSS JOIN
(SELECT date_trunc('day', now() at time zone 'Asia/Tehran') as tehran_local_start_of_today) as tlsot
CROSS JOIN
(SELECT date_trunc('day', now() at time zone 'Asia/Tehran') at time zone
'Asia/Tehran' as utc_start_of_today_at_tehran_timezone) as usotatt;
The PostgreSQL function now() (column utc_now) returns the current timestamp in UTC without any timezone. Its type is timestamp with time zone. Its Java equivalent is Instant.now().
When you use now() at time zone 'Asia/Tehran' (column tehran_local_now) it indicates the current date-time in Tehran local. Its type is timestamp without time zone. Its Java equivalent is:
Instant.now().atZone(ZoneId.of("Asia/Tehran")).toLocalDateTime()
When you use date_trunc('day', now() at time zone 'Asia/Tehran') (column tehran_local_start_of_today) it indicates the start of today in Tehran local. Its type is timestamp without time zone. Its Java equivalent is:
Instant.now().atZone(ZoneId.of("Asia/Tehran")).toLocalDate().atStartOfDay()
When you use date_trunc('day', now() at time zone 'Asia/Tehran') at time zone 'Asia/Tehran' (column utc_start_of_today_at_tehran_timezone) it indicates the start of today in Tehran timezone (not local). Its type is timestamp with time zone. You may need this one. Its Java equivalent is:
Instant.now().atZone(ZoneId.of("Asia/Tehran")).truncatedTo(DAYS).toInstant()

postgresql: timezone sensitive query on a timestamp without timezone

I have a table with a timestamp without time zone column (data entered is assumed to be in Australia/Sydney time zone).
Query on data for a time range (ie 8am-4pm) in America/New_York time zone.
Is there an easy way to achieve this?
thanks, p.
Figured it out.
You need to first convert the time to it's with time zone version ie my_ts at time zone 'Australia/Sydney' and then convert that to it's NY counterpart via at time zone 'America/New_York'
select
my_ts as "Default(syd)",
my_ts at time zone 'Australia/Sydney' as "SYD",
my_ts at time zone 'Australia/Sydney' at time zone 'America/New_York' as "NY",
date_part('hour', my_ts at time zone 'Australia/Sydney' at time zone 'America/New_York') as "NY-hr"
from my_table
where date_part('hour', my_ts at time zone 'Australia/Sydney' at time zone 'America/New_York')>=8
and date_part('hour', my_ts at time zone 'Australia/Sydney' at time zone 'America/New_York')<16
You can convert everything to the same time zone so you can compare them with (if the timezone was set):
select current_time, current_time at time zone 'gmt';
timetz | timezone
-------------------+-------------------
20:50:51.07742-07 | 03:50:51.07742+00
If the time zone is not set and you need to correct it some local time:
select now()::time, now()::time + '+8:00'::interval;
now | ?column?
-----------------+-----------------
20:57:49.420742 | 04:57:49.420742
Once you get the time the way you want, just the extract the hour and you can use a simple condition to select the proper times.
select *
from
(select extract(hour from now()::time + '+8:00'::interval) as hour) as t
where hour between 8 and 16;