UTC conversion is different between local and server - ruby-on-rails

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.

Related

how can i menction specific offset to be deducted while saving times or datetime in rails?

I am having a datetime column to store employee check-in ,out times. my logic in the controller action to capture check-in is,
def check_in
#employee = current_user.employee
punch_record = #employee.in_outs.where("date = ?", Date.today).first
punch_record.check_in = DateTime.now
punch_record.save
end
when a person clicks check-in then the above logic gets executed.And in database it saves the check-in by deducting the indian offset ,that is (05:30). and in view page, i am using the in_time_zone method to show the check_in time, back in indian timezone,so that its adding the offset(05:30). and the time is showing properly. this is working fine.
record.check_in.in_time_zone(record.time_zone).strftime(" %I:%M %P")
here the record time_zone = Mumbai
now i am facing the time_zone issue in import attendance, where a person fills the employee check-in,out times in spreadsheet and uploads it. the format that is used in excel sheet is 09:30 am.
so here i am using the following logic to convert it into datetime.
irb(main):004:0> "09:30 am".to_datetime
=> Mon, 07 Nov 2016 09:30:00 +0000
here in this case the offset(05:30) is not getting deducted. how to make the offset deducted in this case?
You aren't comparing like with like.
In the first case you are doing:
record.check_in.in_time_zone(record.time_zone)
Which is taking the recorded datetime object (9:30 am) and converting it to the relevant time zone.
In the second case:
"09:30 am".to_datetime
Which is just setting date time to 9:30 am - you are doing nothing to convert it.
Replicate your logic in rails console by doing:
"09:30 am".to_datetime.in_time_zone('Kolkata')
and you will find you get the right result.
If "9:30 am" is input that isn't UTC but represents the time in India itself, then to convert it to UTC simply do:
"9:30".in_time_zone('Kolkata').utc
=> 2016-11-07 04:00:00 UTC

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.

Time.now.beginning_of_week giving two different values

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

Storing dates in UTC after updating the user entered time zone to reflect proper UTC time

I have an Event model which has start and end date/times and an associated time zone.
I need the date times submitted to be stored in the correct version of UTC based off of the time zone entered.
Example, if the user submits 2014-04-24 7PM EST
The database should be:
2014-04-24T23:00:00.000Z
Not this:
2014-04-24T19:00:00.000-07:00
Ok, I know rails stores everything in UTC, but I need to store the UPDATED UTC time based off the time zone - since my form is 3 fields, x2 date/time & x1 time zone
This is my model validation I thought would work properly:
def calc_utc_based_off_timezone
time_zone = self.time_zone
puts "start date before assignment: "
puts self.start_date
self.start_date = self.start_date.in_time_zone(time_zone)
self.end_date = self.end_date.in_time_zone(time_zone)
puts "start date after assignment: "
puts self.start_date
end
Entered in my form:
2014-04-24 - 19:00
2014-04-26 - 19:00
est
Output:
start date before assignment:
2014-04-24 19:00:00 UTC
start date after assignment:
2014-04-24 19:00:00 UTC
What I'm expecting:
start date before assignment:
2014-04-24 19:00:00 UTC
start date after assignment:
2014-04-24 23:00:00 UTC <-- updated UTC to reflect the time zone
Any help would be appreciated, thanks.
There is support for Time objects with Time Zones in the ActiveSupport::TimeWithZone class.
As long as you set the Time zone before giving the params to the new object function Event.new(params[:event]) rails will apply the offset that is set, and interpret as if its in that time zone.
# example params like the request
params = {
time_zone:"EST",
event:{
start_date:"2014-04-24 - 19:00",
end_date:"2014-04-26 - 19:00"}
}
Time.zone = params[:time_zone] # pulled from a time_zone_select field
#event = Event.new(params[:event])
if you have separate time and date fields, just combine them into whichever field holds the datetime in the database.
params[:event][:start_date] = "#{params[:event][:start_date]} #{params[:event][:start_time]}"
params[:event][:end_date] = "#{params[:event][:end_date]} #{params[:event][:end_time]}"
#event = Event.new(params[:event])
The timezones offset will be automatically applied and you should get the following if you inspect the object.
> puts #event.start_date
2014-04-25 00:00:00 UTC
> puts #event.end_date
2014-04-27 00:00:00 UTC
Here is a great guide that deals with Rails and Time Zones - The Exhaustive Guide to Rails Time Zones
You're right, there is something in Rails ActiveSupport for this,
Time.parse("2014-04-24 - 19:00").in_time_zone
Thu, 24 Apr 2014 23:00:00 UTC +00:00
Time.parse("2014-04-24 - 19:00").getutc
2014-04-24 23:00:00 UTC
You can also specify the detail and format (like iso8601) of your response by passing additional arguments like this:
> Time.parse("2014-04-24 - 19:00").getutc.iso8601(2)
"2014-04-24T23:00:00.00Z"
> Time.parse("2014-04-24 - 19:00").getutc.iso8601(3)
"2014-04-24T23:00:00.000Z"
Documentation for ActiveSupport::TimeWithZone: http://api.rubyonrails.org/classes/ActiveSupport/TimeWithZone.html#method-i-getutc

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.

Resources