Time.now.beginning_of_week giving two different values - ruby-on-rails

I am trying to get values from a table for a weeks data as below
Answer.where("ct_id = ? AND ot_id = ? AND created_at >= ?",16,72,Time.now.beginning_of_week)
It fires a query
Answer Load (0.3ms) SELECT `answers`.* FROM `answers` WHERE (ct_id = 16 AND ot_id = 72 AND created_at >= '2016-02-28 18:30:00')
time in the above query is '2016-02-28 18:30:00'
But in rails console the value is
Time.now.beginning_of_week
=> 2016-02-29 00:00:00 +0530
So which is the correct value and why is it giving two different value when i try it in console.
I know that Time.now.beginning_of_week starts the week from Sunday ie 18th ,but why is that when i do it in console its showing 19th monday.
Hope some one can clarify my doubt on it.

When you fire Time.now.beginning_of_week in console it gives time in you local timezone i.e +5.30 (Indian standard time).
All times stored in database are stored in utc.
Answer.where("ct_id = ? AND ot_id = ? AND created_at >= ?",16,72,Time.now.beginning_of_week)
When we use Time.now.beginning_of_week in above query, it will get converted to utc as we need to compare with record stored in db, which is in utc.
To convert below time 2016-02-29 00:00:00 +0530 into utc time, we have to subtract -5.30 from it as it is +5.30 from utc.
Time in utc = 2016-02-29 (00:00:00 - 5.30)
Time in utc = 2016-02-29 18.30
Update:
If you want to get beginning_of_week in utc. You can do following in rails 4. Not sure whether this works on rails 3.
Date.today.beginning_of_week.to_time(:utc) # o/p 2016-02-29 00:00:00 UTC

Related

UTC conversion is different between local and server

I am building a Rails 5 app.
In this app I have a query that checks if an Event is already in the database. This works perfectly locally but not on the server. The problem, as I see it is that I convert the date I want to check into UTC and then do the query. The strange thing is that the output of the conversion is different between local and server for the exact same date.
This is the query
scope :available_holidays, -> (date) { where("events.starts_at > ? AND events.starts_at < ? AND ttype = ?", date.beginning_of_day, date.end_of_day, TYPE_NATIONAL) }
This is how I convert to UTC
Time.parse(holiday[:date].to_s).utc
This is the output locally
2017-12-31 00:00:00
This is the output on the server
2018-01-01 00:00:00
This is my method in the controller
def holidays
output = []
from = Date.civil(params["year"].to_i,1,1)
to = Date.civil(params["year"].to_i,12,31)
holidays = Holidays.between(from, to, params["country"])
holidays.each do |holiday|
date = Time.parse(holiday[:date].to_s).utc
event = Event.available_holidays(date).count
if event == 0
output.push(holiday)
end
end
render json: output.to_json
end
What is going wrong?
I see a number of possible problems.
holidays.each do |holiday|
date = Time.parse(holiday[:date].to_s).utc
Why is Holidays.between returning hashes rather than objects?
Why is holiday[:date] a string and not a Date object?
Why is a date being parsed with Time.parse?
Why are time zones involved with a date?
The first two are structural problems. The second two I think are the real problems. Dates don't have time zones.
When you run, for example, Time.parse("2018-05-06") Ruby will return a Time for midnight at that date with a time zone. For example...
> Time.parse("2018-01-01")
=> 2018-01-01 00:00:00 -0800
> Time.parse("2018-01-01").utc
=> 2018-01-01 08:00:00 UTC
As a Date that's 2018-01-01 so no harm. But if you were in +0100...
> Time.parse("2018-01-01 00:00:00 +0100").utc
=> 2017-12-31 23:00:00 UTC
As a Date that's 2017-12-31.
Don't convert dates to times and back to dates. Just keep them as dates.
holidays.each do |holiday|
date = Date.parse(holiday[:date])
Better yet, have Holidays store holiday[:date] as a Date object.

Time in DB compared to current time

I have a couple of stores that I'd like to display if they're open or not.
The issue is that I have my current time.
Time.current
=> Sat, 11 Jun 2016 11:57:41 CEST +02:00
and then if I for example take out the open_at for a store I get:
2000-01-01 00:00:00 +0000
so what I have now is:
def current_business_hour(current_time: Time.current)
business_hours.where('week_day = ? AND open_at <= ? AND close_at >= ?',
current_time.wday, current_time, current_time).first
end
and then I check if a current_business_hour is present. However this is calculating it wrong by what seems like two hours. The open_at and close_at should be within the time zone of Time.current.
In Rails, dates and times are normally saved in UTC in the database and Rails automatically converts the times to/from the local time zone when working with the record.
However, for pure time type columns, Rails doesn't do such automatic conversion if the time is specified as a string only. It must be specified as a Time object instead, which includes the local time zone.
So, for example, if you wanted to store the open_at time as 14:00 local time, you should not set the attribute with a plain string, because it will be saved to the db verbatim, not converted to UTC:
business_hour.open_at = '14:00'
business_hour.save
# => UPDATE `business_hours` SET `open_at` = '2000-01-01 14:00:00.000000', `updated_at` = '2016-06-11 15:32:14' WHERE `business_hours`.`id` = 1
business_hour.open_at
# => 2000-01-01 14:00:00 UTC
When Rails reads such record back, it indeed thinks it's '14:00' UTC, which is off by 2 hours in the CEST zone.
You should convert the time from string to a Time object instead, because it will contain the proper local time zone:
business_hour.open_at = Time.parse('14:00')
business_hour.save
# => UPDATE `business_hours` SET `open_at` = '2000-01-01 12:00:00.000000', `updated_at` = '2016-06-11 15:32:29' WHERE `business_hours`.`id` = 1
business_hour.open_at
# => 2016-06-11 14:00:00 +0200
Note that the column is now stored in UTC time. Now, you can safely compare the time columns with any other rails datetime objects, such as Time.current.

