Rails/Postgres query all records created after 5 PM in taking into account daylight savings switch? - sql

I am looking to query for all records on a table with a created_at time of after 5 PM, EST WITH taking into account daylight savings.
My DB is Postgres and all timestamps are stored in UTC as a normal Rails app.
So, say I have four records
ID, created_at, time in EST (-5 offset from UTC),
1, "2017-01-01 22:46:21.829333", 5:46 PM
2, "2017-01-01 21:23:27.259393", 4:23 PM
-------- DST SWITCH -----
ID, created_at, time in EDT (-4 offset from UTC),
3, "2017-03-20 21:52:46.135713", 5:52 PM
4, "2017-06-21 20:08:53.034377", 4:08 PM
My query should return records 1 and 3, but should ignore 2 and 4.
I have tried
SELECT "id",
"created_at"
FROM "orders"
WHERE (created_at::TIME WITHOUT TIME ZONE AT TIME ZONE 'utc' AT TIME ZONE 'US/Eastern' > ("created_at"::DATE + TIME '21:00')::TIME AT TIME ZONE 'utc' AT TIME ZONE 'US/Eastern')
ORDER BY "orders"."created_at" ASC
But this query will return record 1,2,3 when it should not return 2.

Sorry about my previous wrong answer. I don't do much with USA time.
all timestamps are stored in UTC
So ("created_at"::DATE + TIME '21:00')::TIME AT TIME ZONE 'utc' AT TIME ZONE 'US/Eastern' actually says it is utc time 21:00, which does not necessarily guarantee it is 5 PM in EST because the timezone offset changes from 5 to 4. So the compare condition is not correct.
You need to get the local time to make the comparison, rather than the utc time.
Try following code:
SELECT
"id",
"created_at"
FROM "orders"
WHERE (created_at AT TIME ZONE 'utc' AT TIME ZONE 'US/Eastern') :: TIME > TIME '5:00 PM'

Related

Combining date and UTC time fields in Postgres

I have 2 separate fields for date and time. The time field is stored in UTC time. How can I combine the 2 into a datetime field into local time
Example:
date: 2021-03-08
time in UTC: 23:00
time zone: GMT+8
I would like to get 2021-03-08 07:00 in local time
or even 2021-03-07 23:00 in UTC
Note: Combining the fields is not an option unfortunately.
Need to know your time zone to convert. To convert utc to america/los_angeles time zone:
select '2021-03-08 23:00'::timestamp at time zone 'UTC' at time zone 'america/los_angeles'
you can check out below codes:
If you have a timestamp without time zone column and you're storing timestamps as UTC, you need to tell PostgreSQL that, and then tell it to convert it to your local time zone.
select created_at at time zone 'utc' at time zone 'america/los_angeles'
from users;
To be more concise, you can also use the abbreviation for the time zone:
select created_at at time zone 'utc' at time zone 'pst'
from users;
To see the list of time zones PostgreSQL supports:
select * from pg_timezone_names;
SInce the time difference is 8 hours, try
SELECT '2021-03-08'::date + ('23:00'::time + '8 hours'::interval);
If you want this to work with arbitrary time zones, the query becomes more complicated:
SELECT '2021-03-08'::date
+ ((current_date + '23:00'::time)
AT TIME ZONE 'UTC'
AT TIME ZONE 'Asia/Ulaanbaatar'
)::time;

Group by time with timezone conversion in Postgresql

I am working with time data that is currently stores in UTC but I want it to be in PST, which is 8 hours behind. I have a pretty lengthy and involved query, but the only thing I am interested in is the time right now so I have included those parts. I want to convert the times to PST and then group by the date for the last week of data. The query has the following structure:
select
date_trunc('day', time1) AT TIME ZONE 'US/Pacific'
...
where
time1 AT TIME ZONE 'US/Pacific' > now() AT TIME ZONE current_setting('TimeZone') - INTERVAL '168 HOURS'
...
group by date_trunc('day', time1)
This results in the following time groupings. From my understanding, it groups from the 0:00 UTC, which is 16:00 in PST. However, I want the groupby to start at 0:00 PST. How do I do this? Right now, the counts in each group are misleading for each day because they go from 4 pm to 4 pm instead of 12 am to 12 am. For example, Sundays have uncharacteristically high counts because Sunday includes part of Monday's data in the groupby. I would appreciate any input to fix this issue. Thank you.
The answer depends on whether it is a timestamp with time zone or one without:
If it's a timestamp with time zone, you can convert to PST with select time1 AT TIME ZONE 'US/Pacific' and get the date with select date_trunc('day', time1 AT TIME ZONE 'US/Pacific')
If it's a timestamp without time zone stored in UTC that you want to convert, you first have to tell PostgreSQL to interpret it as UTC, then convert it, like so: select (time1 AT TIME ZONE 'Z') AT TIME ZONE 'US/Pacific' and of course you can get the date with select date_trunc('day', (time1 AT TIME ZONE 'Z') AT TIME ZONE 'US/Pacific')
In either case you have to convert time zones before truncating to the day level or you may end up with inaccurate results.

SQL Server Timezone in where clause

