in reply to Converting Seconds to Nice Format

I'm surprised that everyone is computing 60*60*24*365 instead of computing the values in an order that makes this calculation unnecessary [ update: except for the above by Dr. Mu which was entered while I typed this one :) ].

#!/usr/bin/perl -wl use strict; print join "\n", map { secs2str( $_ ) } <DATA>; exit( 0 ); { my( @div, @unit ); BEGIN { @div= ( 60, 60, 24, 365 ); @unit= qw( second minute hour day year ); } sub secs2str { my( $left )= @_; my @val= ( map( { my $unit= $left; $left= int( $left / $_ ); $unit -= $left * $_; } @div ), $left ); @val= map { 0 == $val[$_] ? () : "$val[$_] $unit[$_]" . ( 1 == $val[$_] ? "" : "s" ) } reverse 0..$#val; return 0==@val ? "now" : 1==@val ? $val[0] : 2==@val ? join( " and ", @val ) : join ", ", @val[0..$#val-1], "and $val[-1]"; } } __END__ 0 1 10 100 1020 10000.5 100840 1000900 10000000 100000020.5 1000000000
prints
now 1 second 10 seconds 1 minute and 40 seconds 17 minutes 2 hours, 46 minutes, and 40.5 seconds 1 day, 4 hours, and 40 seconds 11 days, 14 hours, 1 minute, and 40 seconds 115 days, 17 hours, 46 minutes, and 40 seconds 3 years, 62 days, 9 hours, 47 minutes, and 0.5 seconds 31 years, 259 days, 1 hour, 46 minutes, and 40 seconds
I'd put fractions other than 0.5 in but then I'd have to include logic to remember number of digits after the decimal place and use that to trim the output (or just show ugly output). (:

        - tye (but my friends call me "Tye")

Replies are listed 'Best First'.
RE: (tye)Re: Converting Seconds to Nice Format
by snax (Hermit) on Nov 13, 2000 at 17:27 UTC
    You, too, are computing 60*60*24*365 -- you are simply hiding the computation in the routine. One could argue that, for an implementation like this, simply doing the multiplication once and coding the integers directly is a cleaner, faster route. Or not :)

    Anyway, in my continuing crusade as champion for the lovely % I would like to point out that

    $left= int( $left / $_ ); $unit -= $left * $_;
    could be "better" written as
    $left= int( $left / $_ ); $unit %= $_;
    Toss in a
    use integer;
    (in the subroutine BLOCK) and you can drop the call to int() as well - but be careful with this since this makes calls to % platform dependent (usually only an issue with negative numbers, not a problem here).

      You, too, are computing 60*60*24*365

      Yes, but the point was that most of the other samples compute it directly and so they end up computing 60*60 three times and 60*60*24 twice as well as repeating the "number of seconds per minute" 4 times, "minutes per hour" 3 times, etc. I guess the added computations are done at compile time so they matter very little. But if anything I think not computing it directly is likely to be slightly faster and a little cleaner.

      Not that any of that is a big deal. I just saw the question, thought of how I've done that many times, figured several people were probably already composing answers, and decided I'd spend my time on something else. Later I wandered back and found quite a few answers, none of which did it the way I usually do. I didn't think my way was so strange so I decided to post it as an example of a different approach and I mentioned the key difference. I wasn't trying to claim that all of the other answers were inferior, in case you got that impression.

      $left= int( $left / $_ ); $unit %= $_;

      FYI, I'm don't think that saves you any computations since % just does a divide and takes a remainder. So you have done two divides where my method only does one divide and one multiply. I actually consciously chose my method over using % to avoid the duplicate divide, but that was probably a mistake on my part. I should have just gone with the cleaner code as you suggest. Thanks.

      I don't recall how floating-point remainders are computed at the machine level. It seems that integer remainders under C had the property that if you did an integer divide next to computing the corresponding remainder (modulus), then most platforms optimized that to a single instruction. But I doubt that would apply to a Perl script even if you'd done use integer. But worrying about these kinds of details is rarely worth it (other than for the fun of it).

      Toss in a use integer;

      ...and the code wouldn't work on nearly as many input values. I'm probably use integer's single biggest detractor. Without use integer you get (about) 58-bit integers in Perl. With use integer you get 32-bit integers. I guess you could argue that noone needs to nicely format the number of seconds in over 136 years, but I don't want to restrict my routine just for sake of saving 5 characters [ especially not by adding 12 characters :) ].

              - tye (but my friends call me "Tye")
        Whoops! Didn't mean to sound confrontational. Mostly I was looking through and saw a place I thought
        %
        would be better used -- and I will admit I'm fuzzy on the actual under-the-hood implementation of this in Perl. I had it pegged as at least as fast as the multiplication and subtraction but it might be slower -- I just don't know. It fits my eye, end of story.

        Regarding the 60*60*24*365 issue, I still think it's best to just explicitly code 60, 3600, etc (and perhaps include the product notation as a comment).

        I will add that your example is creative and exploits the power of Perl, but at the same time some of the others are more transparent to casual perusers of the code. For enthusiasts I like yours, but if you're sharing with other maintainers a more "obvious" might be appropriate.