Ruby on Rails retrieve datetime by converting it from UTC to user's time zone from database by querying date

my application stores attendance datetime from user as 2014-11-23 08:42:00 by converting it to UTC, application has custom search method to search attendance only by entering date as 2014-11-23.
Now problem is i have 5 attendance of date 2014-11-23 with different time and Rails stored it by converting into UTC. so for 2 record it is converted and date is changed to 2014-11-22.
my search query is 2014-11-23 and expected result is all 5 records but getting only 3.
not getting other 2 because they are converted to 2014-11-22
How to get all 5 records only with entering 2014-11-23 ?
Please help.
If you store the datetime you can use it for that query using beginning_of_day and end_of_day methods on the time with timezone
date = Time.parse('Wed, 19 Nov 2014 19:23:59 UTC +00:00')
# => 2014-11-19 19:23:59 UTC
d = date.in_time_zone('Paris')
# => Wed, 19 Nov 2014 20:23:59 CET +01:00
User.where(created_at: (d.beginning_of_day..d.end_of_day)).count
# (46.5ms) SELECT COUNT(*) FROM "users" WHERE ("users"."created_at" BETWEEN '2014-11-18 23:00:00.000000' AND '2014-11-19 22:59:59.999999')

Rails Time objects

I've got a quick-add action in my events controller, as the client really only schedules events at three different time slots in a given day. Date and Time are working fine with the default form, but trying to set the values by hand are giving me some trouble.
def quick_add #params are date like 2012-04-29, timeslot is a string
timeslot = params[:timeslot].to_sym
date = params[:date].to_date
#workout = Workout.new do |w|
w.name = 'Workout!'
w.date = date
case timeslot
when :morning
w.time = Time.local(w.date.year, w.date.month, w.date.day, 6)
when :noon
w.time = Time.local(w.date.year, w.date.month, w.date.day, 12)
when :evening
w.time = Time.local(w.date.year, w.date.month, w.date.day, 18, 15)
else
w.time = Time.now
end
end
The events are getting created, the dates are correct, but times are:
Morning: 2000-01-01 10:00:00 UTC
Expected: 2012-05-02 06:00:00 UTC -400
Noon: 2000-01-01 16:00:00 UTC
Expected: 2012-05-02 12:00:00 UTC -400
Evening: 2000-01-01 22:15:00 UTC
Expected: 2012-05-02 18:15:00 UTC -400
It's worth noting that running the commands in rails console seems to get the results I'd expect.
Time values are stored in UTC/GMT (+0) time zone as an integer so that no time zone data has to be stored with it. Rails always stores times in the database as UTC times. When you read them out, you'll want to convert them back to your local time zone again.
You can use time.getlocal to convert it to your local time zone.

Rails 3 DateTime and Time beginning_of_day end_of_day format incorrect

I am trying to find events on certain days with this code:
Event.where('starttime BETWEEN ? AND ?', DateTime.now.beginning_of_day, DateTime.now.end_of_day)
In rails console if I run DateTime.now.beginning_of_day I get exactly what I expect:
Mon, 09 Apr 2012 00:00:00 -0700
I can see where the problem is occurring but looking at the SQL in rails console. Somehow when the date makes it into the SQL query it gets automatically formatted to the wrong date and time.
SELECT "events".* FROM "events" WHERE (starttime BETWEEN '2012-04-09 07:00:00' AND '2012-04-10 06:59:59')
This is giving me results that vary from today until tomorrow, as the sql above says. I can see that when the DateTime.now.beginning_of_day also DateTime.now.end_of_day are being formatted incorrectly once they make it into the sql query. Do I need to be formatting this in a certain way? Any idea why it would go to 7:00 of today and 7:00 of tomorrow?
I don't know if it makes a difference but I'm using PostgreSQL.
Thanks!
See: http://railscasts.com/episodes/106-time-zones-in-rails-2-1
I think the times you're quoting above are actually the same time, just one is UTC time (you're on pacific time right?). To fix this you could try setting:
config.time_zone = "Pacific Time (US & Canada)"
in your environment.rb
You could also try:
DateTime.now.utc.beginning_of_day
DateTime.now.utc.end_of_day
so the you're using UTC as per the DB
You are probably using UTC time in your database. This is a good thing but it leads to all sorts of confusion. The -0700 offset is converted to 07:00:00 since that's when your day starts in UTC time.
You could use the PostgreSQL AT TIME ZONE construct with UTC timestamps:
SELECT e.*
FROM events e
WHERE starttime >= '2012-04-09 00:00' AT TIME ZONE 'UTC'
AND starttime < '2012-04-10 00:00' AT TIME ZONE 'UTC'
I recently explained PostgreSQL timestamp and time zone handling in a related answer.

Resources