P
Phrogz
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, lural, :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=>lease_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"
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, lural, :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=>lease_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"