I ported a function I once wrote in JavaScript (http://phrogz.net/JS/
Date.prototype.asDuration.js) over to Ruby today. I submit it below
for your review, criticism, and general code sharing.

(If I had cause to use this with more than just second-based
durations, I would modify this function to accept a 'base' option,
e.g. 1.2 means 1.2 days and not 1.2 seconds. If I had cause to use
this with more than just English, I would probably make it easier to
update the singular/plural names for units.)


class Numeric
  DurationPiece = Struct.new( :names, :plural, :singular, :factor )
  DurationPieces = [
    DurationPiece.new( [:y,  :years],         'years',   'year',
1.0*3600*24*365.25 ),
    DurationPiece.new( [:w,  :weeks],         'weeks',   'week',
1.0*3600*24*7      ),
    DurationPiece.new( [:d,  :days],          'days',    'day',
1.0*3600*24        ),
    DurationPiece.new( [:h,  :hours],         'hours',   'hour',
1.0*3600           ),
    DurationPiece.new( [:m,  :minutes],       'minutes', 'minute',
1.0*60             ),
    DurationPiece.new( [:s,  :seconds],       'seconds', 'second',
1.0                ),
    DurationPiece.new( [:ms, :milliseconds],  'ms',      'ms',
1.0/1_000          ),
    DurationPiece.new( [:us, :microseconds],  'us',      'us',
1.0/1_000_000      )
  ]

  def as_duration( options={:hours=>:show, :minutes=>:show} )
    decimal_regexp = /dec(\d*)/i
    zero_regexp    = /ifzero/i
    seconds_left   = to_f
    output         = ''
    chosen_pieces  = options.keys
    DurationPieces.map{ |piece|
      piece_name = ( piece.names & chosen_pieces ).first
      next unless piece_name
      piece_option = options[piece_name].to_s
      num_decimals = piece_option[ /dec(\d*)/oi, 1 ]
      next unless seconds_left >= piece.factor || num_decimals ||
piece_option =~ /ifzero/oi
      val = seconds_left / piece.factor
      val = if num_decimals
        ( num_decimals == '' ? '%g' : "%.#{num_decimals}f" ) % val
      else
        val.to_i
      end
      seconds_left -= val.to_f * piece.factor
      "#{val} #{val==1 ? piece.singular : piece.plural}"
    }.compact.join( ', ' )
  end
end

# The options argument should be a Hash with named symbol properties
# for each unit that should be returned:
#   :y/:years (calculated as 365.25 days)
#   :w/:weeks
#   :d/:days
#   :h/:hours
#   :m/:minutes
#   :s/:seconds
#   :ms/:milliseconds
#   :us/:microseconds
# The value used for each parameter doesn't matter (except see below).

require 'time' # For parse
t1 = Time.parse '5/1/2004 12:13 pm'
t2 = Time.parse '5/2/2004 12:17 pm'
diff = t2-t1
p diff.as_duration( :d=>true, :s=>:please_to_show_this )
#--> "1 day, 240 seconds"


# By default, if a unit has a value of 0, it will not be shown.
p diff.as_duration( :d=>1, :h=>1, :m=>1 )
#--> "1 day, 4 minutes"


# To force a 0-valued item to show, use :evenifzero as the value:
p diff.as_duration( :d=>1, :h=>:evenifzero, :m=>1 )
#--> "1 day, 0 hours, 4 minutes"


# To force a value to display with decimals, use :dec as the value.
# NOTE: This will cause any smaller units to be ignored (unless
explicitly included)
p diff.as_duration( :d=>1, :h=>:dec, :m=>1 )
#--> "1 day, 0.0666667 hours"
p diff.as_duration( :d=>1, :h=>:dec, :m=>:evenifzero )
#--> "1 day, 0.0666667 hours, 0 minutes"


# To force a specific number of decimals, append digits to the value:
p diff.as_duration( :d=>1, :h=>:dec1 )
#--> "1 day, 0.1 hours"
p diff.as_duration( :d=>1, :h=>:dec2 )
#--> "1 day, 0.07 hours"


# If the smallest unit specified is set not to show decimals, but more
than
# half of that unit is left over, the value will be truncated. Set the
smallest
# item to :dec0 to force it to round up if appropriate. For example:
t3 = Time.parse '5/1/2004 12:00:00 pm'
t4 = Time.parse '5/1/2004 1:10:59 pm'
diff2 = t4-t3

p diff2.as_duration( :d=>1, :h=>1, :m=>1, :s=>1 )
#--> "1 hour, 10 minutes, 59 seconds"
p diff2.as_duration( :d=>1, :h=>1, :m=>1 )
#--> "1 hour, 10 minutes"
p diff2.as_duration( :d=>1, :h=>1, :m=>:dec0 )
#--> "1 hour, 11 minutes"
p diff2.as_duration( :m=>:dec2 )
#--> "70.98 minutes"