From: Phrogz Date: 2007-08-28T03:15:08+09:00 Subject: 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.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"