Binary Clock - Django Nibbles

Django can be used to build great websites. But it is also really good for solving small problems quickly. Introducing a new series called “Django Nibbles”, to help you learn an aspect of Django say Templates through a short and simple problem. You can either solve the problem yourself or follow my step-by-step solution.


Q. Create a page that displays a binary clock showing the current time (when the page was loaded).

For instance, if the time is “23:55:02” then we should see:

     
     
     
     

Each column represents a binary digit when read from top to bottom. For more details, read the Binary Clock wiki page.

Django Feature: Views

Time Given: 1 Hour

Go ahead. Try it yourself before reading further!


A. We will be using Python 3 (not 2.7) and Django 1.6 for solving this. Both are the latest versions at the time of writing.

Setting up the project

This section can be skipped if you know the basics of setting up a project in Django (which is simpler in Django 1.6)

On Linux command-line (indicated by the ‘$’ prompt), enter the following to create the binclock project:

$ cd ~/projects
$ django-admin.py startproject binclock
$ cd binclock

Getting the current time

Open binclock/urls.py and replace everything with these lines:

from django.conf.urls import patterns, url

urlpatterns = patterns('',
    url(r'^$', 'binclock.views.home', name='home'),
)

Create a new file binclock/views.py with the following lines:

from django.http import HttpResponse
from django.utils.timezone import localtime, now

def home(request):
    clock = str(localtime(now()))
    return HttpResponse(clock, content_type="text/plain; charset=utf-8")

The home view function returns the current time in plain text. We are importing the now function from django.utils.timezone to get the current time in a timezone-aware format. Calling localtime converts it to the timezone defined in the project’s settings.py file.

Invoking HttpResponse gives you a low-level control over the exact string to return to the browser i.e. the first argument. You can also mention the content-type which, by default, is set to HTML i.e. “text/html; charset=utf-8”. Plain text is good enough for our purpose. We will be using unicode characters, so we need to mention the charset as well.

Try running the server now:

$ ./manage.py runserver

If you open the browser and point it to the default URL http://127.0.0.1:8000/, you should see the date and time displayed. Unless you are in the UTC timezone, this might be different from your current timezone.

Let’s change this to your current timezone (find yours from the timezone list). Open binclock/settings.py and find the line starting with TIME_ZONE and change it your timezone. Since I live in Bangalore, my line would look like this:

TIME_ZONE = 'Asia/Calcutta'

Now refresh the browser page and you should see your current time.

Sketch

Converting to binary

We will convert to the binary format step-by-step. Notice that each digit has to be converted in Packed BCD format rather than its binary form. For e.g. 12 in binary is 1100 but we need it in BCD form, which is 0001 0010.

Python prompt will be indicated by “>>>” (actually, I had used IPython). Open up the shell using ./manage.py shell command:

>>> def bcd_digit(x):
...     return [(x & 8) >> 3, (x & 4) >> 2, (x & 2) >> 1, x & 1]
... 
>>> bcd_digit(5)
[0, 1, 0, 1]

We are using Python’s bitwise operators ‘&’ and ‘>>’ here. To extract the four rightmost bits, we use a bitmask and then shift the result right. We can convert a single decimal digit to a list of binary digits using bcd_digit. But numbers in a clock can have upto two digits.

>>> def bcd_2digits(x):
...     return [bcd_digit(x // 10), bcd_digit(x % 10)]
... 
>>> bcd_2digits(12)
[[0, 0, 0, 1], [0, 0, 1, 0]]

Now we can covert two digit numbers. Let’s try to convert an actual datetime object into binary digits.

>>> from datetime import datetime
>>> def bcd_hhmmss(dt):
...     return bcd_2digits(dt.hour) + bcd_2digits(dt.minute) + \
...         bcd_2digits(dt.second)
... 
>>> t = bcd_hhmmss(datetime(2013,11,16,23,55,2))
>>> t
[[0, 0, 1, 0], [0, 0, 1, 1], [0, 1, 0, 1], [0, 1, 0, 1], [0, 0, 0, 0], [0, 0, 1, 0]]

This is close to the output we want. But the rows have to converted to columns (by transposition). We also need to convert the numbers into unicode characters, for a better appearance.

>>> char_one = chr(0x25CF)
>>> char_zero = chr(0x25CB)
>>> def disp_unicode(clock_matrix):
...     return "\n".join(" ".join(char_one if p == 1 else char_zero for p in line
...                               ) for line in list(zip(*clock_matrix)))
... 
>>> disp_unicode(t)'○ ○ ○ ○ ○ ○\n○ ○ ● ● ○ ○\n● ● ○ ○ ○ ●\n○ ● ● ● ○ ○'
>>> print(disp_unicode(t))
○ ○ ○ ○ ○ ○
○ ○ ● ● ○ ○
● ● ○ ○ ○ ●
○ ● ● ● ○ ○

The disp_unicode function uses a lot of Python tricks. We can find the transpose of any matrix using the zip(*matrix) trick. We are also using nested list comprehensions to iterate row by row and then column by column. The inline if statement (also called a conditional expressions) transforms bits into pretty unicode characters.

Finally, we tie this to our view function in views.py:

def home(request):
    clock = disp_unicode(bcd_hhmmss(localtime(now())))
    return HttpResponse(clock, content_type="text/plain; charset=utf-8")

The full source can be found in github.

Now, try to enjoy reading time a bit ;)

Exercise to Reader: Change the code to show the date instead, in “yyyy-mm-dd” format.

Hi! Welcome to ArunRocks, an odd collection of writeups on programming, travel, gadgets and practically anything under the sun. This state of affairs could be blamed on the ecelectic interests of your host, Arun Ravindran. He loves programming in several languages especially Python. In his day job he works as a Solution Manager at Unisys. Read more...

Comments