Inconsistent behavior of Time.now in Ruby/Rails app - ruby-on-rails

I have a Ruby on Rails app that has a scheduled job I execute using cron and script/runner.
The purpose of the job is to find any updates that are past due ('stale', see below) and (after executing the job) update the 'due date' of the job a pre-defined number of minutes into the future.
The problem I'm having is that when I set the 'due date', the "GMT" time is used. However, when I search for stale jobs, the 'local' time seems to be used. The local time of the server is -7 hours offset from GMT, so the system goes 7 extra hours between updates.
Example: The due date of the job is 06:00 on a certain day. The system finds that job at 06:00 west coast time (-7 GMT). If the job is due again in 15 mins, it sets the 'due date' to 06:15 which gets changed to 13:15 when the object is saved (15 mins and 7 hours into the future).
I'll insert the code below. If it makes any difference, 'mark_updated' is part of a model and self.stale is a class-level function in a module that is included in the same model.
BTW...bonus question - how to get catch the SQL that is being executed from script/runner job in Production? That would make this easier to debug. I'm guessing a little at what is happening.
def mark_updated
self.next_refresh_due_at = Time.now + update_interval_in_minutes.minutes
save
end
def self.stale(max = 3)
news_sources = NewsSource.find(:all,
:conditions => ["next_refresh_due_at < ?", Time.now],
:order => 'next_refresh_due_at ASC',
:limit => max)
return news_sources
end

I'm assuming you have the time zone specified in your environment.rb config file. In that case you should use Time.zone.now instead of Time.now.
See this answer for a full explanation.

Related

Schedule a task in days or months

I want to schedule a job in a far future. I seen tools like delayed_job can do some tasks asynchronously but it seems to be used for tasks scheduled in 5 minutes. Another solution can be to do a cron but it's not very efficient.
Is there a better way? I want to be sure than, if I reboot the server, tasks scheduled are not deleted.
You can use the rufus-scheduler. The example taken from the documentation shows how to do it:
require 'rufus-scheduler'
scheduler = Rufus::Scheduler.new
scheduler.at '2030/12/12 23:30:00' do
# do something at a given point in time
end
It also allows you to schedule in diferent ways:
scheduler.in '10d' do
# do something in 10 days
end
scheduler.every '3h' do
# do something every 3 hours
end
scheduler.cron '5 0 * * *' do
# do something every day, five minutes after midnight
# (see "man 5 crontab" in your terminal)
end
take a look at whenever gem
Seems like using
every '0 0 23 12 *' do
...
end
would work for this. It can take cron syntax so minute hour day month day-of-week. So this will run every December 23rd at 12:00am regardless of what day of the week that is.

How would you calculate expiry to update column in rails

I'm trying to figure out the right approach for calculating the remainder days of a user subscription.
For example; user signs up December 25, 2013 for a month and unsubscribed December 29, 2013. Even though he unsubscribed 4 days after subscribing, his subscription should run on PLAN A for the next 27 days (31 day based month).
I'm thinking I would be using the created_at and updated_at of subscription model. So far I got to
**MODEL: subscription.rb
class Subscription < ActiveRecord::Base
before_save :set_expiry_date
def set_expiry_date
#remaining_days = Subscription.calculate(:created_at - Date.today)
Subscription.expiry = '#remaining_days'
Subscription.save
end
end
Something like that but I'm missing something here and this might be an ugly approach. I'm guessing this gives anyone who can help small understanding what I'm after.
I would then run a rake as cron each day at 23:59 that removes 1 (day) from Subscription.expiry number in there and when it finds a 0, it would update something. That's another question though but I placed the stuff about the rake cron so you see where I'm heading with this.
If this were my project, I would take a different approach. I would set subscription expiry to the actual expiry date rather than a decrementing integer. That makes less work for the cron and also feels like good date practice . . . you get the benefit of persisting expiration dates after the expiration, which might be handy for later data analysis. What if there is a bug in your program or your cron fails to run? By persisting the date, you can do some detailed homework or rerun your crons against a specific cohort.
If I needed to know how many days were remaining on the subscription for UI or for API responses, I could call a method on the subscription class that looks something like the remaining_days method below:
def set_expiry_date
#automatically adds a month
#http://ruby-doc.org/stdlib-2.1.0/libdoc/date/rdoc/Date.html#method-i-next_month
Subscription.expiry_date = Subscription.created_at.next_month
Subscription.save
end
def remaining_days
expired? ? 0 : (Date.today - Subscription.expiry_date).to_i
end
def expired?
(Date.today - Subscription.expiry_date).to_i <= 0
end
def expired_today?
Date.today == Subscription.expiry_date
end
Then, my daily cron that does something on expiration (send email beseeching the customer to come back?) would just look for subscriptions where expired_today? == true. My cron would then also ignore subscriptions that expired before today or are yet to expire.

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.

Adding times in ruby on rails

I currently have a model called Job which has an attribute called CPU. This corresponds to the CPU time that a job was running. I would like to add all the time attributes of all the jobs for a specific date. This column is in the time format 00:00:00. Therefore I thought this would work:
def self.cpu_time
sum(:cpu)
end
Which returns the following "Sat Jan 01 00:00:00 UTC 2000".
For my test data, I used the following cpu times:
00:00:46
00:26:46
Any help would be appreciated
This solved my problem, although it doesnt seem to be the rails way:
def self.cput
#times = find(:all,
:select => 'cput')
#total_time =0
for time in #times do
#total_time += time.cput.to_i - 946684800
end
#total_time
end
You don't state what data type you're using for the cpu column. Personally I would store it in seconds and then convert to hours, minutes and seconds after summing it.

Resources