Ordinal numerals and JavaScript
June 28, 2020 at 12:18 PM by Dr. Drang
A couple of days ago, this question in the Keyboard Maestro forum led me to learn a couple of things about JavaScript. The user, sandbags, wanted a good way to format dates using ordinal numerals instead of plain old digits. In other words, he wanted his date string to come out as “June 26th, 2020” instead of “June 20, 2020.”
The strftime
function, which several scripting languages use, would be my usual go-to for formatting a date, but it doesn’t include a specifier for the ordinal version of the day of the month. So I decided I had nothing to add to the forum discussion, which already had a couple of JavaScript solutions.
But the question stuck with me. Although I don’t use ordinal numbers often, it would be nice to have some code in my back pocket when the need arises. A quick visit to Google unearthed this remarkably compact JavaScript function from jlbruno (which I have renamed and reformatted slightly):
javascript:
1: function ordinal(n) {
2: var s = ["th", "st", "nd", "rd"];
3: var v = n%100;
4: return n + (s[(v-20)%10] || s[v] || s[0]);
5: }
The key to getting the ordinal version of a number (in English) is to recognize the following rules:
- Usually, we just add a “th” suffix: 4th, 5th, 28th, 546th.
- Exceptions occur when the last digit is 1, 2, or 3: 1st, 2nd, 3rd, 52nd, 171st.
- Exceptions to the exceptions occur when the last two digits are 11, 12, or 13: 11th, 12th, 13th, 312th, 411th.
It wouldn’t be particularly difficult to code this in any language, but what makes jlbruno’s function so short is that it takes advantage of three JavaScript design decisions, all of which are used in Line 4:
- At the outer level, there is the short-circuiting Boolean operator,
||
. In JavaScript, this returns the value of the first operand if it’s true and the value of the second operand otherwise. - One step in, it leverages JavaScript’s treatment of indices to an array. If you pass an array an index that’s outside the range, it returns
undefined
, which is considered false. Significantly, JavaScript treats all negative numbers as outside the range. This differs from many scripting languages, which treat negative indices as counting back from the end of the array. - At the innermost level, it uses the JavaScript’s implementation of the modulo operator,
%
, which returns a negative number when its first operand (the dividend) is negative. While all languages return the same modulo result when both operands are positive, they differ when one or both of the operands are negative. Python and Ruby’s%
, for example, don’t work the way JavaScript’s does.
We can see how ordinal
uses these features by considering a few examples. We’ll look at numbers from 0 to 99 only, as that’s what the modulo operator in Line 3 leaves us with.1
- For
n = 38
, we start by calculating(38 - 20)%10 = 8
. Becauses[8]
is undefined, we move on tos[38]
which is also undefined. Thus, the suffix gets the value ofs[0]
, which is “th.” The function returns “38th.” - For
n = 61
, we start by calculating(61 - 20)%10 = 1
. Becauses[1]
is “st,” that becomes the suffix, and the function returns “61st.” - For
n = 13
, we start by calculating(13 - 20)%10 = -7
. Becauses[-7]
is undefined, we move on tos[13]
, which is also undefined. This leaves us with a suffix ofs[0]
, which is “th.” The function returns “13th.” - Finally, for
n = 2
, we start with(2 - 20)%10 = -8
. Becauses[-8]
is undefined, we move on tos[2]
, which is “nd.” The function returns “2nd.”
Whether this is a fast JavaScript function or not, I couldn’t say, but I can’t help but admire its cleverness.
-
If you pass a negative number into
ordinal
, you’ll always get a “th” suffix. That’s not really a problem. Ordinal numbers are always positive (or at least non-negative), so you should never try to get the ordinal version of a negative number. ↩