Rob Hughes
Rob Hughes

Reputation: 906

Ruby on rails. Timezones and scopes

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

Answers (2)

Rob Hughes
Rob Hughes

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

toddmetheny
toddmetheny

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

Related Questions