Rails - Different timezone to limit daily posts - ruby-on-rails

I'm working on Rails application that let users to make max 2 posts for each day, very simple.
My problem is how to manage different timezone where different users lives. For example if I live in London and the day start at 00.00 and finish at 23.59 the posts count will reset at 00.00, but for another users live in New York the counter will then reset at different time. (not at 00.00) How can I manage this situation?
I hope I explained myself.
UPDATE 1
#Cluster
The problem with your code is that time_zone.utc_offset give a wrong time delta:
1.9.3p194 :123 > time_zone = ActiveSupport::TimeZone.new("Prague")
=> (GMT+01:00) Prague
1.9.3p194 :124 > DateTime.now.utc.midnight - time_zone.utc_offset
=> Thu, 10 Apr 2003 00:00:00 +0000
Why that?
Instead of that I've found useful this code:
User.posts.where(:created_at => DateTime.now.in_time_zone(time_zone).beginning_of_day..DateTime.now.in_time_zone(time_zone).end_of_day).count
It seems to get the correct number of messages between the user's day (with user's timezone). What do you think?
UPDATE 2
#Cluster
What's the difference between:
User.posts.where(:created_at => DateTime.now.in_time_zone(utc_time_zone).beginning_of_day..DateTime.now.in_time_zone(utc_time_zone).end_of_day).count
and yours:
1.9.3p327 :015 > time_zone = ActiveSupport::TimeZone.new("Prague")
=> (GMT+01:00) Prague
1.9.3p327 :016 > Time.zone.now.utc.midnight - time_zone.utc_offset
=> 2013-02-15 23:00:00 UTC
in terms of performances and way of doing?
UPDATE 3
Actually the code in your example doesn't work, look here:
# WRONG
1.9.3p194 :096 > user.posts.where('created_at > ?', Time.zone.now.utc.midnight - my_time_zone.utc_offset).count
(0.2ms) SELECT COUNT(*) FROM "messages" WHERE "messages"."sender_id" = 5 AND (created_at > '2013-02-15 23:00:00.000000')
=> 4
# CORRECT
1.9.3p194 :098 > users.posts.where(:created_at => DateTime.now.in_time_zone(my_time_zone).beginning_of_day..DateTime.now.in_time_zone(my_time_zone).end_of_day).count
(0.3ms) SELECT COUNT(*) FROM "messages" WHERE "messages"."sender_id" = 5 AND ("messages"."created_at" BETWEEN '2013-02-16 23:00:00.000000' AND '2013-02-17 22:59:59.000000')
=> 0

Depending on exactly how you want it to work I can think of two solutions.
First, allowing the user to post twice in a 24 hour period, this has the benefit of being timezone agnostic:
User.posts.where('created_at > ?', 24.hours.ago).count
If that returns 2 then don't let them post.
If you really want to ensure that there are no more than 2 posts per day then:
time_zone = ActiveSupport::TimeZone.new(current_user.time_zone)
Users.posts.where('created_at > ?', Time.zone.now.utc.midnight - time_zone.utc_offset).count
User#time_zone needs to hold a valid time zone recognized by Rails.
Since the database datetime (created_at) is already in UTC, first normalize your server time to utc, then set the time to midnight, and apply the utc offset for the timezone. So if you have someone in a -7 timezone, then it will look for posts since 0700 UTC, for someone in a +3 timezone it will look for posts since 2100 UTC.
ActiveSupport::TimeZone
To clarify the difference between the two let me use a couple of examples.
First example is a user posting at 1200 then again at 1800 on Monday.
With the first piece of code, the user will not be able to post again until after 0600 Tuesday. With the second piece of code they will be able to post again at 0000 Tuesday (after midnight).
Second example is a user posts at 2330 then again at 2350 on Monday. Again, in the first example they will not be able to post again until after 2330 Tuesday, but the second example will allow them to post again at midnight, allowing them to post at 0010 and 0020, giving them 4 posts in 50 minutes.
It really depends on what the purpose of the post limitation is for. The first example is going to be much simpler to use and implement as it will work irregardless of time zones.
If your pulling time zone info from FB, then check what format that timezone info is in. I'm guessing it will not be a valid string that ActiveSupport::TimeZone will accept. For example it wants "Mountain Time (US & Canada)" for MST(-0700). If FB is giving you back something like -7 for MST or -25200 (-7 hours in seconds) then you can use those without mucking around with AS:TZ
RE: Update 1
Here's the output using Time.zone instead of DateTime
1.9.3p327 :015 > time_zone = ActiveSupport::TimeZone.new("Prague")
=> (GMT+01:00) Prague
1.9.3p327 :016 > Time.zone.now.utc.midnight - time_zone.utc_offset
=> 2013-02-15 23:00:00 UTC

