Why does Postgres not recognize my DST? - postgresql

In my postgresql.conf file, my default timezone is set to 'Europe/London'.
Since the DST will be switched off on 30th October, this means that now (4th October while I am writing) 'Europe/London' should correspond with '+01'.
I have a dummy table with a time column of timestamp with timezone type.
Postgres specs state that:
For timestamp with timezone, the internally stored value is always in UTC (Universal Coordinated Time, traditionally known as Greenwich Mean Time, GMT). An input value that has an explicit time zone specified is converted to UTC using the appropriate offset for that time zone. If no time zone is stated in the input string, then it is assumed to be in the time zone indicated by the system's TimeZone parameter, and is converted to UTC using the offset for the timezone zone.
As for the bold sentence, if I make this insert:
INSERT INTO dummy VALUES ('2016-12-25 12:00:00.000')
I expect it to be interpreted as a 'Europe/London' timezone, thus '2016-12-25 12:00:00.000+01'.
Therefore, when I will retrieve it, I expect to be showed that value (or, at least, the equivalent '2016-12-25 11:00:00.000+00').
Instead, if I do the query
SELECT * FROM dummy
I am returned this:
|time |
|timestamp with time zone|
--------------------------
|2016-12-25 12:00:00+00 |
I can't get the reason of this behaviour. Is the DST management of 'Europe/London' timezone wrong? Am I missing something?
If I switch to any other timezone, it always works as expected.

The timestamp you enter is interpreted in the Europe/London time zone.
On the 25th of December, London will be offset 0 hours from UTC, so noon will be at 12:00 UTC.
Time zone Europe/London is not the same as time zone +01, at least not all the time.

Related

PostgreSQL: Can't get timezones with DST to work properly

I'm trying to figure out how Postgres handles DST in combination with intervals. Specifically I want to allow my users to create events with recurring dates - e.g. everyday at 16:00 local time.
For what I'm doing I need to store the first date in the user's local time, and then add a number of days to it, without changing the time of day in the user's local time. I was hoping that timestamptz with a full timezone name (so it knows when to apply DST?) combined with simple 1 day intervals would do the job - but it fails at my simple example:
Germany uses CET (+1:00) and switches to CEST (+2:00) at 2:00 in the morning on March 28.
Tunesia uses CET all year.
Thus I expected, that using a timestamptz on March 27 and adding 1 day to it, I'd see a different utc-offset in Berlin, and no change in Tunis - but they both changed the offset equally, as if Tunis was using DST:
select
'2021-03-27 16:00:00 Africa/Tunis'::timestamptz as "tunis_before_dst",
'2021-03-27 16:00:00 Africa/Tunis'::timestamptz + INTERVAL '1 day' as "tunis_after_dst",
'2021-03-27 16:00:00 Europe/Berlin'::timestamptz as "berlin_before_dst",
'2021-03-27 16:00:00 Europe/Berlin'::timestamptz + INTERVAL '1 day' as "berlin_after_dst"
results in:
tunis_before_dst: '2021-03-27 16:00:00+01'
tunis_after_dst: '2021-03-28 16:00:00+02'
berlin_before_dst: '2021-03-27 16:00:00+01'
berlin_after_dst: '2021-03-28 16:00:00+02'
Looking through pg_timezone_names, I can see that my Postgres instance is aware of Africa/Tunis not having DST - so I'm wondering why it's changing the UTC offset for it then.
I guess it's obvious that timezones and DST are very confusing to me, but am I doing something wrong handling them or is timezonetz not what I think it is?
Rant first. The concept of DST is breathtaking nonsense. Even the name is obvious BS. "Daylight Saving Time". No daylight has been saved. I can't believe the EU still did not manage to get rid of it, even though the overwhelming majority wants it gone, and it's been consensus to scrap it for a while now.
With that out of my system, the primary misunderstanding is this: you assume that the data type timestamp with time zone would store time zone information. It does not. Becomes obvious here:
Thus I expected, [...] I'd see a different utc-offset in Berlin, and no change in
Tunis - but they both changed the offset equally
The time zone offset you see in the output is the offset determined by the current timezone setting of your session. Time zone serves as input / output modifier / decorator. Postgres always stores UTC time internally. And no time zone information whatsoever.
The type name is a bit deceiving there. It has been known to fool the best:
Time zone storage in data type "timestamp with time zone"
Once you've grasped that concept, the rest should become obvious.
To preserve the local time (wall clock time of day), use the data type timestamp without time zone (timestamp), or even just time (never use the broken timetz), and store time zone information additionally - ideally the time zone name ('Europe/Berlin' like you have it), not a time zone abbreviation or a numeric offset.
timestamp with time zone (timestamptz) is the right choice to store unique points in time, independent of any time zones. The time zone offset is just an input modifier. Both of the following literals result in the same timestamptz value exactly, because both time zones happen to apply the same offset at this time of the year:
'2021-03-27 16:00:00 Africa/Tunis'::timestamptz
'2021-03-27 16:00:00 Europe/Berlin'::timestamptz
But these differ by one hour, because the German offset has changed according to the local DST regime:
'2021-03-28 16:00:00 Africa/Tunis'::timestamptz
'2021-03-28 16:00:00 Europe/Berlin'::timestamptz
Related:
Ignoring time zones altogether in Rails and PostgreSQL
Preserve timezone in PostgreSQL timestamptz type

