Duration Notation

One of the things that comes up pretty frequently in JavaScript are time spans. Animations, request timeouts, debounce delays, and throttles all need time spans to be specified. They come up quite frequently, but are—almost as often—inscrutable.

Don't believe me?

How long is 360000?

How about 3600000?

Did you have to count the number of zeros?

...and then do some mental math to try to figure out how much time is meant?

It's exactly these sorts of issues that lead to buggy behavior, where a 30 second timeout could turn into 3 seconds as easily as 5 minutes.

Fortunately there are a few techniques that can make time spans much easier to work with.

Technique #1 Numeric Constants

The first, and simplest technique is to define some numeric constants.

const MILLISECONDS = 1
const SECONDS = 1000 * MILLISECONDS
const MINUTES = 60 * SECONDS
const HOURS = 60 * MINUTES
const DAYS = 24 * HOURS

Then, any time you want to express a time span, use an equation with units:

const animationDuration = 200 * MILLISECONDS

const requestTimeout = 30 * SECONDS

const cacheExpiry = 5 * DAYS

If you need multiple units, use an equation in the form a * x + b * y + c * z:

const timer = 1 * HOURS + 15 * MINUTES + 30 * SECONDS

Operator precedence will give you the right value without even needing parenthesis.

This is almost always what you want to use when dealing with time spans in JavaScript.

It's easy to write.

It's easy to edit.

It's easy to read.

It executes quickly.

The only "downside" (if you can even call it that) is that it's a bit wordy. In my experience, the extra characters and expressiveness are worth it.

Technique #2: Date.parse

If numeric constants are too verbose for your needs, Date.parse can help make things terser without sacrificing too much expressiveness.

Date.parse parses the provided date and returns the number of milliseconds since the Unix epoch. This is particularly useful if time spans are less than a day:

// Fill in the blanks:    1970-01-01T__:__:__Z
const timer = Date.parse('1970-01-01T01:15:30Z')

Just be sure to specify the date (1970-01-01) and the time zone (Z) in order for the hours, minutes, and seconds to be parsed correctly.

This technique suffers from being too clever. Without including some additional documentation it's not immediately clear why Date.parse would be used for a timer.

This brings us to our next technique...

Technique #3: Utility Functions

Although Date.parse might not be a good choice on its own, it's trivially easy to write a simple utility function that properly expresses the intent:

const timeSpan = (span) => Date.parse(`1970-01-01T${span}Z`)

const timer = timeSpan('01:15:30')

Of course, this is a naive example that doesn't include any input checking, so be sure not to use this in production.

It's not too hard to come up with a variety of alternative APIs for a timeSpan function:

// classic parameters
const timer = timeSpan(1, 15, 30)

// object parameters
const timer = timeSpan({
  hours: 1,
  minutes: 15,
  seconds: 30
})

// HH:MM:SS format
const timer = timeSpan('01:15:30')

// tagged template literal
const timer = timeSpan`01:15:30`

// ISO-8601 durations
const timer = timeSpan('PT1H15M30S')

I'll leave implementing these various API formats as an exercise for the reader. Regardless, using a utility function helps to make the intent of the duration clearer.

Fun Hack: Number Prototype Extension

Never do this. Prototype extension is pretty much always a bad idea, but sometimes it's fun to mess around with clever syntax:

Object.assign(Number.prototype, {
  hours () {
    return this * 60..minutes()
  },
  minutes () {
    return this * 60..seconds()
  },
  seconds () {
    return this * 1000..milliseconds()
  },
  milliseconds () {
    return Number(this)
  }
})

const timer = 1..hours() + 15..minutes() + 30..seconds()

In Review

If you find yourself fixing bugs in JavaScript time spans, you should consider using a different technique for representing time spans to make the correct code appear obvious and the wrong code look wrong.

Just because you can be terser doesn't mean you should. Using a few more words can save hours of debugging typos and odd timing issues.