Consider to use UTC timezone for everyone. I.e. StackOverflow uses that approach for votes and it works fine.

Related

Why does a Time math calculation return one value in IRB but sends a diff value to SQL?

I have a method in my Post model that looks like this:
def self.num_posted_x_days_ago(n)
start_date = Time.now.midnight - n.day
end_date = Time.now.midnight - (n+1).day
where(created_at: start_date..end_date).count
end
When I run it in IRB, I get this:
> Post.num_posted_x_days_ago(8)
(0.3ms) SELECT COUNT(*) FROM "posts" WHERE ("posts"."created_at" BETWEEN '2015-02-27 05:00:00.000000' AND '2015-02-26 05:00:00.000000')
=> 0
Note that the Time on the date is 05:00:00.000000 for both the start_date and end_date.
But when I add a binding.pry into the method, and inspect both the start_date and end_date, I get this:
1] pry(Post)> start_date
=> 2015-02-26 00:00:00 -0500
[2] pry(Post)> end_date
=> 2015-02-25 00:00:00 -0500
Which shows the right time starting at midnight.
When I try to do it all manually at the console, I get this:
[31] pry(main)> Time.now.midnight
=> 2015-03-07 00:00:00 -0500
[32] pry(main)> Time.now.midnight - 9.days
=> 2015-02-26 00:00:00 -0500
[33] pry(main)> Time.now.midnight - 9.day
=> 2015-02-26 00:00:00 -0500
Notice that all the times show midnight.
What could be causing this mismatch?
Database stores time in UTC. When you display it in console it shows you your local timezone.
Assuming you're interested in posts written on a particular day in your timezone the result should be what you expected. If you want to retrieve one day of posts in UTC timezone instead replace Time.now.midnight with Time.now.utc.midnight
---edit---
All dates stored in the database use UTC. Whenever Rails saves into database or retrieves from the database it converts from your local time zone to UTC or vice versa. That's why you see different times in SQL query. If you check the returned objects you will see that in fact these are correct results.
The only thing to keep in mind is that if you run the app on a different server it's likely that the system timezone will be different (it's a good practice to set server timezone to UTC), so Time.now.midnight will mean something different than on your local computer.
If you want to make sure Rails always retrieves posts from midnight of EST, you need to specify timezone explicitely:
Time.now.in_time_zone('EST').midnight
or (should switch to EDT when observing daylight saving time):
Time.now.in_time_zone('America/New_York').midnight

Issue comparing DateTime/Time in different zones in Rails

In my application the user sets a specific end time for a task. When a task is loaded, it creates a corresponding CheckIn using that task's information (self in this case is the task):
def create_checkin_end_time(day)
week = (Date.today .. Date.today + 6)
week.each do |day_of_week|
if day_of_week.strftime("%A") == day
return DateTime.new(day_of_week.year, day_of_week.month, day_of_week.day, self.end_time.hour, self.end_time.min)
end
end
end
I've set config.time_zone equal to "Eastern Time (US & Canada") but instead of creating the task as UTC it submits as EST then subtracts 4 hours from that.
[2] pry(main)> CheckIn.last.end_time
CheckIn Load (0.7ms) SELECT "check_ins".* FROM "check_ins" ORDER BY "check_ins"."id" DESC LIMIT 1
=> Sun, 20 Oct 2013 19:30:00 EDT -04:00
[3] pry(main)> Task.last.end_time
Task Load (0.7ms) SELECT "tasks".* FROM "tasks" ORDER BY "tasks"."id" DESC LIMIT 1
=> Sun, 20 Oct 2013 23:30:00 EDT -04:00
The end time for that instance of the model should be 11 PM, based on the task that was used to create it. This is my first go at working with time in Rails so any help would be really appreciated. Thank you.
UPDATE:
Switched it back to UTC and added the method to ApplicationController, but the real problem here was the way I was creating the CheckIn. Because Task.end_time was used as reference in the creation and was called out of the database as EST, the offset was doubled in the creation of the CheckIn. Real weird. My solution was to use .change on creation:
CheckIn.create(task_id: self.id, start_time: self.create_checkin_start_time(day).change(:hour => self.start_time.hour + 4), end_time: self.create_checkin_end_time(day).change(:hour => self.end_time.hour + 4))
This is by NO MEANS a great/permanent solution. I'm in a bit of a time-crunch at the moment and this will suit my needs.
I'd recommend leaving config.time_zone as UTC so you don't see these kind of weird changes happening in the database.
If you want to force a particular timezone on the users of the application, then your best bet is as a before_filter inside ApplicationController:
before_filter :set_timezone
def set_timezone
Time.zone = "Eastern Time (US & Canada)"
end
That way then, all the times will be saved to the database as UTC times, but will be displayed on the site as being in Eastern time.
The reasons for doing this are pretty simple: it's easier to convert from one universal central time (UTC) than it is to convert from some arbitrary point back to UTC and then to some other arbitrary point.

Time zone confusion (1 hour off)

Time.use_zone('Pacific Time (US & Canada)') do
p Time.zone.now
end
I get the following: => Sun, 14 Apr 2013 20:30:53 PDT -07:00
Yet when I do Rails Time Zone Select.... it says -8:00 quite clearly. Why is it -7 in one area and -8 in another?
Other times, time zones like Hawaii which are -10:00 don't get offset by an hour.
I assume this has something to do with DST, but I'm more curious whether it means it's working properly or improperly and there is something else I need to do.
Ultimately I'm using this in a datepicker, and I find it very odd that when I use Time.zone.parse (along with my time zone around filter), its offsetting everything by 1 hour.
THanks
Edit
Heres a similar problem I also just experienced with another piece of code
2.0.0-p0 :006 >
2.0.0-p0 :006 > u.meetups.in_future.first.meetup_time
Meetup Load (0.4ms) SELECT `meetups`.* FROM `meetups` WHERE `meetups`.`user_id` = 1 AND (meetup_time >= '2013-04-23 04:46:48') ORDER BY meetup_time ASC LIMIT 1
=> Tue, 23 Apr 2013 05:43:00 UTC 00:00
2.0.0-p0 :007 >
Notice the discrepancy in the result compared to the where clause.
Edit
It appears to work for CST properly, but PST is off by ~1 hours?? I feel this is all the same problem, I am just missing a piece of the puzzle.
The output is correct. Pacific Daylight Time has an offset of -7, while Pacific Standard Time has an offset of -8. My guess is that "Rails Time Zone Select" (whatever that is) is only is showing you the "standard" offsets, rather than the current ones. This is common in time zone pickers.
Hawaii does not implement Daylight Savings Time of any kind, so that addresses your second point.
On your third point, I would have to know more about your database platform to answer why the values are converted to UTC. Given that these are event times, I would say that they should be in UTC. They could also be in the time zone of the location of the "meetup", but only if the offset from UTC was also stored. But never should they be in the time zone of the server.
On your fourth point, it's difficult to tell what you mean without more details. Expand if you feel necessary.

Why do my time-related unit tests break every day at 4pm?

