Postgres: get local timestamp with time zone of latest midnight - postgresql

I'm in the Time Zone Europe/Berlin (+02), Postgresql is running at UTC (+00).
I need to get the timestamp with time zone at the latest local midnight date (The start of day date of the current day in my time zone).
So my end result would be something like this if we have 2013-03-03 14:00:00+02
2013-03-03 22:00:00+00
2013-03-04 00:00:00+02 // the same
I tried to get this date with
SELECT TIMESTAMP 'today' AT TIME ZONE 'Europe/Berlin'
Unfortunately this yields the wrong date (the previous day midnight) during 00:00 and 02:00 as the UTC time is stil at the previous day and today seems to use utc to calculate the rest.
If we have 2013-03-03 00:05 at Europe/Berlin this will return
2013-05-01 22:00:00+00
If I want to have the correct date I need to use
SELECT date_trunc('day', now() AT TIME ZONE 'Europe/Berlin') AT TIME ZONE 'Europe/Berlin';
2013-05-02 22:00:00+00
which is correct, but quite ugly.
Is there a cleaner variant of this command?

Use timestamptz. The tz at the end meaning with time zone:
SELECT TIMESTAMPTZ 'today' AT TIME ZONE 'Europe/Berlin'
Or if you like it more explicit:
SELECT TIMESTAMP with time zone 'today' AT TIME ZONE 'Europe/Berlin'

Wrap it in a function:
create function midnight() returns timestamptz as $$
select date_trunc('day', now() AT TIME ZONE 'Europe/Berlin') AT TIME ZONE 'Europe/Berlin';
$$ language sql;

Based on Erwin's answer to a related question, this was the simplest and fastest way I figured out how to do it:
SELECT timezone('Europe/Berlin', now()::date::timestamp) AS local_midnight_in_utc;
The key is the cast to a timestamp, which removes the time zone from the date.
You can test your sample time of '2013-03-03 00:05' with this:
SELECT timezone('Europe/Berlin', '2013-03-03 00:05'::date::timestamp) AS midnight;
and it returns
2013-03-02 23:00:00+00
According to explain analyze, this is about 3x as fast as the datetrunc version. A runtime of .017ms vs 0.006ms based on a best of 5 runs.

Related

Why is at time zone works not as was expected?

I have a server that gives me some information grouped by day, so I tried to use date_trunc(), but because of the timezone I know that 2020-06-05 21:00:00 in my DB is actually 2020-06-06 00:00:00.
So if I just use date_trunc, I got 2020-06-05 00:00:00, but I need 2020-06-06 00:00:00.
I'm trying this:
select tm , date_trunc('day', (tm) at time zone '+3')
from scm.tbl
where (tm BETWEEN '2020-06-05 15:00:00+00:00:00' AND '2020-06-08 20:59:00+00:00:00')
order by tm
And I have this:
2020-06-05 17:59:59 | 2020-06-05 00:00:00
2020-06-05 18:00:10 | 2020-06-06 00:00:00
At 18:00 the date became 2020-06-06, but it shouldn't. Why does it? What am I doing wrong here?
The problem is that AT TIME ZONE will convert a timestamp without time zone into a timestamp with time zone, and that is again handled according to your current timezone setting.
So you need to use AT TIME ZONE twice, first to interpret the timestamp in the correct time zone, and then to extract what an UTC clock would show at that time:
SELECT tm, date_trunc('day', tm AT TIME ZONE '+3' AT TIME ZONE 'UTC')
FROM scm.tbl
WHERE (tm BETWEEN '2020-06-05 15:00:00+00:00:00' AND '2020-06-08 20:59:00+00:00:00')
ORDER BY tm;
I found my answer here timezone aware date_trunc function
When I saw this in the answer:
timestamp with time zone '2001-01-1 00:00:00+0100' at time zone '-02'
It's because tm is +03:00:00 and I'm adding it again...
So what I can do is:
date_trunc('day', tm at time zone '0')
I guess there's must be better way to do it, without '0'

Convert a UTC timezone in postgresql to EST (local time)

