Comparing times in a rails model validation is not working - ruby-on-rails

I am trying to have my validation prevent a user from creating an Appointment that is not in the future. My validation is working to prevent past appointments from being created, but it is also preventing the user from creating appointments today, which should be allowed. What is wrong here?
Appointment.rb:
validates :pickuptime, presence: true
validate :pickuptime_is_in_future
byebug
def pickuptime_is_in_future
if pickuptime < Time.now
errors.add(:pickuptime, "Appointment must be in the future")
end
end
I debugged this (using byebug), and at the point where the byebug line is, I found the following:
(byebug) Time.now
2014-12-20 20:33:35 -0500
(byebug) pickuptime
Sat, 20 Dec 2014 21:34:00 UTC +00:00
(byebug) pickuptime < Time.now
true
Why is this true if Time.now is 8:33pm and pickuptime is 9:34 pm?
Any help would be much appreciated, thanks!

TlDR;
Your Time Zone is not set.
A list of all available Time Zones can be fetched using
rake time:zones:all
and Set your timezone.
# application.rb:
class Application < Rails::Application
config.time_zone = 'Eastern Time (US & Canada)'
end
Long Answer
You are seeing a difference because of Time Zones and your local system UTC Offset.
(byebug) Time.now
2014-12-20 20:33:35 -0500
This Time is -05:00 hours or 5 hours behind UTC
(byebug) pickuptime
Sat, 20 Dec 2014 21:34:00 UTC +00:00
This Time is 00:00 hours or 0 hours behind UTC
(byebug) pickuptime < Time.now
true
Therefore this is true because your local time is actually
Sun, 21 Dec 2014 01:33:35 UTC +00:00
and the pickuptime is
Sat, 20 Dec 2014 21:34:00 UTC +00:00
and happens because your Time Zone is not set. To do so follow below.
A list of all available Time Zones can be fetched using
rake time:zones:all
and set your timezone. As detailed in ActiveSupport::TimeZone
# application.rb:
class Application < Rails::Application
config.time_zone = 'Eastern Time (US & Canada)'
end

Related

Set the right UTC DateTime considering clock changes

I have a ruby on rails app that creates event, from the frontend the date of the event is generated in Mountain Time and then the application transforms it in UTC.
The issue is that since the events are generated in the future sometimes we have issues with the clock change.
Let's say the event should happen:
Day X at 9:30 MT
It would be transformed in:
Day X at 14:30 UTC
But if we create an event in the future that fall in the week the clock change we would have an event configured at the wrong time, because it does not take into consideration the clock change.
Is there a way to generate a UTC dateTime from a specific TimeZone considering if the clock change would happen in that date?
According to Rails API
if your app has its time zone configured as Mountain Time(MT),
Daylight Saving Time(DST) works by default
# application.rb:
class Application < Rails::Application
config.time_zone = 'Mountain Time (US & Canada)'
end
Time.zone # => #<ActiveSupport::TimeZone:0x000000...>
Time.zone.name # => "Mountain Time (US & Canada)"
Time.zone.now # => Tue, 14 Dec 2021 09:46:09 MST -07:00
So if the event date falls after
the DST change, parsing (see ActiveSupport::TimeWithZone)
that date should return the appropiate time
time = Time.zone.parse('2022-03-13 01:00:00') # the parsed datetime is in the same timezone
=> Sun, 13 Mar 2022 01:00:00 MST -07:00
time.dst?
=> false
time = Time.zone.parse('2022-03-13 02:00:00')
=> Sun, 13 Mar 2022 03:00:00 MDT -06:00
time.dst?
=> true
You mention that the application transforms it to UTC. So if I assume,
the correct UTC date is passed to the backend(maybe as an ISO8601 encoded string), you should parse it and convert it to the app time zone by doing something like this:
date = params[:date]
# => "2021-12-14 18:05:05"
utc_datetime = DateTime.parse(date, "%Y-%m-%d %H:%M:%S")
=> 2021-12-14 18:05:05 +0000
mt_datetime = utc_datetime.in_time_zone
=> 2021-12-14 11:05:05 MST -07:00
...
end

Why does ActiveRecord return a UTC datetime in this case?

I have three models:
class EventInstance
has_many: :conflict_resource_bookings
belongs_to :event_template
end
class ConflictResourceBooking
belongs_to :event_instance
# Cached to simplify data loading:
belongs_to :event_template
end
class EventTemplate
has_many :event_instances
end
When I'm querying for datetimes, they usually come back in Time.zone:
Time.zone = "Pacific Time (US & Canada)"
ConflictResourceBooking.joins(:event_instance).order("event_instances.starts_at").first.event_instance.starts_at
# => Sat, 11 Feb 2017 15:00:00 PST -08:00
EventInstance.all.minimum(:starts_at)
# => Sat, 17 Dec 2016 13:00:00 PST -08:00
But I was surprised in one case, the datetime from ActiveRecord came in UTC, not Time.zone:
Time.zone = "Pacific Time (US & Canada)"
ConflictResourceBooking.joins(:event_instance).minimum("event_instances.starts_at")
# => 2017-02-11 23:00:00 UTC
ConflictResourceBooking.includes(:event_instance).minimum("event_instances.starts_at")
# => 2017-02-11 23:00:00 UTC
# Or, with EventTemplate:
ConflictResourceBooking.joins(:event_template).minimum("event_templates.starts_at")
# => 2017-01-07 23:00:00 UTC
In a different case, the combination of joins(...).minimum does return a localized datetime:
EventInstance.joins(:event_template).minimum("event_templates.starts_at")
# => Sat, 17 Dec 2016 13:00:00 PST -08:00
I would like to get a datetime in Time.zone, but I don't know why some of them came back in UTC. Have I made a mistake?
Is there something else I can look into to explain this difference?
Setting Time.zone
Our application is multi-tenant with users all around the world. In order to show times in the user's timezone, we assign Time.zone at the beginning of each request (in a ApplicationController.before_action hook). The assigned timezone is from the User record, eg Time.zone = current_user.time_zone.
In the examples above, I was in rails console, so I assigned Time.zone directly.

