Beefy Boxes and Bandwidth Generously Provided by pair Networks
good chemistry is complicated,
and a little bit messy -LW
 
PerlMonks  

Converting Seconds to Nice Format

by Segfault (Scribe)
on Nov 11, 2000 at 12:10 UTC ( [id://41079]=perlquestion: print w/replies, xml ) Need Help??

Segfault has asked for the wisdom of the Perl Monks concerning the following question:

Hi, I'm not terribly new at Perl, but one thing that has nagged me for a while is: how do I take an amount of time given in seconds into a nice, human-readable format?

For example, I'd like to read the system uptime from /proc/uptime and convert it to something like:

     3 days, 2 hours, 18 minutes

What would be the simplest approach to this? Is there a function like localtime() (but obviously that would return days, minutes, etc. for a duration, not a specific point in time) that will do this?

Replies are listed 'Best First'.
Re: Converting Seconds to Nice Format
by davorg (Chancellor) on Nov 11, 2000 at 13:09 UTC

    Or to to it without using external modules, you could do something like this:

    use strict; my $secs = 1_000_000; my $days = int($secs / 86_400); $secs -= ($days * 86_400); my $hours = int($secs / 3600); $secs -= ($hours * 3600); my $mins = int($secs / 60); $secs -= ($mins * 60); print "1,000,000 seconds is $days days, $hours hours, $mins mins and $ +secs seconds\n"
    --
    <http://www.dave.org.uk>

    "Perl makes the fun jobs fun
    and the boring jobs bearable" - me

Re: Converting Seconds to Nice Format
by btrott (Parson) on Nov 11, 2000 at 12:20 UTC
    Time::Seconds might do what you want. It's part of the Time-Object distribution.

    Use it like this:

    my $s = Time::Seconds->new(86400); print $s->days;
    This prints 3. To get it into the format you want (number of days, then number of hours, then number of minutes) you'll need to do some messing about; you can't just call days, then hours, then minutes, because each of those methods converts the seconds you *supplied* into that many days/hours/minutes. Which isn't what you want: you want the number of days, then the leftover number of hours, etc.
Re: Converting Seconds to Nice Format
by snax (Hermit) on Nov 11, 2000 at 19:28 UTC
    Similar to davorg's response, this also works:
    sub mymod ($$\$\$) { my ($a, $b, $divref, $remref) = @_; $$remref = $a % $b; $$divref = ($a - $$remref)/$b; } my ($secs, $days, $hours, $mins); $secs = 1_000_000; &mymod($secs, 86_400, \$days, \$secs); &mymod($secs, 3_600, \$hours, \$secs); &mymod($secs, 60, \$mins, \$secs); print "$days days $hours hours $mins minutes and $secs seconds.\n";

    It's not really shorter, but I like using

    %
    :)
Re: Converting Seconds to Nice Format
by jepri (Parson) on Nov 11, 2000 at 20:38 UTC
    Find it on CPAN - Date::Manip

    Warning: Untested code

    use Date::Manip; $secs1 = time(); $secs2 = $secs1-(Read from file); $date1 = &ParseDateString("epoch $secs1"); $date2 = &ParseDateString("epoch $secs2"); $delta=&DateCalc($date1,$date2,\$err); # => 0:0:WK:DD:HH:MM:SS the weeks, days, hours, minutes, and seco +nds between the two $delta=&DateCalc($date1,$date2,\$err,1); # => YY:MM:WK:DD:HH:MM:SS the years, months, etc. between the two

    ____________________
    Jeremy

Re: Converting Seconds to Nice Format
by Anonymous Monk on Nov 11, 2000 at 22:02 UTC
    Since your looking in /proc I assume you're running linux?

    $uptime = `uptime`; $uptime =~ s/.*(\d+ days, \d+ min).*/$1/;

    This doesn't give you seconds, of course.

(tye)Re: Converting Seconds to Nice Format
by tye (Sage) on Nov 12, 2000 at 09:56 UTC

    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")
      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")
Re: Converting Seconds to Nice Format
by merlyn (Sage) on Nov 12, 2000 at 02:04 UTC
Re: Converting Seconds to Nice Format
by Anonymous Monk on Nov 12, 2000 at 05:56 UTC
    Something like this:
    $up = `cat /proc/uptime`; $up =~ m/[^ .]*/; $upseconds = $&; $days = (int($upseconds / (60*60*24))) % 7; $hours = (int($upseconds / (60*60))) % 24; $minutes = (int($upseconds / 60)) % 60; $weeks = (int($upseconds / (60*60*24*7))) % 52; $years = (int($upseconds / (60*60*24*365))) % 10; $decades = (int($upseconds / (60*60*24*365*10)));
      Your code looks file, but I would suggest that you may want to reorder the lines so it looks a little better.
      $up = `cat /proc/uptime`; $up =~ m/[^ .]*/; $upseconds = $&; $seconds = (int($upseconds)) % 60; $minutes = (int($upseconds / (60))) % 60; $hours = (int($upseconds / (60*60))) % 24; $days = (int($upseconds / (60*60*24))) % 7; $weeks = (int($upseconds / (60*60*24*7))) % 52; $years = (int($upseconds / (60*60*24*365))) % 10; $decades = (int($upseconds / (60*60*24*365*10)));
Re: Converting Seconds to Nice Format
by Dr. Mu (Hermit) on Nov 12, 2000 at 09:42 UTC
    The following subroutine, with a one-line join, should do the trick:
    sub PrettyTime { my $s = int(shift); join(', ', reverse map {my $t = $s % $_->[0]; $s /= $_->[0]; $t.$_-> +[1].($t > 1 ? 's' : '')} ([60, ' sec'], [60, ' min'], [24, ' hr'], [9 +999999, ' day'])) }
    Limitations are integer seconds and up to 9999999 days. Both would be easy to remedy. But it does handle singular and plural units. For example, print PrettyTime(1234561) prints 14 days, 6 hrs, 56 mins, 1 sec Eliminating zeroes is harder, though, because join sticks its delimiter in even when map returns undef. Leading zeroes are no easier either, because map doesn't respond to a last. For these you might have to resort to a foreach with an internal unshift (eliminates the reverse) into a results array. Then do the join.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://41079]
Approved by root
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others perusing the Monastery: (2)
As of 2024-12-08 08:49 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    Which IDE have you been most impressed by?













    Results (50 votes). Check out past polls.