简体   繁体   中英

TimeZone and DST in Rails and PostgreSQL

Background

Article model with default created_at column

Rails config.time_zone = 'Warsaw'

I've got an article with created_at = local time 2012-08-19 00:15 (2012-08-18 22:15 in UTC).

Goal

To receive all articles created in 2012-08-19 (in local time).

My (not working properly) solution

Article.where(
  "date_trunc('day', created_at AT TIME ZONE '#{Time.zone.formatted_offset}')
   = '#{Date.civil(2012, 8, 19)}'"
)

Which generates SQL:

SELECT "articles".* FROM "articles"
WHERE (date_trunc('day', created_at AT TIME ZONE '+01:00') = '2012-08-19')

And returns an empty set. But if I run the same query in psql it returns an article ... which confuses me.

Question

What am I doing wrong and how to fix it?

Goal: To receive all articles created in 2012-08-19 (in local time).

'+01:00' (like you use it) is a fixed time offset and cannot take DST ( Daylight Saving Time ) into account. Use a time zone name for that (not an abbreviation). These are available in PostgreSQL:

SELECT * FROM pg_timezone_names;

For Warsaw this should be 'Europe/Warsaw' . The system knows the bounds for DST from it's stored information and applies the according time offset.


Also, your query can be simplified.

As created_at is a timestamp [without time zone] , the values saved reflect the local time of the server when the row was created (saved internally as UTC timestamp).

There are basically only two possibilities, depending on the time zone(s) of your client.

  1. Your reading client runs with the same setting for timezone as the writing client: Just cast to date.

     SELECT * FROM articles WHERE created_at::date = '2012-08-19'; 
  2. Your reading client runs with a different setting for timezone than the writing client: Add AT TIME ZONE '<tz name of *writing* client here> '. For instance, if that was Europe/Warsaw , it would look like:

     ... WHERE (created_at AT TIME ZONE 'Europe/Warsaw')::date = '2012-08-19'; 

The double application of AT TIME ZONE like you have it in your posted answer should not be necessary.

Note how I use a time zone name here, instead of the abbreviation. There is a subtle difference which the first draft of the answer did not take into account. More explanation at this closely related question .

If you span multiple time zones with your application ..

.. set the column default of created_at to now() AT TIME ZONE 'UTC' - or any other time zone. The point is: use the same everywhere.

.. or switch to timestamp with time zone .

Linked answer helped. I have to run following query:

SELECT *
FROM   articles
WHERE (created_at AT TIME ZONE 'UTC' AT TIME ZONE 'CEST')::date = '2012-08-19';

This question would need the exact definition of the column created_at (what data type exactly?)

Rails always creates created_at column as timestamp without time zone. So I have to make the first AT TIME ZONE 'UTC' to say dbms that this timestamp is at UTC, and the second one to display date at CEST zone.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM