18

I've got a script that takes in a value in seconds (to 2 decimal points of fractional seconds):

$seconds_input = 23.75

I then convert it to milliseconds:

$milliseconds = $seconds_input * 1000; // --> 23750

And then I want to format it like so:

H:M:S.x // --> 0:0:23.75

Where 'x' is the fraction of the second (however many places after the decimal there are).

Any help? I can't seem to wrap my mind around this. I tried using gmdate() but it kept lopping off the fractional seconds.

Thanks.

AJB
  • 7,389
  • 14
  • 57
  • 88
  • http://us.php.net/manual/en/function.sprintf.php – scragz Jan 21 '11 at 20:54
  • Just wanted to add in that this was to produce an input value for FFMPEG's -ss field. By including the fractional seconds it improves the granularity of the target frame and gives the user almost the *exact* frame they're looking at in the video player. Thanks again everybody, you saved me a tonne of time. – AJB Jan 21 '11 at 21:37

4 Answers4

30

Edit: Well, I was a bit hasty. Here's one way to do what you're asking:

function formatMilliseconds($milliseconds) {
    $seconds = floor($milliseconds / 1000);
    $minutes = floor($seconds / 60);
    $hours = floor($minutes / 60);
    $milliseconds = $milliseconds % 1000;
    $seconds = $seconds % 60;
    $minutes = $minutes % 60;

    $format = '%u:%02u:%02u.%03u';
    $time = sprintf($format, $hours, $minutes, $seconds, $milliseconds);
    return rtrim($time, '0');
}
ircmaxell
  • 163,128
  • 34
  • 264
  • 314
  • Certainly not the only way, but it's smart and easy, though. – GolezTrol Jan 21 '11 at 20:55
  • From what I gather, they are talking about a duration though, and not a datetime. – scragz Jan 21 '11 at 20:55
  • That could be a problem, but only if the duration is over 24 hours. – GolezTrol Jan 21 '11 at 20:59
  • Yeah, I've just been trying this and I'm getting 18:Dec:st.0. I think this is the right direction though, thanks ircmaxell. – AJB Jan 21 '11 at 21:03
  • you need to make sure that _leading_ zeroes are included in the milliseconds field, though. 1 millisecond should read .001, not .1, which is what the code above would produce. – Alnitak Jan 21 '11 at 21:06
  • I love Stack Overflow. Thanks everybody. I'm not even sure which one I like best. – AJB Jan 21 '11 at 21:34
  • This solution doesn't really work with all possible test data. It doesn't properly zero-pad hours, can leave trailing decimal points, and is subject to floating-point rounding errors. – Peter Bailey Jan 21 '11 at 21:36
  • @Peter: It can leave trailing decimal points? Where? As far as Zero-padding hours, does it need to? It's definitely subject to floating-point-rounding errors, but the lower-precision values are done first, so that should't really impact the answer for most sane inputs... Unless I'm mistaken... – ircmaxell Jan 21 '11 at 21:39
  • I ran yours next to mine with the same test data - the differences were clear. Mind you, I'm not saying mine is right - I took some liberties in interpreting requirements from what was *not* said. – Peter Bailey Jan 21 '11 at 21:44
  • @Peter: The only 2 issues were when you had non-integer millisecond times, and the very large second. But that was based on the assumption above that he had an integer number of milliseconds. I guess the better solution depends upon the requirements (which we are only guessing about here) – ircmaxell Jan 21 '11 at 21:51
21

My take

function formatSeconds( $seconds )
{
  $hours = 0;
  $milliseconds = str_replace( "0.", '', $seconds - floor( $seconds ) );

  if ( $seconds > 3600 )
  {
    $hours = floor( $seconds / 3600 );
  }
  $seconds = $seconds % 3600;


  return str_pad( $hours, 2, '0', STR_PAD_LEFT )
       . gmdate( ':i:s', $seconds )
       . ($milliseconds ? ".$milliseconds" : '')
  ;
}

And then the test

$testData = array(
    23,              // Seconds, w/o millis
    23.75,           // Seconds, w/millis
    23.75123456789,  // Lots of millis
    123456789.75    // Lots of seconds
);

foreach ( $testData as $seconds )
{
  echo formatSeconds( $seconds ), PHP_EOL;
}

which yields

00:00:23
00:00:23.75
00:00:23.75123456789
34293:33:09.75
Peter Bailey
  • 105,256
  • 31
  • 182
  • 206
  • 1
    I love Stack Overflow. Thanks everybody. I'm not even sure which one I like best. – AJB Jan 21 '11 at 21:35
  • I am a php noob, I found that @ircmaxell's didn't work for all the values. In my case, ms value was: 1189107. I get around 23hrs using ircmaxell function and 00.19:49 using yours. – zengr Nov 30 '12 at 02:01
  • @Peter Bailey - I think line 6 should read: `if ( $seconds >= 3600 )` - i.e. if $seconds is greater than _or equal to_ 3600. This will ensure that exactly 1 hour - i.e. 3600 seconds, is output as `01:00:00` rather than `00:00:00` without this adjustment. Thank you for the function - I am using it watchdog logging for some performance monitoring of a Drupal site. – therobyouknow Jan 10 '20 at 11:32
3

Mine is much less readable, so it must be better. :p

Basically the same idea as @ircmaxell's version. It does trim the trailing '0's and even will skip the last '.' separator if milliseconds are 0.

<?

function format_period($seconds_input)
{
  $hours = (int)($minutes = (int)($seconds = (int)($milliseconds = (int)($seconds_input * 1000)) / 1000) / 60) / 60;
  return $hours.':'.($minutes%60).':'.($seconds%60).(($milliseconds===0)?'':'.'.rtrim($milliseconds%1000, '0'));
}

echo format_period(23.75);
GolezTrol
  • 114,394
  • 18
  • 182
  • 210
2

if you really want to do it using date function you are right, you have to deal with milliseconds externally, is only based on second-timestamps.

you could do something like this:

<?
$input = "23.75";
$seconds = floor($input);
$date = DateTime::createFromFormat('s', floor($seconds));
$ms = ($input-$seconds);
if($ms == 0) {
$ms = "";
} else { 
$ms = ltrim($ms,"0,");
}
echo $date->format('H:i:s').$ms;

but be aware of the hour-overflow, if your hours exceed 24 you will probably end up discarding the days.

in your case i would say your approach with floor to get the seconds is correct, and then you should probably just use modulo arithmetics like this:

<?
$totalsecs = 86400*10;

$secs = $totalsecs%60;

echo "secs: $secs \n";

$minutes = ($totalsecs - $secs) % (60*60);
?>

and so on..

AJB
  • 7,389
  • 14
  • 57
  • 88
The Surrican
  • 29,118
  • 24
  • 122
  • 168