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?
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
| [reply] [d/l] |
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. | [reply] [d/l] |
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 % :) | [reply] [d/l] |
Re: Converting Seconds to Nice Format
by jepri (Parson) on Nov 11, 2000 at 20:38 UTC
|
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 | [reply] [d/l] |
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.
| [reply] [d/l] |
(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") | [reply] [d/l] [select] |
|
$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).
| [reply] [d/l] [select] |
|
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")
| [reply] [d/l] [select] |
|
|
Re: Converting Seconds to Nice Format
by merlyn (Sage) on Nov 12, 2000 at 02:04 UTC
|
| [reply] |
Re: Converting Seconds to Nice Format
by Anonymous Monk on Nov 12, 2000 at 05:56 UTC
|
$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)));
| [reply] [d/l] |
|
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)));
| [reply] [d/l] |
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. | [reply] [d/l] [select] |
|
|