rails: timestamp schema migration on postgres doesn't include timezone
I’ve come to a very interesting problem that we could do better to prevent others from having the same issue, related to how rails create “timestamp” fields on PostgreSQL.
Standard migrations will generate a “timestamp without timezone” field, which should be fine for any non database vendor specific use-cases (rails simple queries).
The problem is that if you want to use timezone conversions like “AT TIME ZONE” on postgres, you get a very confusing behavior:
For timestamp with time zone, 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.
Conversions between timestamp without time zone and timestamp with time zone normally assume that the timestamp without time zone value should be taken or given as timezone local time. A different time zone can be specified for the conversion using AT TIME ZONE.
See: 8.5.1.3. Time Stamps at http://www.postgresql.org/docs/9.3/static/datatype-datetime.html
This is the expected behavior of AT TIME ZONE: (it will do the timezone calculations to convert any timestamp data to the one specified).
show timezone;
TimeZone
----------
UTC
SELECT NOW(), NOW() at time zone 'America/Sao_Paulo';
now | timezone
------------------------------+---------------------------
2015-08-04 17:18:32.34449+00 | 2015-08-04 14:18:32.34449
FYI: America/Sao_Paulo (BRT) is UTC-3.
Querying a recently created model for their “created_at” field, and a version using “AT TIME ZONE” I get:
SELECT created_at, created_at AT TIME ZONE 'America/Sao_Paulo' AS created_at_timezone FROM "mytable" ORDER BY id DESC LIMIT 1;
id | created_at | created_at_timezone
----------+----------------------------+-------------------------------
36597068 | 2015-08-04 17:26:37.551733 | 2015-08-04 20:26:37.551733+00
Instead of -3, what I’m observing is a +3. This is because “created_at” doesn’t include the timezone information, and therefore, according to PostgreSQL documentation, it’s using server machine timezone for the calculation.
I can’t have access to that server (heroku postgres instance), but I suspect it’s using CST (which is UTC-6).
I can define the timezone using a subquery like the following example:
SELECT t.created_at, t.created_at AT TIME ZONE 'America/Sao_Paulo' AS created_at_timezone FROM (SELECT created_at AT TIME ZONE 'UTC' as created_at FROM "mytable" ORDER BY ID desc) as t LIMIT 1;
created_at | created_at_timezone
-------------------------------+----------------------------
2015-08-04 17:26:37.551733+00 | 2015-08-04 14:26:37.551733
There is currently a workaround to create database timestamp with timezone using migrations DSL: http://stackoverflow.com/questions/19207856/railshow-to-create-a-time-column-with-timezone-on-postgres
What I propose is that we create a flag for whether you want to create a timestamp “with timezone or not”, or default it to create with timezone, as there is no bad side-effects
About this issue
- Original URL
- State: closed
- Created 9 years ago
- Comments: 15 (8 by maintainers)
Commits related to this issue
- Active Record + PostgreSQL: native support for `timestamp with time zone` In https://github.com/rails/rails/issues/21126 it was suggested to make "timestamp with time zone" the default type for datet... — committed to ghiculescu/rails by ghiculescu 3 years ago
- Active Record + PostgreSQL: native support for `timestamp with time zone` In https://github.com/rails/rails/issues/21126 it was suggested to make "timestamp with time zone" the default type for datet... — committed to hexdevs/rails by ghiculescu 3 years ago
We are facing this problem in the company I work.
We are using Metabase (http://www.metabase.com) for reports and charts, directly connected to the rails database.
Since
timestamp without zone
does not store any information about time zone at all, other-than-rails clients are not able to convert the timestamps to another time zone. Actually, they don’t even know what time zone is being used. They are “forced” to assume that the timestamps are already using the desired time zone, which causes problems if you are using any other-than-UTC timezone.From Metabase docs:
In this example, setting the session time zone in a PostgreSQL connection doesn’t affect
timestamp without zone
columns:created_at
istimestamp without zone
updated_at
istimestamp with zone
Note the beautiful
+00
and-04
onupdated_at
column.As a solution, we are planning on modifying all
datetime
columns totimestamp with zone
and also change active record defaultdatetime
type for PostgreSQL:Result example:
Extracted from http://nandovieira.com/working-with-dates-on-ruby-on-rails
We are running those changes on a test server and we haven’t noticed any problem so far.
EDIT: We’ve executed this two weeks ago, everything working properly, no issues so far.
As a followup to this, I think the default behavior of Rails is somewhat incompatible with most other tools. An “out of the box” datetime (
timestamp without zone
written as UTC) field in Rails will not be parsed correctly in a at least a few other postgres clients I’ve seen. The rubypg
gem doesn’t parse it correctly, thesequel
gem doesn’t, and nodejspg
module does not. And when I say it “doesn’t parse it correctly”, I mean Rails writes a UTC timestamp, and the other postgres clients are setup to always parse it as a local timestamp. I’m not sure which is correct, but it doesn’t seem ideal to create data that doesn’t play nice with other clients. I’m sure there’s a good reason for it, I just ran into this years after having it in place when we wanted to read Rails-created data from a nodejs app.This behavior is the same as mysql database using
datetime
which also ignores time zone. Default options look like this:datetime
datetime
(no)timestamp without time zone
(no)timestamp
timestamp
(yes)timestamp without time zone
(no)timestamptz
timestamp with time zone
(yes)If I understand it correctly, mysql’s
timestamp
(and thus rails’) is in violation of ansi sql. Andtimestamps
definition should usetimestamptz
(or whatever general equivalent is) by default instead ofdatetime
if aiming for better interoperability with non-rails db access.I’m running into this as well. I’d vote for Rails defaulting timestamps to ‘timestamp with time zone’ – especially for the created_at and updated_at fields – I can’t see any downsides.