Postgresql date timezone issue

I have a pg database with a column type timestamp with time zone. I inserted the following date:
2016-08-01 00:00:00 GMT
However, in the database, it shows up as:
2016-07-31 20:00:00-04
Does anyone know what might be going on?
Thanks in advance!
Despite the name, TIMESTAMP WITH TIME ZONE doesn't actually store the time zone. It uses the session's time zone to normalize to UTC, and stores UTC. On retrieval it converts back from UTC to the session time zone.
You can change the session time zone by using the SET TIME ZONE command. Preferably, you should use the standard IANA time zone identifiers. For example:
SET TIME ZONE 'Europe/Paris'
or
SET TIME ZONE 'UTC'
Alternatively use the TIMESTAMP [WITHOUT TIME ZONE] type instead, which does no conversions.

Why do Postgres timezones change around 1967/68?

On 9.3.3, if one runs:
select
EXTRACT(TIMEZONE FROM timestamp with time zone '1911-03-01 00:00 -8:00:00'),
EXTRACT(TIMEZONE FROM timestamp with time zone '1911-05-15 00:00 -8:00:00'),
EXTRACT(TIMEZONE FROM timestamp with time zone '1917-03-01 00:00 -8:00:00'),
EXTRACT(TIMEZONE FROM timestamp with time zone '1917-05-15 00:00 -8:00:00'),
EXTRACT(TIMEZONE FROM timestamp with time zone '1967-03-01 00:00 -8:00:00'),
EXTRACT(TIMEZONE FROM timestamp with time zone '1967-05-15 00:00 -8:00:00'),
EXTRACT(TIMEZONE FROM timestamp with time zone '1968-03-01 00:00 -8:00:00'),
EXTRACT(TIMEZONE FROM timestamp with time zone '1968-05-15 00:00 -8:00:00');
One gets the following results:
0;0;
0;3600;
0;3600;
3600;3600
(The first time is the founding day of Las Vegas, the next few are some I used to debug the issue)
It seems there is no offset around 1911, an offset between 1911 and 1967 during summer but not winter and then always has one from 1968 onwards. This seems a little weird. Does anyone have any idea what is going on with the offsets here and whether this is expected behaviour or if there is something in my linux's setup that I could possibly change?
Time zones change for all sorts of reasons.
Daylight savings rules change.
Sometimes timezone offsets change, too, if nations redefine their time zone for political reasons.
The canonical time zone information database is the tz or "zoneinfo" database, which used to be called the Olsen database. The zoneinfo DB is on the IANA site. There are a variety of programs to dump human readable versions of the DB.
You can use timestamp without time zone if you wish to store a particular moment in wall-clock time, without concern for time zone.
timestamp with time zone is sensitive to the system TimeZone setting on input and output, and is stored in UTC time as absolute seconds. So it's converted for input and output. If you want different conversions or to override the conversion you can use the AT TIME ZONE operator.
The rules for your time zone are established by law, and the law changes.
If you want to store the INSTANT at which the (local) clocks in Las Vegas marked 00:00:00 in the day in which the city was founded, and assuming that Las Vegas was using an offset of -8 hours, then you should store 1911-03-01 00:00 -8:00:00::timezonetx in a timezonetx field. Be aware, however, that what is really stored is only the "universal instant", when you read it you cannot know to which "local time at Las Vegas" it corresponds (unless you explicityl convert it, after reading it, to a timezone).
Depends on your timezone, really. In 1966, DST was first implemented nationally in the US to take place in 1967. States needed to pass laws to end it, meaning that in many areas, 1967 is the only year which used DST. This can lead to some very interesting glitches, where every date except those in 1967/68 behaves normally.