For a long time I've been having this issue that at a certain time of the day, a TON of my tests break. I have a lot of tests that are doing simple date comparisons and everything runs fine from midnight to like 4:00 in the afternoon. Any idea why this is happening? I've set my timezone in my environment file too.
It seems like some of my calls like 5.days.from_now.to_date are adding an extra day.
Edit
For instance, this test fails:
# Widget that creates items for how many days the trip is gone.
def test_should_create_correct_amount_of_days_for_trip
w = DayWidget.create(:trip => trips(:hawaii))
assert_equal w.days.size, 5
end
# Code in trip model that calculates amount of days
def number_of_days
(self.return_date.to_date - self.depart_date.to_date).to_i + 1
end
# Test fixture yaml for Hawaii
hawaii:
depart_date: <%= Time.now.tomorrow.to_s(:db) %>
return_date: <%= 5.days.from_now.to_s(:db) %>
After 4:00 pm, the test above fails and says it created 6 days instead of 5. :(
You're probably in the Pacific time zone, 8 hours behind UTC (which is why at 4:00p they start breaking, since that's when it hits midnight UTC).
Without seeing your test/comparison code, all I could say is to make sure you're comparing dates/times with the same location (UTC to UTC or localtime to localtime).
Update: Ok, it looks like Time.now returns a Time object, whereas using XXX.days.from_now returns an ActiveSupport::TimeWithZone object, resulting in different handling of timezones:
ruby-1.9.2-p136 :009 > (Time.now+5.days).to_s(:db)
=> "2011-02-08 19:40:24"
ruby-1.9.2-p136 :010 > 5.days.from_now.to_s(:db)
=> "2011-02-09 03:40:29"
My recommendation would be to either call .utc on your times in your fixtures like so:
ruby-1.9.2-p136 :017 > 5.days.from_now.utc.to_s(:db)
=> "2011-02-09 03:42:39"
ruby-1.9.2-p136 :018 > (Time.now+5.days).utc.to_s(:db)
=> "2011-02-09 03:42:39"
or to just switch to using 1.day.from_now instead of Time.now.tomorrow to keep the types consistent.
You can also use Time.current instead of Time.now.
Time.current returns ActiveSupport::TimeWithZone just like #ago and #from_now, so you can safely compare dates returned by them.
If you are truly dealing with just dates, be sure to set the hours, minutes and seconds to 0, otherwise you are at the mercy of the time of day you create the dates.

Rails timestamps don't use the right timezone

I'm a bit confused about timezones in rails. I want my rails app to use British Summer Time (like daylight savings in the US) for the timestamps set in updated_at and created_at in my models. I changed my environment.rb to say
config.time_zone = 'London'
The ubuntu server my app is on seems to use BST for it's time: in the command line, for example, if i type 'date' i get the current time (not offset by an hour). In the rails console, i see the following:
>> time = Time.now
=> Wed Oct 27 16:29:17 +0100 2010
>> time.zone
=> "BST"
All fine. However, if i make a new AR model object and save it, the timestamps are from an hour ago. So, it looks like this is using UTC. Now, i can see the logic in this: since the timestamps might be used in the model logic, you want them to be based on an unvarying yardstick time, ie UTC. But, this is a weird bit of behaviour that i don't understand:
#change a record and save it
>> someobj.save
=> true
#object's updated_at is one hour ago
>> someobj.updated_at
=> Wed, 27 Oct 2010 15:34:22 UTC +00:00
>> Time.now
=> Wed Oct 27 16:34:31 +0100 2010
#however, Time.now - object's updated at is just a few seconds.
>> Time.now - someobj.updated_at
=> 15.305549
So, before doing the subtraction, updated_at is converted into the current time zone.
The reason i want to show the date in the current time zone is just for status reports etc in the views: if someone updates something i want them to see that it was updated '1 minute ago' not 'one hour ago'.
Can anyone unconfuse me? cheers, max
EDIT: My immediate problem, of showing the right time in the status, is solved by using the 'time_ago_in_words' helper, which adjusts for time zone. I'd still like someone to explain what's going on with the timestamps though :)
Timestamps are stored in UTC by default, and this is probably the best way to do it. If you move from one server environment to another, you don't want all of your times shifting around just because you switched time zones.
If you want to know what the timestamp is in your local time zone, you just have to ask for it that way:
someobj.updated_at.localtime
Note the offset listed at the end of the times -- the first offset is 0, the second is 1. When the time calculation occurs, the offset is included automatically, so that the subtraction gives you the correct result. someobj.updated_at and Time.now each displays its value in a different time zone, so they are really only 9 seconds apart, not 1 hour and 9 seconds.

Resources