Fast Time-of-Day Conversion (Hours, Minutes, Seconds)

Two new algorithms.

This is an early placeholder blog post draft. More algorithms and explanation will be added later. The URL and anchor tag will not change.

Initial draft: 17 August 2026

The attention so far through these Fast Date articles has beeen mostly about dates, but time of day is also an interesting problem, and as usual, the default intuitive approach is not the fastest.

A date library will often perform a complete decomposition by performing the following steps:

  1. A timestamp is usually a single unit, measured in either seconds, milliseconds, or some other sub-second unit, offset from an epoch (such as for Unix: 1970-01-01 00:00:00 UTC).
  2. A timezone is usually measured in minutes or seconds offset from UTC.
  3. The timezone offset is scaled to the same unit-size as the timestamp, and both are summed to obtain a "local_timestamp".
  4. local_timestamp is broken into whole-days (rd) and sub-day parts (time) by division and modulus (eg. for seconds, using divisor 86400).
  5. rd is sent to a date decomposition function (the type discussed in articles 1-3).
  6. time is sent to a time decomposition function (the type discussed in this article).

Note: leap seconds complicate this process somewhat, but given Unix time ignores leap seconds, they are outside the scope of this article.

My initial thought was, given that the functions to process rd and time are completely independent, and given that the date function is fairly complex, it should seem that even if the time logic can be streamlined, it shouldn't make much difference, as it can be processed "in the shadow" of the date algorithm (Instruction-level parallelism).

This assumption turned out to be wrong.

We first look at the way time is decomposed in most date libraries, including the Linux kernel, glibc, Go's standard library, CPython's datetime, OpenJDK's java.time and the .NET runtime:

The intuitive sequential approach
  1. hour   = time / 3600;
  2. rem    = time % 3600; // Compiles to: time - hour * 3600
  3. minute = rem / 60;
  4. second = rem % 60; // Compiles to: rem - minute * 60

Notice that there is a complete dependency chain throughout this entire process: second depends on minute, which depends on rem, which depends on hour. This gives the processor no choice but to wait for each to be computed in sequence.

V8 and Boost and musl each compute instead appear to compute these directly:

The intuitive sequential approach
  1. hour   = time / 3600
  2. minute = (time / 60) % 60
  3. second = time % 60

This looks like it could in theory be faster for a superscalar processor, each of the computations can begin in parallel, but when we look at the computation of minute it compiles to: a = time / 60; minute = a - a / 60 * 60. This is, three chained expensive operations.

With a simple bit of shuffling, we can structure the entire thing to be quite parallel, with only at most two chained expensive operations:

[NEW] The Unintuitive parallel approach
  1. tmin   = time / 60; // "Total Minutes"
  2. hour   = time / 3600;
  3. second = time - tmin * 60;
  4. minute = tmin - hour * 60;

In contrast to the previous approach, there is now a great deal of parallel logic in this different structure:

  • The computation of tmin and hour can occur simultaneously.
  • Once they complete, computation of minute and second can also overlap, as they have no co-dependency.

Finally, we can utilise the identity from our previous weekday article for a final speed boost:

xmodD=(x+cxD)mod(D+c)

In this case, setting c = 4 gives us the useful version where the mod becomes % 64:

[NEW] The very unintuitive modulus-padding approach
  1. tmin   = time / 60; // "Total Minutes"
  2. hour   = time / 3600;
  3. second = (time + 4 * tmin) % 64;
  4. minute = (tmin + 4 * hour) % 64;

Stay tuned, this article will be updated soon with much more detail and explanation.



GitHub revision: e35f46b8e1a2d35433dc1b84f2ea02d9166b1602