Helper functions: time formatting

Today we embark upon a journey into the wide world of helper functions, aka the things you probably never think about because you just import them from lodash or whatnot.

Let's say you're building a video UI, and you know you'll be getting video data from the server. That data will be an object with properties like:

{
  name: 'Great video!',
  thumbnail: 'nice-thumbnail.jpg',
  videoSource: 'video-file.mp4',
  durationInSeconds: 403,
  ...
}

You want to show the duration on a pretty overlay, but to do that you need to go from the durationInSeconds to a more readable HH:MM:SS format. You could accomplish this with the moment library, but in order to do that you'd have to require all of moment, which is a little heavy for just this one operation.

Enter this beautiful one-liner to get from seconds to HH:MM:SS (Hat tip to Stack Overflow users Harish Anchu and Frank):

const formatSecondsAsHHMMSS = valueInSeconds =>
  new Date(valueInSeconds * 1000).toISOString().substr(11, 8);

To dig into this function a bit, what we're doing is as follows:

  • multiplying the seconds by 1000 because the Date constructor traffics in milliseconds
  • converting the resulting milliseconds value to an ISO 8601 string. This string will, depending where you live, have a date of sometime in 1969 or 1970, but that's fine, because we don't care about the actual date, just the time.
  • grabbing just the time portion of that resulting string via the handy-dandy substr function.

That's a great start, but there's also room to iterate here. For example, maybe you don't want to show a leading zero:

const formatSecondsAsHHMMSS = valueInSeconds => {
  const result = new Date(valueInSeconds * 1000).toISOString().substr(11, 8);
  if (result.charAt(0) === '0') return result.substr(1);
  return result;
};
formatSecondsAsHHMMSS(403);
// --> "0:06:43"

Or you could tweak the substr to send back just MM:SS:

const formatSecondsAsMMSS = valueInSeconds =>
  new Date(valueInSeconds * 1000).toISOString().substr(14, 5);
formatSecondsAsMMSS(403);
// --> "06:43"

Or you could tweak it to send back just MM:SS if hours is 0:

const formatSecondsAsHHMMSSWithTrimming = valueInSeconds => {
  const result = new Date(valueInSeconds * 1000).toISOString().substr(11, 8);
  if (result.substr(0, 3) === '00:') return result.substr(3);
  return result;
};
formatSecondsAsHHMMSSWithTrimming(403);
// --> "06:43"
formatSecondsAsHHMMSSWithTrimming(13403);
// --> "03:43:23"

Or you could recursively check for zeroes and trim them all off:

const formatSecondsAsHHMMSSWithRecursiveTrimming = valueInSeconds => {
  let result = new Date(valueInSeconds * 1000).toISOString().substr(11, 8);
  while (result.charAt(0) === '0' || result.charAt(0) === ':')
    result = result.substr(1);
  return result;
};
formatSecondsAsHHMMSSWithRecursiveTrimming(403);
// --> "6:43"
formatSecondsAsHHMMSSWithRecursiveTrimming(13403);
// --> "3:43:23"

Each of these serves a different use case, and depending on your implementation one or the other will make the most sense. Maybe you'll want to pass in an options argument to allow this nice helper function to be as flexible as you please:

const formatSecondsAsHHMMSSWithOptions = (
  valueInSeconds,
  options = { trimLeadingZeroes: false }
) => {
  let result = new Date(valueInSeconds * 1000).toISOString().substr(11, 8);
  if (options.trimLeadingZeroes) {
    while (result.charAt(0) === '0' || result.charAt(0) === ':')
      result = result.substr(1);
  }
  return result;
};
formatSecondsAsHHMMSSWithOptions(403);
// --> "00:06:43"
formatSecondsAsHHMMSSWithOptions(403, {
  trimLeadingZeroes: true,
});
// --> "6:43"

I could go on like this, but maybe we'll stop there for today. The point is... well, I'm not sure what the point is, but I think it has something to do with learning being fun. Which it is! So, bye-bye for now, see you next time. ⏱