Reputation: 906
I need help trying to use timezones in a scope. Below is an example of what I'm trying to achieve.
All dates are stored in UTC time:
EG. A user in timezone UTC +1 posts a gig that will start at 20:00. On the gig show page, everyone will see this as 20:00, which is correct.
My problem arises with the fact that I must 'expire' gigs, so they no longer show up in search after the date
has passed.
Until now I have been using a scope;
scope :expired, -> { where('date <= ?', Time.current.to_datetime) }
so then gig.expired
would show all the gigs where the gig.date
is in the past.
The problem is that as the user is in UTC +1 timezone, for him the gig will 'expire' at 21:00, not 20:00, as it expires in UTC not UTC+1.
EDIT:
I am now saving the timezone as an attribute of the gig model upon creation. I was already using geocoder for location, which saves coordinates. Using the timezone gem, I can lookup the coordinates and extract the timezone, which is saved as a string to the model.
Eg. In a view, @gig.timezone
displays Europe/Amsterdam
(or whatever timezone was saved)
Or <%= @gig.date.in_time_zone(@gig.timezone) %>
displays 2016-03-17 21:38:00 +0100
My problem now, is using this with the previous scope. If I try;
def self.expired
Gig.where('date < ?', Time.now.in_time_zone("Europe/Amsterdam"))
end
Rails still 'expires' the gigs in UTC time. How can I add the gigs' saved timezone, and use that in the scope?
Upvotes: 0
Views: 621
Reputation: 906
Ok, after a couple of days trying to make this work I came up with a solution. If anyone can do any better I'd love to hear about it.
On gig creation I lookup the timezone with the timezone gem and save the resulting :string
to the timezone
attribute I created in the gig model
Longitude
and latitude
are available as attributes, as the gig has a location which is set in the create form;
timezone = Timezone.lookup(@gig.latitude, @gig.longitude)
@gig.update_attributes(timezone: timezone)
Then I added another column gigzonetime
to the gig model. I set this to the gig.date with the timezone offset subtracted.
gigzonetime = @gig.date - @gig.date.in_time_zone(@gig.timezone).utc_offset
@gig.update_attributes(gigzonetime: gigzonetime)
On the show and index pages I display <%= @gig.date %>
to show the local time in which it will be displayed, and in my scope I compare to the new attribute gigzonetime
so it expires at the correct time.
scope :expired, -> { where('gigzonetime <= ?', Time.current.to_datetime) }
This seems to be working for all timezones (future and past) It also seems to work correctly for daylight saving time differences.
Upvotes: 0
Reputation: 4443
I have never used the TimeZone gem, but if you're having difficulty using it in a scope, you could consider instead simply using it in a method that returns what you want.
Fwiw, I think you're potentially making your app difficult to maintain if you're saving different times for different time zones. I could see showing the user a date that's converted to their time zone...but they should be saved in a uniform fashion. This code, if it's relying on the time zone of the current user is going to struggle. It's important to compare apples to apples.
How is date being saved? If it's being saved as UTC time just compare it to UTC time.
If you prefer to use a scope I would do something like this:
scope :expired, -> { where('date < ?', Date.today) }
Alternatively you could do something like this:
def self.expired
Gig.where('date < ?', Date.today)
end
Upvotes: 0