Beefy Boxes and Bandwidth Generously Provided by pair Networks
No such thing as a small change

Formatting elapsed time

by grinder (Bishop)
on Sep 06, 2001 at 16:37 UTC ( #110550=snippet: print w/replies, xml ) Need Help??

The following snippet turns a number of seconds to a compact string representing the equivalent amount of seconds in weeks, days, hours, minutes and seconds. This is useful for reporting elapsed time in an easily-graspable human-readable format (just how many weeks or days 123 456 789 seconds anyway)?

Here are some examples


The code has a rather pleasing symmetry.

sub wdhms {
    my( $weeks, $days, $hours, $minutes, $seconds, $sign, $res ) = qw/
+0 0 0 0 0/;

    $seconds = shift;
    $sign    = $seconds == abs $seconds ? '' : '-';
    $seconds = abs $seconds;

    ($seconds, $minutes) = ($seconds % 60, int($seconds / 60)) if $sec
    ($minutes, $hours  ) = ($minutes % 60, int($minutes / 60)) if $min
    ($hours,   $days   ) = ($hours   % 24, int($hours   / 24)) if $hou
    ($days,    $weeks  ) = ($days    %  7, int($days    /  7)) if $day

    $res = sprintf '%ds',     $seconds;
    $res = sprintf "%dm$res", $minutes if $minutes or $hours or $days 
+or $weeks;
    $res = sprintf "%dh$res", $hours   if             $hours or $days 
+or $weeks;
    $res = sprintf "%dd$res", $days    if                       $days 
+or $weeks;
    $res = sprintf "%dw$res", $weeks   if                             
+   $weeks;

    return "$sign$res";
Replies are listed 'Best First'.
Re: Formatting elapsed time
by Hofmator (Curate) on Sep 06, 2001 at 17:34 UTC

    Am I overlooking something or could your sprintf statements be written much simpler like this?

    $res .= sprintf "%dw", $weeks if $weeks; $res .= sprintf "%dd", $days if $days; $res .= sprintf "%dh", $hours if $hours; $res .= sprintf "%dm", $minutes if $minutes; $res .= sprintf '%ds', $seconds;

    Update: That was changed a little bit too much, as grinder correctly remarked ... nevertheless, my main concern were those ugly string interpolations, and I think this works:

    $res .= sprintf "%dw", $weeks if $we +eks; $res .= sprintf "%dd", $days if $days or $we +eks; $res .= sprintf "%dh", $hours if $hours or $days or $we +eks; $res .= sprintf "%dm", $minutes if $minutes or $hours or $days or $we +eks; $res .= sprintf '%ds', $seconds;

    -- Hofmator

      Yes they could, but it would change the behaviour of the routine. For instance, 86401 would produce 1d1s instead of 1d0h0m1s.

      Which may be what you want (ultra-compact!), but it wasn't what I needed.

      update: re your update: you're quite right. Adding the $res into the format string is a bit too clever by half. I completely overlooked .= . It's feedback like this that make Perl Monks such a useful site.

      g r i n d e r
Re: Formatting elapsed time
by m-rau (Scribe) on Feb 10, 2005 at 17:48 UTC
    The following code is a bit more complex. Sorry. But it produced output reflecting the amount of seconds. If something runs short, it says 3 seconds. If something takes longer, it says 1 hour, 3 seconds. If it takes even longer, the script can say 1 year, 3 monhts, 12 weeks, 4 days, 1 hour, 3 minutes, 20 seconds.
    The code is implemented as a runtime tracker.
    #!/usr/bin/perl my $t0; BEGIN { $t0 = time; } END { my $d = time() - $t0; my @int = ( [ 'second', 1 ], [ 'minute', 60 ], [ 'hour', 60*60 ], [ 'day', 60*60*24 ], [ 'week', 60*60*24*7 ], [ 'month', 60*60*24*30.5 ], [ 'year', 60*60*24*30.5*12 ] ); my $i = $#int; my @r; while ( ($i>=0) && ($d) ) { if ($d / $int[$i] -> [1] >= 1) { push @r, sprintf "%d %s%s", $d / $int[$i] -> [1], $int[$i]->[0], ( sprintf "%d", $d / $int[$i] -> [1] ) > 1 ? 's' : ''; } $d %= $int[$i] -> [1]; $i--; } my $runtime = join ", ", @r if @r; warn sprintf "RUNTIME %s\n", $runtime; } my $runTime = rand( 10 ); printf "Runtime is %d\n", $runTime; sleep( $runTime );
      Well, hopefully it takes those 12 weeks and changes that into 2 more months :-)

      As a very minor nit, the definitions of year and month may need to be adjusted depending on just how precise one wants to be. There are several definitions/equivalencies for the exact length of a year located here.

      For example, using the current Sidereal year's approximation as 365.2564, a month is more accurately said to be 365.2564/12 or 30.438 rather than 30.5, and a year based on 30.5 * 12 is 366 days long...


Log In?

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: snippet [id://110550]
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others scrutinizing the Monastery: (3)
As of 2023-12-02 19:07 GMT
Find Nodes?
    Voting Booth?
    What's your preferred 'use VERSION' for new CPAN modules in 2023?

    Results (18 votes). Check out past polls.