Number of seconds as English duration

Discussion in 'Ruby' started by Phrogz, Aug 27, 2007.

  1. Phrogz

    Phrogz Guest

    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"
    Phrogz, Aug 27, 2007
    #1
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. =?Utf-8?B?UmFlZCBTYXdhbGhh?=

    English/English DLL

    =?Utf-8?B?UmFlZCBTYXdhbGhh?=, Oct 15, 2005, in forum: ASP .Net
    Replies:
    2
    Views:
    1,671
    =?Utf-8?B?UmFlZCBTYXdhbGhh?=
    Oct 16, 2005
  2. IchBin
    Replies:
    1
    Views:
    770
  3. Replies:
    9
    Views:
    378
  4. Michael Tan
    Replies:
    32
    Views:
    964
    Ara.T.Howard
    Jul 21, 2005
  5. luke
    Replies:
    4
    Views:
    115
    Gene Tani
    Jul 7, 2005
Loading...

Share This Page