A collection of computer systems and programming tips that you may find useful.
Brought to you by Craic Computing LLC, a bioinformatics consulting company.

Monday, March 14, 2011

Sorting Dates and Times in Ruby

Sorting an array of objects on a field that represents a time or date can be a bit tricky.

Take an example of a Rails ActiveRecord object with a standard 'created_at' column.

You can sort these with code like this:
myobjs.sort_by{|a| a.created_at}
This works but it is really sorting on the String representation of those dates. That may be good enough for some uses but not if you want to sort down to the minute and second and in particular, if you want to sort in correct descending order.

The best approach is to convert the date or time to the number of seconds since the epoch, which is an integer, and do a numeric sort on that.

A 'created_at' column has the class 'ActiveSupport::TimeWithZone' and this will output a String under most uses. Convert this to an integer with 'myobj.created_at.to_i' and then sort on that.
myobjs.sort_by{|a| a.created_at.to_i}

If you are working with Ruby Date objects, such as 'Date.today' this will not work. In most uses the Data will be converted to a string like this "Mon, 14 Mar 2011". If you try to convert the Date object directly to an integer with 'to_i' you will get an 'undefined method' error.

Here you need to explicitly convert to the epoch seconds format using 'strftime' and THEN convert that to an integer.
> today = Date.today
=> Mon, 14 Mar 2011
> today.strftime("%s").to_i
=> 1300060800

If you only have date strings, such as '2011-03-14', then you will need to convert these to Date objects using 'Date.parse' and then convert those to integers.

Working with dates and times can get messy but converting to integers is the best way to avoid complications.


1 comment:

Unknown said...

I did a quick test of the the date conversion (using 1.9.1) and it appears to give the wrong result (in IRB):

> require 'date'
=> true
> require 'time'
=> true
> Time.at(Date.new(2011,3,14).strftime('%s').to_i)
=> 2011-03-13 19:00:00 -0500
> Time.at(Date.new(2011,3,14).to_time.to_i)
=> 2011-03-14 00:00:00 -0500

Also, if there's no need to convert back, there's date#hash as well.

Archive of Tips