Making Word Clock, A Clock Which Tells The Time With Words
Tags: eleventy, 11ty, projects, javascript
Published: 2020-08-23 19:39:16

---
title: Making Word Clock, A Clock Which Tells The Time With Words
published: true
date: 2020-08-23 19:30:00 UTC
tags: eleventy,11ty,projects,js,javascript
canonical_url: https://agckb.xyz/posts/making-word-clock/
---

> This post was originally posted on [The Algorithmic Cookbook](https://agckb.xyz/) under [Making Word Clock, A Clock Which Tells The Time With Words](https://agckb.xyz/posts/making-word-clock/)

> This post is also on [YouTube](https://www.youtube.com/watch?v=F3BXfdKgaYs).

I saw [a TikTok](https://www.tiktok.com/@tarthnyston/video/6777496610340375814) where this guy was flexing a Qlocktwo W, a watch which tells the time with words. This inspired me to make [a web version](https://faraixyz.github.io/word-clock) of it, which I did in one evening-ish (and fixed here and there, which is more like when I started writing this post and uncovered a lot of problems with it). Here&rsquo;s how.

## Prior Art

Wowed by the watch, I tried to find it online. My first search term was for &lsquo;word clock&rsquo;, I came across an [online word clock by timeanddate.com](https://www.timeanddate.com/wordclock/). It not only shows the time in words, it does so for multiple cities,and in different languages. Outclassed before I&rsquo;ve even started.

![timeanddate.com showing the current time in Harare as &ldquo;IT IS HALF PAST SEVEN&rdquo;](https://agckb.xyz/posts/making-word-clock/timedate-word-clock.png)

Looking around a bit more, it turns out the clock in the TikTok is called a [QlockTwo W](https://qlocktwo.com/eu/qlocktwo-w) and it costs at least $800.

![5 QLockTwo W watches lined up in various colours next to a label saying &lsquo;design award 2019&rsquo;](https://agckb.xyz/posts/making-word-clock/qlocktwo-w-lineup.jpg)

## Making The Grid

The first challenge was making a grid. The best way would be to generate it algorithmically, but I didn&rsquo;t (and still don&rsquo;t) know how to. Instead, I did it manually. At first I was going to rip one off online, but I decided to try and do it myself.

![Papers on a table showing sample grids](https://agckb.xyz/posts/making-word-clock/sample-grid.jpg)

After much experimentation, I managed to make this layout. Note that the underscores are for random characters.

```
# /_includes/js/grid.txt
a I T _ I S _ _ T W O N E _ _
b _ T H R E E L E V E N I N E 
c _ _ T W E L V E I G H T E N 
d _ F I V E _ S I X _ F O U R
e S E V E N _ T W E N T Y _ _
f T H I R T Y O H F O U R T Y
g _ F I F T Y _ _ T W O N E _
h _ F O U R T E E N _ F I V E
i T H R E E _ _ F I F T E E N
j _ S E V E N T E E N _ T E N 
k _ _ E I G H T E E N _ _ _ _
l _ _ _ N I N E T E E N _ _ _
m _ T W E L V E L E V E N _ _
n S I X T E E N _ O C L O C K
o T H I R T E E N _ _ _ _ _ _

```

## Turning The Grid Into Code

To build the website, I decided to use [Eleventy](https://11ty.dev) because of how extendable it is through JavaScript. The way I&rsquo;ve set it up is a big mess, but it works.

To define the grid, I created a JS file with an object containing the grid as I&rsquo;ve defined it and an order in which to read the rows (eclipses for brevity).

```
module.exports = {
  order: ['a', 'b', ..., 'o'],
  a: ['I', 'T', ...rc(1), 'I', 'S', ...rc(2), 'T', 'W', 'O', 'N', 'E', ...rc(2)],
  b: [...rc(1), 'T', 'H', 'R', 'E', 'E', 'L', 'E', 'V', 'E', 'N', 'I', 'N', 'E'],
  ...,
  o: ['T', 'H', 'I', 'R', 'T', 'E', 'E', 'N', ...rc(6)]   
}

```

You may notice various function calls like `...rc(n)`. What this does is generates n random characters and places them into the property&rsquo;s array through the [spread operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax). So `...rc(2)` will generate two random characters. Here&rsquo;s the code which generates the random characters.

```
function randChars (num) {
  const chars = []
  for (let i = 0; i < num; i++) {
    const randCharCode = 65 + Math.floor(Math.random() * 25)
    chars.push(String.fromCharCode(randCharCode))
  }
  return chars
}
const rc = randChars

```

For `num` items, it generates a random number from 65 to 90 (which corresponds to the [ASCII codes](https://en.wikipedia.org/wiki/ASCII#Printable_characters) for the letters A-Z) and pushes the corresponding ASCII character to a `chars` array, which is then returned. Not sure why I added the line `const rc = randChars` instead of renaming `randChars` to `rc` since I only used it once.

This whole thing seems terribly inefficient, but it shouldn&rsquo;t matter too much since it&rsquo;s for building the site. It still builds pretty quick. Then again, the really slow stuff comes later.

To show this grid on the webpage, I created a data file in the `_data/` directory which imports the script I defined the grid in. As I was writing this, I realized that I could have just defined it there to begin with. Either way, it had to go on the webpage, which I did with a Nunjucks template.

```
<div id="clock" aria-hidden="true">
    {%- for row in grid.order %}
        <div class="row" data-row="{{ row }}">
            {%- for col in grid[row] %}
                <span data-cell="{{ row }}{{ loop.index }}" data-lit=false>{{ col }}</span>
            {%- endfor %}
        </div>
    {%- endfor %}
</div>

```

This code:

- loops through the rows of the grid in the order I defined in the `order` property,
- creates a `div` with the `data-row` attribute set to the current grid,
- in it, it then loops through each element on that row and
- puts it in a span with the `data-cell` attribute set to the row and item&rsquo;s index, a `data-lit` to false (more in a bit) and the contents being the letter.

Along with that, I needed to specify how the grid is defined to update the time. I did this with a massive, 201 line long object specifying where all the words are on the grid. I specified the locations for the (control???) words `'IT'`, `'IS'`. `'OH'` and `'OCLOCK'`, with the locations of the minutes and hours being defined in their own object. Thanks to the way my grid is designed, I didn&rsquo;t have to list out all the locations for each of the 59 minutes as I put all the -TY numbers before the -TEENS and SUB-TEENS.

```
const words = {
  IT: {
    row: 'a',
    start: 1,
    end: 2
  },
  ...,
  hours: {
    1: {
      row: 'a',
      start: 10,
      end: 12
    },
    ...
  },
  minutes: {
    1: {
      row: 'g',
      start: 11,
      end: 13
    },
    ...
  }

```

That means that the grid is defined in two places, one place for the markup, and another for the time controlling. With that set, it&rsquo;s time to show the, uh&hellip; time?

## Showing The Time

The site&rsquo;s code is in \_includes/js/main.js and is initialized in the `init` function.

```
function init() {
  const now = new Date()
  const nextMinIn = (60 - now.getSeconds()) * 1000 + now.getMilliseconds()
  updateTime(now)
  setTimeout(() => {
    updateTime(new Date())
    setInterval(() => {
      updateTime(new Date())
    }, 60000)
  }, nextMinIn)
}

```

What this code does is that it:

- shows the current time,
- calculate the time to the next minute (`nextMinIn`) in milliseconds,
- sets a timeout to run after `nextMinIn` milliseconds which:
  - updates the time and
  - sets an interval to update the time every minute.

All the fun stuff starts in. `updateTime`, which takes a time `time`.

As for what `updateTime` actually does,

```
function updateTime(time, currLitElems) {
  lightTime(time, currLitElems)
  const timeElem = document.getElementById('accessTime')
  let prettyNow = time.toLocaleTimeString([], {hour12: true, hour: '2-digit', minute: '2-digit', })
  timeElem.innerHTML = `It is <time datetime="${prettyNow}">${prettyNow}</time>`
}

```

It updates the time on both the word clock and the `timeElem` I made to provide an accessible version for the current time in the HTML in `_includes/main.njk`.

```
<p id="accessTime" class="sr-only" aria-live="polite" aria-atomic="true"></p>

```

Back to `updateTime`, there’s the `lightTime` function which takes the `time` and shows it on the UI.

```
function lightTime(time) {
  dimLitWords()
  const hour = time.getHours() % 12 === 0 ? 12 : time.getHours() % 12
  const hourCells = words.hours[hour]
  const minutes = time.getMinutes()
  const litElems = [words["IT"], words["IS"], hourCells]

  switch(true) {
    case minutes === 0:
      litElems.push(words['OCLOCK'])
      break;
    case minutes < 10:
      litElems.push(words['OH'])
    case minutes < 20:
      litElems.push(words.minutes[minutes])
      break
    default:
      const remainder = minutes % 10
      const tenny = minutes - remainder
      litElems.push(words.minutes[tenny])
      if (remainder !== 0) {
        litElems.push(words.minutes[remainder])     
      }
  }

  lightWords(litElems)
}

```

It finds all the lit items which matched the query `[data-lit="true"]` and turns them off by setting `data-lit` to `false`. Controlling the `data-lit` attribute is how I show certain times (or not).

After that, I parse the time’s hour and minutes, and initiate an array for the cells I want to light with &ldquo;IT&rdquo;, &ldquo;IS&rdquo; and the cell location corresponding to the hour.

As for minutes, I’m doing this in a switch statement:

- If it’s 0, I’ll add ‘OCLOCK’ to get lit.
- If it’s less than 10, I push &ldquo;OH&rdquo;.
- If it’s less than 20, I push the cell location for the `minutes`. Notice how there isn’t a `break` in the previous statement? That’s because I wanted to show an &lsquo;OH&rsquo; if the number is under 10 as well as the number itself, which is covered in the < 20 case. This is probably the first time I’ve done that.
- Otherwise (for numbers over 20), I push the -TY portion and the remainder if it’s greater than 0.

To light the cells, the code calls `lightWords` which calls `lightWord` that iterates through the range specified by a cell (in `row` from `start` to `end`).

## Other Things

For this site, I decided to use [11ty](https://11ty.dev/) since it has a lot of JavaScript integration and is pretty fast. It&rsquo;s structured like this:

```
word-clock/
  _data/
    grid.js
    grid.txt
  _includes/
    css/
      main.njk
      main.css
      normalize.css
    js/
      main.js
      main.njk
    partials/
      head.njk
    index.njk
  .eleventy.js
  index.md
  css.md
  js.md

```

As previosuly mentioned, `_data` holds the grid that is rendered by `_includes/index.njk`. `index.njk` has the web page template, with the grid. I put the site&rsquo;s `head` in `partials/head.njk`, which itself contains a description tag for SEO. Surely SEO is more complicated than that. There&rsquo;s also a meta generator with the Eleventy version I used.

The `css.md` and `js.md` files are there so I can concat all the css and js files into a css and js file respectivley. They need a better [asset pipeline like Hugo&rsquo;s](https://gohugo.io/hugo-pipes/) or maybe I need to learn how to use it better. I should go through [Andy Bell&rsquo;s Eleventy Course](https://piccalil.li/course/learn-eleventy-from-scratch/).

I&rsquo;ve gone through the JS, but the CSS is nothing special. There&rsquo;s [normalize.css](https://necolas.github.io/normalize.css/) as my reset and the CSS I declared (ellipses for brevity).

```
html {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
    font-size: 100%;
    --bg: rgb(16, 16, 16);
    --fg: white;
    --dimed: rgb(200, 200, 200);
    --lit: orange;
}

.sr-only{...}

a {color: orangered;}
a:hover, a:active {color: orange; outline: 2px dashed white;}
a:visited {color: goldenrod;}

/* Clock Row */
.row {
    display: flex;
    justify-content: center;
    font-family: monospace;
}

/* Clock Cell */
.row > span {
    font-size: 1.25rem;
    padding: 0.75vmin 1vmin;
    font-weight: bold;
}

[data-lit=false] {
    color: var(--dimed)
}

[data-lit=true] {
    color: var(--lit);
}

```

It&rsquo;s the first time I&rsquo;ve used CSS variables. These ones are mostly for the background and foreground colors, and the colors for the links and the states for `data-lit`. The [`sr-only`](https://webaim.org/techniques/css/invisiblecontent/) is to make content visible only for screen readers, which is the `access-time` I mentioned earlier.

The clock rows use flexbox to spread things about and each cell has some padding and bold monospace text. Interestingly, it&rsquo;s rather responsive without me specifying any `max-width`s and the like. There&rsquo;s the `margin: x auto` trick to center things, but that&rsquo;s the extent of my work on making the site responsive.

Last thing is that at some point I used the app [play.js](https://playdotjs.com/) to run this website, and it worked. The trick is to hook into the eleventy package, and serve the site yourself. I wish I could show it to you in action, but something changed betwen Eleventy versions meaning that it doesn&rsquo;t work anymore. The most I have is this picture showing the `package.json` file, Eleventy&rsquo;s server log and the webpage, all on my iPhone 7 Plus.

![package.json, server logs and the webpage on an iphone 7 plus screenshot](https://agckb.xyz/posts/making-word-clock/word-clock-playjs-iphone.png)

While it app itself has a long way to go (dependency resolution is rather recent and git operations are rather weak), I&rsquo;m hoping that the play.js team keep improving the app.

## Conclusion and What&rsquo;s Next

With all my work, here’s what Word Clock looks like now:

![The header with the words word clock and a description &ldquo;a clock which displays the time&hellip; in words&rdquo;, a letter grid saying &ldquo;it is five oh two&rdquo; in orange and the site footer](https://agckb.xyz/posts/making-word-clock/word-clock.PNG)

And that&rsquo;s how I made Word Clock in an evening-ish&hellip; and a bit more since I discovered a lot of bugs while making it. Interestingly, writing about how I made Word Clock helped me find bugs I didn&rsquo;t notice before. I should do this more often.

As for what I want to do next, there&rsquo;s a few things. It wouldn&rsquo;t hurt to make this site prettier and include a few animations here and there. Also, it would be nice to support additional locales or languages as well as to make a grid generator to make telling the time more dynamic with algorithms.

You can find [Work Clock](https://faraixyz.github.io/word-clock/) online and it’s [source code on GitHub](https://github.com/faraixyz/word-clock).

Thanks for reading! If you&rsquo;ve enjoyed this post, you can support me by

- sharing this post,
- becoming a [Patron](https://patreon.com/faraixyz),
- send me some money on either [Paypal](https://paypal.com/fgandiya), [Ko-fi](https://ko-fi/faraixyz) or [Buy Me A Coffee](https://www.buymeacoffee.com/faraixyz) or
- Get a domain on using [my Namecheap affiliate link](https://shareasale.com/r.cfm?b=467188&u=2110247&m=46483&urllink=namecheap%2Ecom&afftrack=farai%2Exyz). Note that I&rsquo;ll earn a comission on anything you buy with that link.