How I used Time in Rails — What I will do differently next ‘time!’

For a recent school project, I built a web application that tracks the on-time percentage of each major US airline at the major US airports. The application can be found here. The code is here.

The Flight Stats Airport Status API provided the information:

"delays": {
"departureGateDelayMinutes": 3,
"departureRunwayDelayMinutes": 1
}

If the ‘departure gate delay’ was less than 15 minutes I considered the flight on-time and tagged it appropriately. I then divided this number by the total number of flights for that airline at that specific airport to obtain the airline’s on-time percentage. Obtaining the information was relatively straightforward. The difficult part was obtaining the information in the correct timeframe.

Below is a segment of the API request:

“airport/status/{airport}/dep/{year}/{month}/{day}/{hourOfDay}”

The API takes the year, month, day, and hour as parameters and returns relevant flight information over a 6-hour period. Therefore to obtain a full day of flight information at Denver International Airport (Denver), I would need to request the API at the following times (using the 24-hour clock):

 0:00 -- for 23:00 the previous day to 5:00 the next day
 6:00 -- for 5:00 to 11:00
12:00 -- for 11:00 to 17:00
18:00 -- for 17:00 to 23:00 

To do this, I wrote the following bit of code.

def offset
{mt: -7}
end
def api_offset_time(time_zone)
offset_time = Time.now + offset[time_zone].hours
api(offset_time)
end
def api(offset_time)
parse(connection.get(“#{airport_code}/dep/#{offset_time.year}/#{offset_time.month}/#{offset_time.day}/#{offset_time.hour})
end

The API is called by passing in a timezone as a key. I then used Time.now and subtracted the offset to obtain the new offset_time which is then passed into the API method. The year, month, day, and hour are then called respectively.

To execute this code I wrote a rake task :

desc “update delays”
task :denver => :environment do
puts “Updating Denver API”

Flights.new(“DEN”, “AA”, :mt).save
end

On the fourth line, I pass in the airport code, the airline code, and the appropriate timezone key.

To execute this rake task at the correct time (every 6 hours starting at 0:00), I setup a Heroku scheduler to run the following rake task every hour:

desc “update delays”
task :explode => :environment do
times = [0,6,12,18]
if times.include?(Time.now.hour)


Rake::Task[‘denver’].execute
Rake::Task[‘dulles’].execute
 end
end

The rake task checks every hour to see if the time is 0,6,12, or 18 and then executes accordingly.

This approach worked but in hindsight(or maybe after a big refactor), I would have done things differently.

First, what if I wanted to add in other airports that were not in the MST. For example, Dulles International Airport is in the Eastern Time Zone (EST).

Since I was based everything off the MST, I had to adjust all my offsets appropriately and for airports in different timezones I had to pass in different timezone keys (see bolded below).

def offset
{ec: -5, cc: -6, mt: -7, pc: -8}
end
desc “update delays”
task :dulles => :environment do
puts “Updating Dulles API”

Flights.new(“IAD”, “WN”, :ec).save
end

This solution worked but left a lot of extraneous variables. Say for example I am at parents house in Virginia (EST) and not at my home in Denver (MST). Because the time on my MacBook updates automatically, Time.now would change making running in development mode quite inconsistent.

To fix this I didn’t change the time on my computer. I also told Heroku, which runs on Universal Time Coordinated (UTC), to run on MST. This isn’t an ideal situation and also created another small problem.


times = [0,6,12,18]
if times.include?(Time.now.hour)


#Run rake tasks

Remember in my rake task where I updated all the airports on a 6-hour time interval? Here I used Time.now which is MST, but I wanted every airport to update on the following schedule relative to their own timezone:

0:00 -- for 23:00 the previous day to 5:00 the next day
6:00 -- for 5:00 to 11:00
12:00 -- for 11:00 to 17:00
18:00 -- for 17:00 to 23:00

But since I was using times.include?(Time.now.hour) in my rake task and Time.now was MST, it was only updating on the above schedule for airports in the MST. For those in the EST (Dulles International Airport), it was still covering the entire 24-hour period, but two hours ahead.

For example if the rake task executed at 12:00 MST (14:00 in EST), the offset would be calculated using MST minus the offset_time (12:00–5 hours = 7:00). The API would pick up data from 7:00 to 13:00 (6-hour interval) for all airports in the EST while it would pick up data from 5:00 to 11:00 for all airports in the MST.

Over a long period, this really wouldn’t matter since it covered the entire 24-hour period, but it is interesting to note how the data accumulation wouldn't occur at consistent timing across multiple timezones.

The last problem was the ‘Last Updated Section:’

Since the Heroku PostgreSQL database runs in UTC, I modified the display of this time with the following bit of code:

def self.last_updated_at
last.created_at.in_time_zone(‘America/Denver’).strftime(“%A, %B %d, %Y at %I:%M%p”)
end

The problem here is that while the last_updated_at time is technically correct, for every airport the last_updated_at time is always displayed in MST making it possibly confusing for the end user.

Things I would have done differently:

First I would have not used Time.now. Instead I would have based my whole application around UTC. To do this, I would have used Time.current which returns the current time in UTC. I would have also left Heroku in UTC.

I would have still left the API configured for local time, but would have adjusted the timezone offsets appropriately.

def utc_offset
{ec: -12, cc: -13, mt: -14, pc: -15}
end

*This does not take into account daylight savings time — that itself is another article.

Now if anyone were to fork my repository, the code would work because we would all be using UTC.

Second I could have built various rake tasks to update the flight data on the consistent schedule I outlined twice above.

In conclusion, while the application works, basing everything in MST was not the best idea. Using Time/Date.current is a better solution.

Show your support

Clapping shows how much you appreciated Bret Doucette’s story.