Let's Build a Binary Clock in Node.js Learning
May 08, 2021I’m always on the lookout for fun programming exercises suitable for beginners. Recently, I came across this video on YouTube where the poster builds a binary clock in Javascript, HTML, and CSS. It was my first time coming across the idea of binary blocks and I thought it could be a fun exercise!
This is going to be my first tutorial-style post directed towards beginners so I’m still figuring out a proper format. One thing though, I’m not very fond of doing frontend and writing CSS so I decided to make this into a Node.js app. Another thing, I’m going to to use TypeScript for the code in this post. It’s JavaScript with some added features and it transpiles to JavaScript in the end. With that out of the way, let’s build a binary clock!
Pre-requisites
First, you need a quick tour over what the binary numbering system is. Here are some videos from Khan Academy:
Secondly, you need to know how a binary clock works. Here’s a great explanation on Reddit.
Finally, let’s install the required software:
- Any text editor. I advise to start with VSCode
- Node.js:
a way to run JavaScript without a browser. I usually use
NVM as a way to
install multiple Node.js versions my machine
(for Windows).
After you install
NVM
, runnvm install --lts
to install a long-term supported version of Node.js - Yarn: a package manager to install our dependencies
The Code
The full code is on GitHub. You can follow along with how the code progresses over time using the Git commits. I also advice to not copy the code and instead, type everything yourself. Keep this with you in your learning journey.
I. Setting up a new project
We will start first by creating a new project. In a terminal, we create a new folder using:
mkdir binary-clock
Then we move to that folder using:
cd binary-clock
Finally, we declare the folder as a JavaScript project using:
yarn init
You’ll be prompted to answer a few questions regarding the project and then
you’ll find a file named pacakge.json
in the current folder. This file
contains the configurations and dependencies of your project.
Now, let’s setup TypeScript by running the following command:
yarn add -D typescript @types/node
You’ll find two changes in the current directory. The file package.json
contains new lines under devDependencies
and there’s a new folder named
node-modules
that holds the actual code of those dependencies.
Let’s add a new file named tsconfig.json
which will hold our TypeScript
configurations with the following content:
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"lib": ["ESNext"],
"moduleResolution": "node",
"strict": true,
"outDir": "dist",
"rootDir": ".",
"declaration": true,
"allowSyntheticDefaultImports": true
},
"exclude": [
"./node_modules",
"./dist"
],
"include": [
"./src/**/*.ts"
]
}
Note: I don’t really know what all those options do. I have them copied from one project to the other. There’s an online reference for those.
The last step is to make building and running the project easier. Let’s add the
following to package.json
:
"scripts": {
"compile": "tsc",
"start": "node dist/src/index.js"
}
Now, we can create the src
folder:
mkdir src
Inside src
, we can create our main entry point file, index.ts
(index is
used by convention). The following command creates a new file under src
directory:
touch src/index.ts
Add this to src/index.ts
:
console.log("Hello, world!");
To build and run the project:
yarn compile && yarn start
II. Getting the current time
Our goal is to convert the current time into the binary format so we start by
reading the current time. JavaScript provides a Date
object that holds the
current date and time. Since we only care about hours, minutes, and seconds, we
use some methods defined on the Date
object to extract this information:
// in src/index.ts
// read current date and time
const date = new Date();
// extract time information
let hours = date.getHours();
let minutes = date.getMinutes();
let seconds = date.getSeconds();
Let’s wrap this in a function:
const _getTimeSections = (): Array<number> => {
const date = new Date();
let hours = date.getHours();
let minutes = date.getMinutes();
let seconds = date.getSeconds();
return [
hours,
minutes,
seconds
];
}
Note: There are multiple ways to create functions in JavaScript. The above is called an arrow function.
The previous function takes nothing in its arguments and returns an array of
elements of type number
. That’s where TypeScript comes into play: the type
system. JavaScript is a
dynamic,
weakly typed
language. TypeScript, on the other hand, adds optional static typing to our
programs. In fact, we can further annotate each variable with its
type
like so:
const _getTimeSections = (): Array<number> => {
const date: Date = new Date();
let hours: number = date.getHours();
let minutes: number = date.getMinutes();
let seconds: number = date.getSeconds();
return [
hours,
minutes,
seconds
];
}
Now, since it’s declared/typed as a
number
, the compiler will complain if you tried to assign astring
to thehours
variable.let hours: number = 12; hours = "hello"; // ERROR!
Note: the name of the function starts with an _
. This is a habit I picked
from writing JavaScript since it does not have a notion of private functions,
officially anyway. However, TypeScript does have a private
keyword by
default.
III. Converting the time to binary
In this section, we will do two tasks: extract each section (hours, minutes, and seconds) into two components (tens and ones) and convert each component to binary. Let’s start by extracting section components:
// in src/index.ts
const _getTimeSectionComponents = (section: number): Array<string> => {
return section.toString().padStart(2, '0').split("");
}
Here we take a time section as a number, hours
for example, and extract it
into two components: tens and ones. For example, if it’s currently 21
, we get
an array of two elements: ['2', '1']
. The call to padStart()
function adds
a zero to the right if the hour is only one digit. For example, 4
should be
04
and thus, we get: ['0', '4']
. The split()
function splits the string
into an array. For example, '12'.split("")
will return ['1', '2']
. Notice
that these functions work only on strings and that’s why we first called the
toString()
function on the section.
The second task is where we convert each component of each section into binary:
// in src/index.ts
const _convertComponentsToBinary = (components: Array<string>): Array<string> => {
return components.map((component: string) => {
return Number(component).toString(2).padStart(4, '0')
});
}
The map()
function is sort of like a for-loop except that it loops over some
items/array and returns another array after applying a function on the given
array. It sounded complicated writing this. Let’s take our example: given this
['2', '1']
as the components array, we use map to loop over each element of
this array and apply the following logic on each one:
Number(component).toString(2).padStart(4, '0')
component
is a variable that holds each element in the array. Let’s replace
the variable with a value:
Number('2').toString(2).padStart(4, '0')
So Number()
will convert the string element into a number, toString(2)
will
convert the number into the binary format as a string, and padStart(4, '0')
will pad the result with up to 4 0’s (since each column in a binary clock has 4
rows). Applying all three functions on '2'
will give us => '0010'
(2 is 10
in binary).
The newly aquired value '0010'
will be appended to an array and the iteration
will move to the next value, '1'
. The new iteration will apply the same three
functions and return '0001'
. This will be added to the array as well. Now
that the iteration is done, the whole array, ['0010', '0001]
will be returned
from the map()
function call and that’s our final return value.
VI. Printing the output
Here’s the function that prints the output to the terminal:
// in src/index.ts
const _printToConsole = (components: Array<Array<string>>) => {
console.clear();
for (let i = 0; i < 4; i++) {
let row: string = "";
for (let j = 0; j < 3; j++) {
row += (components[j][0][i] + ' '); // tens component
row += (components[j][1][i] + ' '); // ones component
}
console.log(row);
}
}
It takes an array of components (each component is an array of strings [in
binary format] in itself, ['0000', '0001']
). The function starts first by
clearing the terminal so we always have only the current time in the output.
Then, it prints each row from each component. We have 4 rows, that’s the first
loop. Then we have 3 sections * 2 components = 6 components in total, that’s
the inner loop. We append the row elements into the row
variable. Finally, we
print each row after the inner loop.
V. Ticking the clock
Last but not least, let’s run our clock. We will use a JavaScript function
named setInterval()
. This function is used to execute some logic every
defined number of milliseconds. It takes two arguments: a function containing
the logic to execute, and the number of milliseconds that define the frequency.
We use the functions we defined previously here, and pass 1000 milliseconds as
the frequency to tick our clock every 1 second:
// in src/index.ts
setInterval(() => {
let [hours, minutes, seconds] = _getTimeSections();
let hourComponents = _getTimeSectionComponents(hours);
let minuteComponents = _getTimeSectionComponents(minutes);
let secondComponents = _getTimeSectionComponents(seconds);
let hourBinaryComponents = _convertComponentsToBinary(hourComponents);
let minuteBinaryComponents = _convertComponentsToBinary(minuteComponents);
let secondBinaryComponents = _convertComponentsToBinary(secondComponents);
let components = [
hourBinaryComponents,
minuteBinaryComponents,
secondBinaryComponents,
];
_printToConsole(components);
}, 1000);
Here’s the final output:
VI. Extra: improvements
This is an extra part. You can take it as a homework, although, you’ll find the
improvements in the final code on GitHub. A first improvement is to move the
ticking logic inside setInterval()
into a separate function. Another one is
to wrap our functions inside a BinaryClock
class and deal with it as an
object. This is part of the object oriented programming capabilities provided
by TypeScript. Here’s the
documentation of classes and objects
from TypeScript documentation page.
That’s the end of our tutorial. If you have any problems following along or any general comments, please do not hesitate to comment below or contact me.