I am new to PostgreSQL and I was wondering if there is a direct way to just convert the timestamp values in a table to a different timezone using a function. In my case it is UTC to EST.
These are the values for example that I need to convert to EST (not just one value but all the values in the table)
date
-------------------
2015-10-24 16:38:46
2016-01-19 18:27:00
2016-01-24 16:14:34
2016-02-09 23:05:49
2016-02-11 20:46:26
Here in London, we are currently 1 hour ahead of UTC. So - if I take your timezone without timestamp and say it is in UTC I will get it printed for my local timezone.
richardh=> SELECT ((timestamp '2015-10-24 16:38:46') AT TIME ZONE 'UTC');
timezone
------------------------
2015-10-24 17:38:46+01
(1 row)
But you want "EST" which seems to be somewhere in the Americas, judging by the value returned. You can wrap the expression in a little SQL function if you wanted to.
richardh=> SELECT ((timestamp '2015-10-24 16:38:46') AT TIME ZONE 'UTC') AT TIME ZONE 'EST';
timezone
---------------------
2015-10-24 11:38:46
(1 row)
Edit: how to do it in a query
SELECT ((stored_timestamp AT TIME ZONE 'UTC') AT TIME ZONE 'EST') AS local_timestamp
FROM my_table;
Similarly
execute
SELECT '2015-10-24 16:38:46'::timestamp AT time zone 'EST';
timezone
------------------------
2015-10-24 21:38:46+00
(1 row)
I usually leave everything in UTC and convert when it is time to show.
I use something like:
SELECT my_date_utc AT time zone 'utc' at time zone 'est' From ....
If you have problem accessing with your zone, you can simply pass your zone interval also.
To convert timestamp from IST to UTC.
SELECT '2020-12-14 06:38:46'::timestamp AT time zone INTERVAL '+05:30';
timezone
------------------------
2015-10-24 11:38:46+00
(1 row)
To convert timestamp from UTC to IST.
SELECT '2020-12-14 06:38:46'::timestamp AT time zone INTERVAL '-05:30';
timezone
------------------------
2020-12-14 12:08:46+00
(1 row)
It is 12:22 here in Los Angeles now.
I find that I have to reverse the UST and america/los_angeles arguments:
ods=> SELECT NOW(),(NOW() AT TIME ZONE 'america/los_angeles') AT TIME ZONE 'utc';;
now | timezone
-------------------------------+-------------------------------
2022-04-22 19:22:35.943605+00 | 2022-04-22 12:22:35.943605+00
(1 row)
Am I missing something?
You should always store the main reference of a date in UTC and either convert it to a time zone in your queries or store the specific timezone version of the data in another column. The reason for this is that it is quick and easy to convert a date from UTC to another time zone as long as you know that the timezone that it is stored as is UTC. It takes the guess work out of it. Alternatively, you can store the date WITH the timezone.
If you have an operation that automatically populates the date with the system clock of your server, then you can either
A: Change the operation to use UTC time
B: Change the system clock on the server to UTC
I had the same problem, I am working with different regions and timezones, I need to just fix the timezone in the query the way it doesn't effect other customers around the regions and I havent changed the table structure or any thing(Open–closed principle) . What I did In my query:
SELECT TO_CHAR(current_timestamp at time zone 'Australia/Melbourne', 'DD/MM/YYYY hh24:mi AM') as date_of_extract
This worked for me and I could change the 'UTC' defult timezone for my postgressql to the 'Australia/Melbourne'(any time zone you are looking into). hope this is helpful.
Building off of #Leandro Castro's answer...
To get current time in in timezone, use the CURRENT_TIME function:
SELECT CURRENT_TIME(0) AT time zone 'utc' at time zone 'est';

Why does Postgres discard timezone information with 'AT TIME ZONE'

I have a postgres database with TIMESTAMP WITH TIME ZONE values. The values could have any arbitrary time zone. I now want to use the date_trunc function on those but in a specific timezone. I though I could use the AT TIME ZONE function to normalize all timestamps to one timezone, but as result I get a TIMESTAMP without time zone information.
e.g:
select now(), now() AT TIME ZONE 'Europe/London';
now | timezone
-------------------------------+----------------------------
2015-08-12 12:21:55.337973+02 | 2015-08-12 11:21:55.337973
What I want is to get the result with the time zone information attached to it like 2015-08-12 11:21:55.337973+01 but I can't seem to find a command for this.
My current solution is to use AT TIME ZONE``, then using date_trunc on theTIMESTAMPwithout timezone andAT TIME ZONE``` again afterwards to re-attach the time zone information. However, it seems ugly to use it in that way:
SELECT
date_trunc('day', '2015-08-01 00:00:00+02'::timestamptz),
date_trunc('day', '2015-08-01 00:00:00+02'::timestamptz AT TIME ZONE 'Europe/London')
AT TIME ZONE 'Europe/London';
date_trunc | timezone
------------------------+------------------------
2015-08-01 00:00:00+02 | 2015-07-31 01:00:00+02
My Questions would be:
Is there a postgresql command that converts a timestamp from one timezone to another without discarding the time zone information
Why does postgres behave in this way? Is there a reason fir discarding the time zone information?
Thank you for your help.

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()

Now() without timezone

I have a column added_at of type timestamp without time zone. I want it's default value to be the current date-time but without time zone. The function now() returns a timezone as well.
How do I solve that problem?
SELECT now()::timestamp;
The cast converts the timestamptz returned by now() to the corresponding timestamp in your time zone - defined by the timezone setting of the session. That's also how the standard SQL function LOCALTIMESTAMP is implemented in Postgres.
If you don't operate in multiple time zones, that works just fine. Else switch to timestamptz for added_at. The difference?
Ignoring time zones altogether in Rails and PostgreSQL
BTW, this does exactly the same, just more noisy and expensive:
SELECT now() AT TIME ZONE current_setting('timezone');
Well you can do something like:
SELECT now() AT TIME ZONE current_setting('TimeZone');
SELECT now() AT TIME ZONE 'Europe/Paris';
SELECT now() AT TIME ZONE 'UTC';
Not sure how that makes any sense for a column "added_at". You almost always want an absolute timestamp (timestamp with time zone) not a floating one.
Edit responding to points below:
Yes, should use timestamp with time zone (absolute time) unless you have a good reason not to.
The client timezone is given by SHOW TimeZone or current_setting(...) as shown above.
Do take some time to skim the manuals - they cover all this quite well.
"Current Date/Time":
CURRENT_TIME and CURRENT_TIMESTAMP deliver values with time zone; LOCALTIME and LOCALTIMESTAMP deliver values without time zone.
New, and Native Answer in 2020
In PostgreSQL, If you only want the current date-time by calling CURRENT_TIMESTAMP() without time zone, and fractional digits in the seconds field which come after the decimal point of the seconds field?
(Tested on PostgreSQL v12.4)
Then use this:
SELECT CURRENT_TIMESTAMP(0)::TIMESTAMP WITHOUT TIME ZONE;
If you define your column's data type as timestamp (not as timestamptz), then you can store the timestamp without time zone, in that case you don't neet to add TIMESTAMP WITHOUT TIME ZONE
Like this:
CREATE TABLE foo (created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP(0))
In the above function, 0 is passed to get rid of the fractional digits in the seconds field.
If your application doesn't care about timezone, you can use SELECT LOCALTIMESTAMP for it.
Ex:
SELECT LOCALTIMESTAMP
-- Result: 2023-01-30 17:43:33.628952