How to convert postgres UTC into another timezone?

Is there a way to convert postgres UTC time to the user's timezone or at the very least Eastern Standard Time (I'd be grateful with either answer)?
I thought that there was a way to change postgres UTC time, but I don't think there is without causing a lot of issues. From what I read it would be better to use code on the frontend that would convert it to the correct time zone?
This barely makes sense to me.
What's the point?
So that when a user checks off he completed a good habit, the habit disappears, and is suppose to reshow tomorrow at 12am, but the habits end up reshowing later in the day because of UTC.
habit.rb
scope :incomplete, -> {where("completed_at is null OR completed_at < ?", Date.today)} # aka those habits not completed today will be shown
def completed=(boolean)
self.completed_at = boolean ? Time.current : nil
end
def completed
completed_at && completed_at >= Time.current.beginning_of_day
end
please change your scope to this, it will search time zone specifically.
scope :incomplete, -> {where("completed_at is null OR completed_at < ?", Time.zone.now.beginning_of_day)}
Place this code snippet into your application.rb
config.time_zone = 'Eastern Time (US & Canada)'
and run this so it will be set on heroku heroku config:add TZ=America/Los_Angeles.
Generally, you can also use #in_time_zone. Always use the time like Time.zone.now.
Time.utc(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00
DateTime.utc(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00
Date.new(2000).in_time_zone('Alaska') # => Sat, 01 Jan 2000 00:00:00 AKST -09:00
date and time zones
api docs

Make Rails ignore daylight saving time when displaying a date

I have a date stored in UTC in my Rails app and am trying to display it to a user who has "Eastern Time (US & Canada)" as their timezone. The problem is that rails keeps converting it to Eastern Daylight Time (EDT) so midnight is being displayed as 8am when it should be 7am. Is there anyway to prevent the DST conversion?
>> time = DateTime.parse("2013-08-26T00:00:00Z")
=> Mon, 26 Aug 2013 00:00:00 +0000
>> time.in_time_zone("Eastern Time (US & Canada)")
=> Sun, 25 Aug 2013 20:00:00 EDT -04:00
Update
I eventually went with a twist on #zeantsoi 's approach. I'm not a huge fan of adding too many rails helpers so I extended active support's TimeWithZone class.
class ActiveSupport::TimeWithZone
def no_dst
if self.dst?
self - 1.hour
else
self
end
end
end
Now I can do time.in_time_zone("Eastern Time (US & Canada)").no_dst
Create a helper that utilizes the dst? method on TimeZone to check whether the passed timezone is currently in DST. If it is, then subtract an hour from the supplied DateTime instance:
# helper function
module TimeConversion
def no_dst(datetime, timezone)
Time.zone = timezone
if Time.zone.now.dst?
return datetime - 1.hour
end
return datetime
end
end
Then, render the adjusted (or non-adjusted) time in your view:
# in your view
<%= no_dst(DateTime.parse("2013-08-26T00:00:00Z"), 'Eastern Time (US & Canada)') %>
#=> Sun, 25 Aug 2013 19:00:00 EDT -04:00

How to get time/date string to UTC from local time zone

I have this string:
"2011-12-05 17:00:00"
where this is local time
irb(main):034:0> Time.zone
=> (GMT-08:00) Pacific Time (US & Canada)
Now how to I get a Time object with this in UTC?
These don't work:
Time.local("2011-12-05 17:00:00") => 2011-01-01 00:00:00 +0000
Time.local("2011-12-05 17:00:00").utc => 2011-01-01 00:00:00 UTC
UPDATE:
On my local machine, this works:
Time.parse("2011-12-05 17:00:00").utc
=> 2011-12-06 01:00:00 UTC
but on heroku console it doesn't:
Time.parse("2011-12-05 17:00:00").utc
=> 2011-12-05 17:00:00 UTC
It seems that you should use Time.parse instead of Time.local:
require "time"
Time.parse("2011-12-05 17:00:00").utc
Try this in rails application:
Time.zone = "Pacific Time" # => "Pacific Time"
Time.zone.parse("2011-12-05 17:00:00") # => Mon, 05 Dec 2011 17:00:00 UTC +00:00
Details here
I realized my issue was a little more complicated. I wanted to store the times in the database with no time zone, and no time zone conversions. So I left the server and rails app time zone at UTC.
But when I pulled the times out, I would sometimes need to compare them with the current time in the time-zone that the user was in. So I needed to get Time.zone.now for the user, but again, I have Time.zone set to UTC so no automatic conversions happened.
My solution was to pull out the stored user time zone and apply it to Time.now to get the users local time like this:
class Business < ActiveRecord::Base
def tz
TZInfo::Timezone.get(ActiveSupport::TimeZone::MAPPING[self.time_zone])
end
def now
tz.utc_to_local(Time.zone.now)
end
end
So that I could do something like:
#client.appointments.confirmed_in_future(current_business.now)
-
def confirmed_in_future(date_start = Time.zone.now)
confirmed.where("time >= ?", date_start)
end

Resources