PostgreSQL wrong converting from timestamp without time zone to timestamp with time zone

I faced with the following issue this morning:
select '2011-12-30 00:30:00'::timestamp without time zone AT TIME ZONE 'EST5EDT';
returns me 2011-12-30 05:30:00+00 witch is wrong.
But next queries below:
select '2011-12-30 00:30:00'::timestamp without time zone AT TIME ZONE 'UTC-5';
select '2011-12-30 00:30:00' AT TIME ZONE 'EST5EDT';
i see right date 2011-12-29 19:30:00
Preventing your question about my local timezone:
SELECT current_setting('TIMEZONE');
current_setting
-----------------
UTC
(1 row)
Do anyone have answer why postgresql converts timestamp without time zone some weird way and instead taking away 5 hours it adds instead?
Key things to understand
timestamp without time zone AT TIME ZONE re-interprets a timestamp as being in that time zone for the purpose of converting it to UTC.
timestamp with time zone AT TIME ZONE converts a timestamptz into a timestamp at the specified timezone.
PostgreSQL uses ISO-8601 timezones, which specify that east of Greenwich is positive ... unless you use a POSIX timezone specifier, in which case it follows POSIX. Insanity ensues.
Why the first one produces an unexpected result
Timestamps and timezones in SQL are horrible. This:
select '2011-12-30 00:30:00'::timestamp without time zone AT TIME ZONE 'EST5EDT';
inteprets the unknown-typed literal '2011-12-30 00:30:00' as timestamp without time zone, which Pg assumes is in the local TimeZone unless told otherwise. When you use AT TIME ZONE, it is (per the spec) re-interpreted as a timestamp with time zone in the time zone EST5EDT then stored as an absolute time in UTC - so it's converted from EST5EDT to UTC, i.e the timezone offset gets subtracted. x - (-5) is x + 5.
This timestamp, adjusted to UTC storage, is then adjusted for your server TimeZone setting for display so that it gets displayed in local time.
If you instead wish to say "I have this timestamp in UTC time, and wish to see what the equivalent local time in EST5EDT is", if you want to be independent of the server TimeZone setting, you need to write something like:
select TIMESTAMP '2011-12-30 00:30:00' AT TIME ZONE 'UTC'
AT TIME ZONE 'EST5EDT';
This says "Given timestamp 2011-12-30 00:30:00, treat it as a timestamp in UTC when converting to timestamptz, then convert that timestamptz to a local time in EST5EDT".
Horrible, isn't it? I want to give a firm talking to whoever decided on the crazy semantics of AT TIME ZONE - it should really be something like timestamp CONVERT FROM TIME ZONE '-5' and timestamptz CONVERT TO TIME ZONE '+5'. Also, timestamp with time zone should actually carry its timezone with it, not be stored in UTC and auto-converted to localtime.
Why the second works (so long as TimeZone = UTC)
Your original "works" version:
select '2011-12-30 00:30:00' AT TIME ZONE 'EST5EDT';
will only be correct if TimeZone is set to UTC, because the text-to-timestamptz cast assumes TimeZone when one isn't specified.
Why the third one works
Two problems cancel each other out.
The other version that appears to work is TimeZone independent, but it only works because two problems cancel themselves out. First, as explained above, timestamp without time zone AT TIME ZONE re-interprets the timestamp as being in that time zone for conversion to a UTC timestamptz; this effectively subtracts the timezone offset.
However, for reasons I beyond my ken, PostgreSQL uses timestamps with the reverse sign to what I'm used to seeing most places. See the documentation:
Another issue to keep in mind is that in POSIX time zone names, positive offsets are used for locations west of Greenwich. Everywhere else, PostgreSQL follows the ISO-8601 convention that positive timezone offsets are east of Greenwich.
This means that EST5EDT is the same as +5, not -5. Which is why it works: because you're subtracting the tz offset not adding it, but you're subtracting a negated offset!
What you'd need to get it correct is instead:
select TIMESTAMP '2011-12-30 00:30:00' AT TIME ZONE 'UTC'
AT TIME ZONE '+5';