I am storing datetime field with UTC time. We have a requirement to filter the records with CST timezone.
I have tried this query:
select id, CreatedOn,
CreatedOn AT TIME ZONE 'UTC' AT TIME ZONE 'Central Standard Time' AS LocalTime
from Status
WHERE CAST((CreatedOn AT TIME ZONE 'Central Standard Time') AS date) = '2018-09-06'
order by CreatedOn desc;
The issue is that it is also bringing those records which were saved on September 5th CST time in the evening when the UTC time was changed to 6th September. What is the correct way to filter out only September 6th records in CST time?
I found an issue with the query, I was missing 'UTC' AT TIME ZONE in the where clause. Here is the correct query which works:
select id, CreatedOn,
       CreatedOn AT TIME ZONE 'UTC' AT TIME ZONE 'Central Standard Time' AS LocalTime from CosmoStatus WHERE CAST((CreatedOn AT TIME ZONE
'UTC' AT TIME ZONE 'Central Standard Time') AS date) = '2018-09-06'
order by CreatedOn desc;

Accounting for timezone in postgres query

In am trying to automatically account for timezone in my postgres query. My goal is to select records that are between 0:00 and 23:59 in EST or UTC - 5:00.
The current query only returns records between 0:00 and 23:59 in UTC time.
select path, start_time from routes where routes.network_id = 1 and routes.start_time between '2017-06-13 00:00:00'::timestamp AND '2017-06-13 23:59:59'::timestamp
the column start_time is a timestamp without timezone, so by default it is in UTC
SELECT pg_typeof("start_time") from routes limit 1;
returns timestamp without timezone
how would one write a query to account for 5 hours difference and convert start_time to UTC - 5?
Try this:
select path, start_time - interval '5 hours' as start_time_est
from routes
where routes.network_id = 1
and routes.start_time between '2017-06-13 00:00:00-5'::timestamp with time zone
AND '2017-06-13 23:59:59-5'::timestamp with time zone;

Query datetime as text and include different timezones

Short version:
I have 2 users in DB, each created in a different timezone:
User.find(1).created_at
=> Thu, 04 Aug 2016 11:15:29 IDT +03:00
User.find(33).created_at
=> Sun, 01 Jan 2017 17:50:20 IST +02:00
So my table shows 11:15, and 17:50. so for example I would like to search for 17:50, and later 11:15 as text:
search_param = '17:50'
No problem I'll just convert the date to text, then search it, But user won't be found, since the it's saved as UTC:
User.where("to_char(created_at,'DD-Mon-YYYY HH24:MI:SS') ilike ?", "%#{search_param}%").first
=> nil
To find it I'll just apply the offset to my query (adding time zone UTC+2), and indeed user was found:
User.where("to_char(created_at AT TIME ZONE 'UTC+2','DD-Mon-YYYY HH24:MI:SS') ilike ?", "%#{search_param}%").first
=> User #33 #2017-01-01 17:50:20 +0200
BUT some users are saved as UTC+3 and some as UTC+2.. I can't apply both offsets... So if I change search_param to be 11:15 I won't find user_id_1 because I will also need to change UTC+2 to UTC+3
When I use UTC+2 I will only find users which were created as +2 (like User.find(33)).
When I use UTC+3 I will only find users which were created as +3 (like User.find(1)).
My question: How to do a where query- a text search for both users' created_at hour, as they were both saved in a different timezone offset?
Or in this example a query that for a search_param 17:50 will find user_id_33 and for search_param 11:15 will find user_id_1?
More Details:
I notice in DB they are saved as UTC (I think):
User.select("created_at as created_at_db").find(33).created_at_db
=> "2017-01-01 15:50:20.903289"
User.select("created_at as created_at_db").find(1).created_at_db
=> "2016-08-04 08:15:29.171776"
Time setting:
#application.rb
config.time_zone = 'Jerusalem'
config.active_record.default_timezone = :utc
created_at column info:
User.columns.select{|table_col| table_col.name == 'created_at'}
=> [#<ActiveRecord::ConnectionAdapters::PostgreSQLColumn:0x81f9c48
#coder=nil,
#default=nil,
#limit=nil,
#name="created_at",
#null=false,
#precision=nil,
#primary=false,
#scale=nil,
#sql_type="timestamp without time zone",
#type=:datetime>]
You should be able to query your user entities with:
date_trunc('minute', created_at AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Jerusalem')::time = '17:50'
Or, with to_char() (but less index-friendly):
to_char(created_at AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Jerusalem', 'HH24:MI') = '17:50'
The point is that created_at is timestamp (or timestamp without time zone, which is just an alias). First, you can tell PostgreSQL, that interpret its values as they were in UTC, with the <timestamp> AT TIME ZONE <time zone> operator:
created_at AT TIME ZONE 'UTC'
Then, tell PostgreSQL to offset this value as if it were a local time in Asia/Jerusalem with the <timestamp with time zone> AT TIME ZONE <time zone> operator (which is completely different from the operator above with the same name):
created_at AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Jerusalem'
Now, you just need to truncate this value to extract only the hour & minute part.
Maybe worth mentioning that using these:
created_at AT TIME ZONE 'UTC+2'
created_at AT TIME ZONE 'UTC+3'
worked for you accidentally. These create timestamp with time zone values within the -2 and -3 UTC offsets, respectively. Then, because your config.active_record.default_timezone is set to :utc, it is sent to your client (ruby) within the UTC time zone, which means it added 2 and 3 hours, respectively.
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.