In most cases, showing the current time on a web page is redundant: any visitor already has a dozen ways to tell what time it is. However, building a simple clock is a useful way to learn the basics of “human readable” time in JavaScript, and, as we’ll see later, actually has several useful applications.
Clock Basics
Due to the feature’s redundancy, we shouldn’t be too worried about progressive enhancement, and can create the clock almost entirely in JavaScript. The only HTML we’ll add to the page is a <time>
element:
<time id="currtime"></time>
In our script we’ll create a reference to the element and the current time, borrowing from the Date()
object. From that, we’ll derive the current hour, minute and seconds:
var currentTime = document.getElementById("currtime");
var timeNow = new Date();
var hh = timeNow.getHours();
var mm = timeNow.getMinutes();
var ss = timeNow.getSeconds();
Then, we’ll format and write that information into the element:
var formattedTime = hh + " : " + mm + " : " + ss;
currentTime.innerHTML = formattedTime;
While you will get the current time added to the page using this code, you’ll also immediately see a problem: the time is presented only once, and never changes. In order to keep it updated, we must change currentTime
repeatedly by wrapping it in a function:
function updateTime(){
var timeNow = new Date();
var hh = timeNow.getHours();
var mm = timeNow.getMinutes();
var ss = timeNow.getSeconds();
var formattedTime = hh + " : " + mm + " : " + ss;
currentTime.innerHTML = formattedTime;
setTimeout(updateTime, 1000);
}
Inside the function is a self-referential setTimeout
timer, which will call on the function it is embedded within every second. Once that is in place, we only need call on the function from the outside once to get it started:
updateTime();
You might find that the clock’s seconds are slightly out of sync with the actual time: setTimeout
isn’t guaranteed to run exactly every second, and its progress is unlikely to coincide precisely with the passing of actual time. You can address this by calling updateTime()
more frequently inside of itself: every 500 milliseconds, or even every 100. Of course, calling the function with increased frequency also makes it more wasteful: at an interval of 100 milliseconds, you’ll be calling the function 10 times a second, but the time will only actually update once in that period.
Cleaning Up The Presentation
You’re likely to see one other problem: JavaScript reports getHours
and the other time components as pure integers, starting at 0 for midnight and proceeding through 1, 2, etc. While this may work for some situations, in most cases we’d expect single-digit time measures to be preceded by a 0.
While there’s many possible ways we could change this, I’ll take the safest route. Anything we have to do repeatedly should be handled by a helper function. I’ll call this one zeroPadder
:
function zeroPadder(n) {
return (parseInt(n, 10) < 10 ? "0" : "") + n;
}
Inside updateTime()
, the formattedTime
line changes to call upon this new function for each component of time:
formattedTime = zeroPadder(hh) + " : " + zeroPadder(mm) + " : " + zeroPadder(ss);
zeroPadder
is written rather more tersely than the rest of the script, so it’s worthwhile taking a moment to look at it closely.
First, zeroPadder
is taking a variable: in the context of the function, this variable is called n
. It’s also returning a new value for n
, after a calculation. This new value is “inserted” into wherever it is called from.
The calculation uses parseInt
, a function that takes a string and returns a number. While we’re passing zeroPadder
a digit, parseInt
won’t care: it converts it to a base 10 digit (base 10 due to the same number being used as a “radix” argument: the safest method). The result is then compared, to see if n
is less than 10. If it is, "0" is prepended to n
, otherwise, nothing changes.
The function could be written to produce the exact same result in a far longer way:
function zeropadder(n) {
n = parseInt(n, 10);
if (n < 10) {
n = "0" + n;
}
return n;
}
As a rule, smaller, more efficient code is always better, so long as it is understood.
24-to-12
Most people are more comfortable with 12-hour time, as opposed to JavaScript’s default 24-hour measurement. Fortunately, this is very easy to change. At the same time, we can tighten up our updateTime()
function:
function updateTime(){
var timeNow = new Date();
var hh = timeNow.getHours() % 12 || 12;
var mm = timeNow.getMinutes();
var ss = timeNow.getSeconds();
var formatAMPM = (hh >= 12 ? "PM" : "AM");
currentTime.innerHTML = hh + " : " + zeroPadder(mm) + " : " + zeroPadder(ss) + " " + formatAMPM;
setTimeout(updateTime, 1000);
}
This also deserves an explanation:
- The line that sets the
formatAMPM
variable is the simplest: it uses the previously introducedif-else
shortcut syntax to see ifhh
is greater than or equal to 12. If it is,formatAMPM
is set to a value ofPM
, otherwise, it is set toAM
- The line after that is a little tricker. First, the statement uses a remainder operator to find the value of
hh
after division by 12; the operation only ever returns integers. In this case, ifhh
is less than 12, it remains unchanged: 2 divided by 12 is less than 1, but ifhh
had a value of 16, the remainder after division by 12 would be 4, i.e. 4pm. - The double pipe (
||
) operator means do the following if the previous operation resolves tofalse
. In JavaScript, a numerical value of0
is exactly equivalent tofalse
, and can, in most instances, be used interchangeably for it. In other words, if the time is noon to 12:59:59,hh
will have a value of12
. Dividing 12 by 12 gives a result a 0. As “0PM” can’t exist, the double pipe operator picks up on this result and corrects it to 12. Again, this could be written as a longer (and more complex)if
statement.
Note that we’ve removed the formattedTime
variable in favor of setting the innerHTML
property of currentTime
directly, without padding hh
with a leading 0. Again, smaller and more efficient code is better: any variable we can drop, and thus not force JavaScript to store and track in memory, is a savings in performance.
Further Improvements
Obviously there’s a lot more that we could do here: at the very least, the <time>
element needs a datetime
attribute, and including the day in the display would be useful. We’ll look at doing just that, and much more, in the next article in this series.
Enjoy this piece? I invite you to follow me at twitter.com/dudleystorey to learn more.
Check out the CodePen demo for this article at https://codepen.io/dudleystorey/pen/unEyp