PostgreSQL date() with timezone

I'm having an issue selecting dates properly from Postgres - they are being stored in UTC, but
not converting with the Date() function properly.
Converting the timestamp to a date gives me the wrong date if it's past 4pm PST.
2012-06-21 should be 2012-06-20 in this case.
The starts_at column datatype is timestamp without time zone. Here are my queries:
Without converting to PST timezone:
Select starts_at from schedules where id = 40;
starts_at
---------------------
2012-06-21 01:00:00
Converting gives this:
Select (starts_at at time zone 'pst') from schedules where id = 40;
timezone
------------------------
2012-06-21 02:00:00-07
But neither convert to the correct date in the timezone.
Basically what you want is:
$ select starts_at AT TIME ZONE 'UTC' AT TIME ZONE 'US/Pacific' from schedules where id = 40
I got the solution from this article is below, which is straight GOLD!!! It explains this non-trivial issue very clearly, give it a read if you wish to understand pstgrsql TZ management better.
Expressing PostgreSQL timestamps without zones in local time
Here is what is going on. First you should know that 'PST timezone is 8 hours behind UTC timezone so for instance Jan 1st 2014, 4:30 PM PST (Wed, 01 Jan 2014 16:00:30 -0800) is equivalent to Jan 2nd 2014, 00:30 AM UTC (Thu, 02 Jan 2014 00:00:30 +0000). Any time after 4:00pm in PST slips over to the next day, interpreted as UTC.
Also, as Erwin Brandstetter mentioned above, postresql has two type of timestamps data type, one with a timezone and one without.
If your timestamps include a timezone, then a simple:
$ select starts_at AT TIME ZONE 'US/Pacific' from schedules where id = 40
will work. However if your timestamp is timezoneless, executing the above command will not work, and you must FIRST convert your timezoneless timestamp to a timestamp with a timezone, namely a UTC timezone, and ONLY THEN convert it to your desired 'PST' or 'US/Pacific' (which are the same up to some daylight saving time issues. I think you should be fine with either).
Let me demonstrate with an example where I create a timezoneless timestamp. Let's assume for convenience that our local timezone is indeed 'PST' (if it weren't then it gets a tiny bit more complicated which is unnecessary for the purpose of this explanation).
Say I have:
$ select timestamp '2014-01-2 00:30:00' AS a, timestamp '2014-01-2 00:30:00' AT TIME ZONE 'UTC' AS b, timestamp '2014-01-2 00:30:00' AT TIME ZONE 'UTC' AT TIME ZONE 'PST' AS c, timestamp '2014-01-2 00:30:00' AT TIME ZONE 'PST' AS d
This will yield:
"a"=>"2014-01-02 00:30:00" (This is the timezoneless timestamp)
"b"=>"2014-01-02 00:30:00+00" (This is the UTC TZ timestamp, note that up to a timezone, it is equivalent to the timezoneless one)
"c"=>"2014-01-01 16:30:00" (This is the correct 'PST' TZ conversion of the UTC timezone, if you read the documentation postgresql will not print the actual TZ for this conversion)
"d"=>"2014-01-02 08:30:00+00"
The last timestamp is the reason for all the confusion regarding converting timezoneless timestamp from UTC to 'PST' in postgresql. When we write:
timestamp '2014-01-2 00:30:00' AT TIME ZONE 'PST' AS d
We are taking a timezoneless timestamp and try to convert it to 'PST TZ (we indirectly assume that postgresql will understand that we want it to convert the timestamp from a UTC TZ, but postresql has plans of its own!). In practice, what postgresql does is it takes the timezoneless timestamp ('2014-01-2 00:30:00) and treats it as if it WERE ALREADY a 'PST' TZ timestamp (i.e: 2014-01-2 00:30:00 -0800) and converts that to UTC timezone!!! So it actually pushes it 8 hours ahead instead of back! Thus we get (2014-01-02 08:30:00+00).
Anyway, this last (un-intuitive) behavior is the cause of all confusion. Read the article if you want a more thorough explanation, I actually got results which are a bit different then their on this last part, but the general idea is the same.
I don't see the exact type of starts_at in your question. You really should include this information, it is the key to the solution. I'll have to guess.
PostgreSQL always stores UTC time for the type timestamp with time zone internally. Input and output (display) are adjusted to the current timezone setting or to the given time zone. The effect of AT TIME ZONE also changes with the underlying data type. See:
Ignoring time zones altogether in Rails and PostgreSQL
If you extract a date from type timestamp [without time zone], you get the date for the current time zone. The day in the output will be the same as in the display of the timestamp value.
If you extract a date from type timestamp with time zone (timestamptz for short), the time zone offset is "applied" first. You still get the date for the current time zone, which agrees with the display of the timestamp. The same point in time translates to the next day in parts of Europe, when it is past 4 p.m. in California for instance. To get the date for a certain time zone, apply AT TIME ZONE first.
Therefore, what you describe at the top of the question contradicts your example.
Given that starts_at is a timestamp [without time zone] and the time on your server is set to the local time. Test with:
SELECT now();
Does it display the same time as a clock on your wall? If yes (and the db server is running with correct time), the timezone setting of your current session agrees with your local time zone. If no, you may want to visit the setting of timezone in your postgresql.conf or your client for the session. Details in the manual.
Be aware that the timezone offset used the opposite sign of what's displayed in timestamp literals. See:
Peculiar time zone handling in a Postgres database
To get your local date from starts_at just
SELECT starts_at::date
Tantamount to:
SELECT date(starts_at)
BTW, your local time is at UTC-7 right now, not UTC-8, because daylight savings time is in effect (not among the brighter ideas of the human race).
Pacific Standard TIME (PST) is normally 8 hours "earlier" (bigger timestamp value) than UTC (Universal Time Zone), but during daylight saving periods (like now) it can be 7 hours. That's why timestamptz is displayed as 2012-06-21 02:00:00-07 in your example. The construct AT TIME ZONE 'PST' takes daylight saving time into account. These two expressions yield different results (one in winter, one in summer) and may result in different dates when cast:
SELECT '2012-06-21 01:00:00'::timestamp AT TIME ZONE 'PST'
, '2012-12-21 01:00:00'::timestamp AT TIME ZONE 'PST'
I know this is an old one but You may want to consider using AT TIME ZONE "US/Pacific" when casting to avoid any PST/PDT issues. So
SELECT starts_at::TIMESTAMPTZ AT TIME ZONE "US/Pacific"
FROM schedules
WHERE ID = '40';