# Number of seconds as English duration

**URL:** <https://www.ruby-forum.com/t/number-of-seconds-as-english-duration/113118>\
**Category:** Ruby\
**Created:** [August 27, 2007, 8:17pm UTC](https://www.ruby-forum.com/t/number-of-seconds-as-english-duration/113118 "2007-08-27T20:17:07Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![phrogz](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/p/2acd7d/32.png) [@phrogz](https://www.ruby-forum.com/u/phrogz)\
**Post date:** [August 27, 2007, 8:17pm UTC](https://www.ruby-forum.com/t/number-of-seconds-as-english-duration/113118/1 "2007-08-27T20:17:07Z")

</div>

I ported a function I once wrote in JavaScript ([Index of /JS/](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”
