hillam/ctt-server

improve time display

Closed this issue · 5 comments

Time is stored in the database in seconds. For the first iteration of displaying time on user profiles, I simply converted to hours and rounded to 2 decimal places. Some sites with very little time still show 0.0 hours (less ~36 seconds). I need a better way to display this...

To display hours or minutes or seconds, do something like this:

if seconds >= 3600
  seconds / 3600.0 + " hours" # round this to 1 decimal place
elsif seconds >= 60
  seconds / 60.0 + " minutes" # round this to 1 decimal place
else 
  seconds + " seconds"

Pros

  • Display lower time values with greater precision.
  • More plain English

Cons

  • Could jumble the display

Here's my JS implementation of the above.. is_diff is a trick to prevent non-decimal values from being forced to show 1 decimal place.

// time: time in seconds
function format_time(time){
    if(time >= 3600){
        return (time / 3600.0).toFixed(is_diff(time, 3600)) + " hours";
    }
    else if(time >= 60.0){
        return (time / 60.0).toFixed(is_diff(time, 60)) + " minutes";
    }
    else{
        return time + " seconds";
    }
}

// returns 0 if the numbers are equal, 1 otherwise
function is_diff(num1, num2){
    return Math.abs(Math.sign(num1 - num2));
}

I can't help but look at this and think there is a way to write this using a strategy pattern-esque thing to prevent repetition and easily add more units (days/months/years)...

Here's a DRY solution implemented in the client popup.js.

// input:   time in seconds
// output:  "3.4 minutes", "3 weeks", "1 day", for example
function format_time(time){
    var units = [
        {
            name:       "year",
            seconds:    31536000
        },
        {
            name:       "month",
            seconds:    2592000
        },
        {
            name:       "week",
            seconds:    604800
        },
        {
            name:       "day",
            seconds:    86400
        },
        {
            name:       "hour",
            seconds:    3600
        },
        {
            name:       "minute",
            seconds:    60
        },
        {
            name:       "second",
            seconds:    1
        }
    ];

    for(var unit of units){
        if(time >= unit.seconds){
            return (time / unit.seconds).toFixed(is_divisible(time, unit.seconds)) + " " + unit.name +
                    Array(1 + is_equal(time, unit.seconds)).join('s'); // plural or not
        }
    }

    // returns 0 if the numbers are equal, 1 otherwise
    function is_equal(num1, num2){
        return Math.abs(Math.sign((num1/num2).toFixed(1) - 1));
    }

    // returns 0 if the first number is divisible by the second, 1 otherwise
    function is_divisible(num1, num2){
        return Math.abs(Math.sign((num1/num2).toFixed(1) % 1));
    }
}

TODO: Implement in RoR and replace all instances of "# hours".

Ruby implementation is a bit more cryptic than JS..

def format_time num
    units = [
        {
            name:       "year",
            seconds:    31536000.0
        },
        {
            name:       "month",
            seconds:    2592000.0
        },
        {
            name:       "week",
            seconds:    604800.0
        },
        {
            name:       "day",
            seconds:    86400.0
        },
        {
            name:       "hour",
            seconds:    3600.0
        },
        {
            name:       "minute",
            seconds:    60.0
        },
        {
            name:       "second",
            seconds:    1.0
        }
    ]

    # 0 if the numbers are equal, 1 otherwise
    is_equal = Proc.new do |num1, num2|
        # ~= Math.abs(Math.sign((num1/num2).toFixed(1) - 1));
        ((('%.1f' % (num1 / num2)).to_f - 1) <=> 0).abs
    end

    # 0 if the first number is divisible by the second, 1 otherwise
    is_divisible = Proc.new do |num1, num2|
        # ~=Math.abs(Math.sign((num1/num2).toFixed(1) % 1));
        ((('%.1f' % (num1 / num2)).to_f % 1) <=> 0).abs
    end

    units.each do |unit|
        if num >= unit[:seconds]
            dec_format  = ('%.' + (is_divisible.call(num, unit[:seconds])).to_s + 'f') % (num / unit[:seconds])
            s           = 's' * is_equal.call(num, unit[:seconds]) # 's' or ''
            return dec_format + " " + unit[:name] + s
        end
    end
end

Implemented the above in rails on cf89db5