timezone aware date_trunc function - postgresql

The following query
SELECT the_date FROM date_trunc('day', timestamp with time zone
'2001-01-1 00:00:00+0100') as the_date
results to
the_date
2000-12-31 00:00
Is there a way to tell date_trunc to do day/month/year conversions based on the timezone it is feeded with?
The expected output would be: 2001-01-1 00:00+0100

You need to specify at which time zone you want it to show
select
date_trunc(
'day',
timestamp with time zone '2001-01-1 00:00:00+0100' at time zone '-02'
) as the_date;
the_date
---------------------
2001-01-01 00:00:00
AT TIME ZONE

While the marked answer might be correct for the OP's weird circumstances it is more likely incorrect for others. You need to convert the timestamp returned by date_trunc to the proper timezone.
select
date_trunc(
'day',
some_timestamp at time zone users_timezone
) at time zone users_timezone as the_date;
The important thing to understand is date_trunc returns a timestamp with no timezone attached to it. You need to convert the timestamp to the proper timezone because the database client or whatever downstream might have a different timezone.

#Adam's answer is definitely more helpful. Although I think we can improve on it again because if we're truncating a Timestamp to a single day (or week/month/etc), then we want to make sure that we're dealing with a Date object, not a Timestamp. Otherwise we may give other pieces of code the impression that something just actually happened to occur at midnight (or potentially some other misleading time of day).
So I would use:
SELECT date_trunc('day', some_timestamp AT TIME ZONE users_timezone)::date AS the_date;
which casts the result to a Date, rather than Timestamp.
The result will be something like:
the_date
------------
2019-09-14
instead of the more misleading result of:
the_date
---------------------
2019-09-14 00:00:00

Related

Need help to identify the actual date time format

I have been struggling since few days to identify correct format for sample input date:
2020-01-30 14:39:25.022000 +00:00:00
Background :
I am working on data migration with postgres. And, have identified that in DB, the datetime related fields like 'modifiedAt' have datatype as varchar, which is bit weird. These records have values in above mentioned format.
I have fixed it in new postgres DB instance with datatype as timestamp. After migrating data, these fields are showing datetime in following format:
2020-01-30 14:39:25
How can I format above date to give output in this dateformat 2020-01-30 14:39:25.022000 +00:00:00?
Any help will be appreciated.
First you want to use timestamptz not timestamp. timestamptz does not actually store the time zone, it just makes the value time zone aware. For more information on this see:
Time Stamps 8.5.1.3. Time Stamps.
Second it looks like you did something like timestamp(0) which reduced the precision to whole seconds. If you leave the precision alone you get:
select '2020-01-30 14:39:25.022000 +00:00:00'::timestamptz;
timestamptz
-----------------------------
01/30/2020 06:39:25.022 PST
-- I am in PST so the value gets rotated to that time zone for display.
-- If you want something closer to you desired output, then:
select to_char('2020-01-30 14:39:25.022000 +00:00:00'::timestamptz AT TIME ZONE 'UTC', 'YYYY-MM-DD HH24:MI:SS.USOF');
to_char
-------------------------------
2020-01-30 14:39:25.022000+00
While your timestamp strings do appear to have a time zone component, it is +00, which is the default for timestamps. So, you might be able to just use TO_TIMESTAMP here with an appropriate format mask covering microseconds:
WITH yourTable AS (
SELECT '2020-01-30 14:39:25.022123 +00:00:00'::text AS ts
)
SELECT TO_TIMESTAMP(LEFT(ts, 26), 'YYYY-MM-DD HH24:MI:SS.US')
FROM yourTable;
Demo
The output value from the demo above is 2020-01-30 14:39:25.022123+00

date_trunc at time zone with original timestamptz

I have a single timestamptz that I want to date_trunc so it removes the hours:
2019-01-01T17:43-03 => 2019-01-01T00:00-03.
However, because date_trunc removes the timezone, I need to do it like this:
date_trunc('day', '2019-01-01T17:43-03'::timestamptz) at time zone '-03'
However, I do not want to hardcode the time zone, since the query is run with timestamptz in many different timezones (these are input to the query and not stored). So I want the timezone to be extracted from the original timestamp. I tried to do something like this, but it does not work:
date_trunc('day', '2019-01-01T17:43-03'::timestamptz) at time zone EXTRACT(...)
Related, I am trying to extract the timezone from a timestamptz, but just getting 0.
SELECT EXTRACT(timezone FROM TIMESTAMP WITH TIME ZONE '2019-01-01T00:00+03')
0
Can anybody help me with this?
I believe you may have a misconception about how timestamps and timezones are stored in PostgreSQL (because you seem to expect the -03 to be preserved when calling date_trunc and that you "want the timezone to be extracted from the original timestamp"). According to the documentation:
When a timestamp with time zone value is output, it is always converted from UTC to the current timezone zone, and displayed as local time in that zone. To see the time in another time zone, either change timezone or use the AT TIME ZONE construct (see Section 9.9.3).
Therefore, the statement that "different clients [that] have timestampts in many different timezones," while true, are all translated to UTC for storage, and then displayed in your local timezone (or the timezone you specify) for output. As such, calling date_trunc() will essentially truncate the UTC timestamp, and if you want it displayed in a specific timezone, you will need to add the AT TIME ZONE clause.
UPDATE: An example is here:
edb=# select date_trunc('day', '2019-01-01T17:43-03'::timestamptz) ;
date_trunc
------------------------
2019-01-01 00:00:00+00
(1 row)
edb=# set timezone to 'US/Pacific';
SET
edb=# select date_trunc('day', '2019-01-01T17:43-03'::timestamptz) ;
date_trunc
------------------------
2019-01-01 00:00:00-08
(1 row)
As you can see, date_trunc will append the timezone that I defineā€”it is not omitted.

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.

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

Postgres: get local timestamp with time zone of latest midnight

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.