Somewhat over ten years ago I was working on a project, where we measured execution time and memory consumption of PHP scripts. To display the execution time in a human-readable way, we used a function to format the timestamp provided by microtime().

<?php

class Application__Helper_Timer
{
    /**
     * Calculates the execution time of a script
     * @param float $executionTime
     * @return string
     */
    public static function formatExecutionTime($executionTime)
    {
        $seconds = $this->executionTime;
        $hours   = 0;
        $minutes = 0;
        while (($seconds - 3600) > 0) {
            $hours++;
            $seconds -= 3600;
        }
        while (($seconds - 60) > 0) {
            $minutes++;
            $seconds -= 60;
        }
        if (1 === strlen((string) $hours)) {
            $hours = '0' . $hours;
        }
        if (1 === strlen((string) $minutes)) {
            $minutes = '0' . $minutes;
        }
        $seconds = round($seconds, 4);
        if (false !== strpos($seconds, '.')) {
            list($seconds, $milliseconds) = explode('.', $seconds);
            if (1 === strlen((string) $seconds)) {
                $seconds = '0' . $seconds;
            }
        } else {
            $milliseconds = 0;
        }

        if ('00' !== $hours) {
            return sprintf('%s:%s:%s,%s', $hours, $minutes, $seconds, $milliseconds);
        }

        return sprintf('%s:%s,%s', $minutes, $seconds, $milliseconds);
    }
}

It served the purpose well and I used it in other projects as well.

Today, I needed again a way to format seconds in a way that was easily readable. Remembering that I used that many times before, I pulled it out of the archives, looked at it and started thinking how complicated it was written. There must be an easier way.

So, after pulling up my sleeves and getting to work, I came up with this solution:

<?php

declare(strict_types = 1);

namespace Formatter;

class SecondsAsTime
{
    public function __invoke(float $seconds) : string
    {
        $hours   = (int) ($seconds / 3600);
        $minutes = (int) (($seconds - $hours * 3600) / 60);
        $seconds -= ($hours * 3600 + $minutes * 60);

        return sprintf('%02d:%02d:%07.4F', $hours, $minutes, $seconds);
    }
}

Very simple and readable.

Also, I tested test, which greatly helped in the development process.

<?php

declare(strict_types = 1);

namespace FormatterTest;

use Formatter\SecondsAsTime;
use PHPUnit\Framework\TestCase;

class SecondsAsTimeTest extends TestCase
{
    public function test1PicoSecond() : void
    {
        $formatter = new SecondsAsTime();
        $this->assertEquals('00:00:00.0000', $formatter(10 ** -12));
    }

    public function test1NanoSecond() : void
    {
        $formatter = new SecondsAsTime();
        $this->assertEquals('00:00:00.0000', $formatter(10 ** -9));
    }

    public function test1MicroSecond() : void
    {
        $formatter = new SecondsAsTime();
        $this->assertEquals('00:00:00.0000', $formatter(10 ** -6));
    }

    public function test1MilliSecond() : void
    {
        $formatter = new SecondsAsTime();
        $this->assertEquals('00:00:00.0010', $formatter(10 ** -3));
    }

    public function test1CentiSecond() : void
    {
        $formatter = new SecondsAsTime();
        $this->assertEquals('00:00:00.0100', $formatter(10 ** -2));
    }

    public function test1DeciSecond() : void
    {
        $formatter = new SecondsAsTime();
        $this->assertEquals('00:00:00.1000', $formatter(10 ** -1));
    }

    public function test1Second() : void
    {
        $formatter = new SecondsAsTime();
        $this->assertEquals('00:00:01.0000', $formatter(1));
    }

    public function test1DecaSecond() : void
    {
        $formatter = new SecondsAsTime();
        $this->assertEquals('00:00:10.0000', $formatter(10 ** 1));
    }

    public function test1HectoSecond() : void
    {
        $formatter = new SecondsAsTime();
        $this->assertEquals('00:01:40.0000', $formatter(10 ** 2));
    }

    public function test1KiloSecond() : void
    {
        $formatter = new SecondsAsTime();
        $this->assertEquals('00:16:40.0000', $formatter(10 ** 3));
    }

    public function test1MegaSecond() : void
    {
        $formatter = new SecondsAsTime();
        $this->assertEquals('277:46:40.0000', $formatter(10 ** 6));
    }

    public function test1GigaSecond() : void
    {
        $formatter = new SecondsAsTime();
        $this->assertEquals('277777:46:40.0000', $formatter(10 ** 9));
    }

    public function test1TeraSecond() : void
    {
        $formatter = new SecondsAsTime();
        $this->assertEquals('277777777:46:40.0000', $formatter(10 ** 12));
    }
}