Friday Fork: Formatting Elapsed Time in PHP

/Friday Fork /Code /PHP

I have been working on a project which got me running PHP in the command line. I wanted to know how long the script had been running since it would be running for hours at a time. As I tweaked the code and ran mini-tests I wanted to know how much my changes were improving the time it took to process.

I recorded the timestamp of when the script started and found the elapsed time it took to process with the time() function.

$start_time = time();
...
$elapsed = time()-$start_time;

I didn’t need any more precision than seconds so I didn’t bother with microtime(true). The problem was that the script (even in smaller tests) would be spitting out values like 1724 seconds. I wanted to format the elapsed time and I wanted to create something flexible enough to be used in future projects. Here is my solution:

function format_time($elapsed = 0, $format = "H:M:S", $units = 's'){

    $m_len = 60;
    $h_len = $m_len * 60;
    $d_len = $h_len * 24;
    $w_len = $d_len * 7;
    $l_len = $d_len * 365 / 12;
    $y_len = $d_len * 365;

    //Convert everything to seconds for simplicity
    switch($units){
        case 's':
            break;
        case 'm':
            $elapsed *= $m_len;
            break;
        case 'h':
            $elapsed *= $h_len;
            break;
        case 'd':
            $elapsed *= $d_len;
            break;
        case 'w':
            $elapsed *= $w_len;
            break;
        case 'l':
            $elapsed *= $l_len;
            break;
        case 'y':
            $elapsed *= $y_len;
            break;
    }

    //Default $hours, $minutes, $seconds to 0
    $years = $months = $weeks = $days = $hours = $minutes = $seconds = 0;
    
    // Check that we received a valid value in $elapsed
    if(!is_numeric($elapsed)){
        return false;
    }else{
        $years = floor($elapsed / $y_len);
        $elapsed -= $years * $y_len;

        $months = floor($elapsed / $l_len);
        $elapsed -= $months * $l_len;

        $weeks = floor($elapsed / $w_len);
        $elapsed -= $weeks * $w_len;
        
        $days = floor($elapsed / $d_len);
        $elapsed -= $days * $d_len;
        
        $hours = floor($elapsed / $h_len);
        $elapsed -= $hours * $h_len;
        
        $minutes = floor($elapsed / $m_len);
        $elapsed -= $minutes * $m_len;
        
        $seconds = $elapsed;
    }

    $time_string = preg_replace(
        array(
            '/(\\\\s)/',
            '/(\\\\S)/',
            '/(\\\\m)/',
            '/(\\\\M)/',
            '/(\\\\h)/',
            '/(\\\\H)/',
            '/(\\\\d)/',
            '/(\\\\D)/',
            '/(\\\\w)/',
            '/(\\\\W)/',
            '/(\\\\l)/',
            '/(\\\\L)/',
            '/(\\\\y)/',
            '/(\\\\Y)/'
        ),
        array(
            $seconds,
            str_pad($seconds, 2, "0", STR_PAD_LEFT),
            $minutes,
            str_pad($minutes, 2, "0", STR_PAD_LEFT),
            $hours,
            str_pad($hours, 2, "0", STR_PAD_LEFT),
            $days,
            str_pad($days, 2, "0", STR_PAD_LEFT),
            $weeks,
            str_pad($weeks, 2, "0", STR_PAD_LEFT),
            $months,
            str_pad($months, 2, "0", STR_PAD_LEFT),
            $years,
            str_pad($years, 2, "0", STR_PAD_LEFT),
        ),
        $format
    );
    
    return $time_string;
}

Parameters

int $elapsed
The amount of time (in seconds) to format.

string $format
The format of the outputted date string. A key letter escaped with a backslash will be replaced with the value of that time unit. The letters are:

'\s' //seconds
'\m' //minutes
'\h' //hours
'\d' //days
'\w' //weeks
'\l' //months
'\y' //years

Uppercase letters will be padded to two digits.

Examples for the value 12,345 seconds:
\H:\M:\S outputs 03:25:45
\hh \mm \ss outputs 3h 25m 45s
\yy \lmo \ww \dd \hh \mm \ss outputs 0y 0mo 0w 0d 3h 25m 45s

string $units
The unit of the input time: s, m, h, d, w, l, y.

Usage examples

format_time(12345)                  // returns "03:25:45"
format_time(12345, '\h:\m:\s')      // returns "3:25:45"
format_time(12345, '\h hours')      // returns "3 hours"
format_time(12345, '\m minutes')    // returns "25 minutes"
format_time(12345, '\s seconds')    // returns "45 seconds"
format_time(12345, '\hh \mm \ss')   // returns "3h 25m 45s"

Pay careful attention to your use of single and double-quotes. If you use double-quotes on your format you need to double-escape any backslashes. Example: format_time(12345, "\\hh \\mm \\ss") returns 3h 25m 45s.

To-do

Obviously this isn’t feature complete. Here are some things I plan on adding to it:

  • (strike:Support for minutes, hours, and days in $elapsed.)
  • Option to display totals when only including a unit of lower value. For example, format_time(12345, 'm') would return 206.
  • Support for floats in $elapsed and an option to output float values. For example format_time(12345.67, 'h:m:s') would return 3:25:45.67.

    • This issue is mostly resolved. The padding doesn’t account for decimals when using padded numbers.
  • (strike:Support for days, weeks, months, and years in return value.)
  • (strike:Replace my formatting system with a preg_replace. I just hate regex so I’m avoiding it.)

    • If you have a better regex for me to use, I’d be happy for some help on it. Regex is a beast I haven’t much experience with.

Fork it

This function is available as a gist on github for you to fork and use however you want. This is non-licensed public domain code.

Share on: /facebook /twitter /google+

comments powered by Disqus