Number of seconds as English duration

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.0360024365.25 ),
DurationPiece.new( [:w, :weeks], ‘weeks’, ‘week’,
1.0
3600247 ),
DurationPiece.new( [:d, :days], ‘days’, ‘day’,
1.0360024 ),
DurationPiece.new( [:h, :hours], ‘hours’, ‘hour’,
1.03600 ),
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”

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs