It's late at night and I've just uncovered a piece of history on a dusty old PC that hasn't been turned on for a Really Long Time.
It's the first task I was set when applying for the job at Linux Format, viz. to write some sort of introduction to Python. There's a lot wrong with my attempt here, but at least it references doomsdays and pays homage to the late John Conway. There's some indulgent use of the first person, some unnecessary pythonics and the original document includes formatting which ought to be none of the contributor's business. At least I was using Python 3 in 2014 though. So without further ado here it be:
//head
First Date with Python
//strap
Being an elegant introduction to Python and an illustration of some simple Gregorian calendrics
All of the mainstream Linux distros now come with at least one version of Python (typically 2.7 or
3.3, we will use the latter which is largely backwards compatible) so starting it is just a matter of
typing python into a terminal. This will open the interactive interpreter where commands can be
issued and expressions evaluated on the fly. We can use this as a simple calculator:
-> 1+1
2
-> 3+0.5
3.5
In the second sum we added the integer 3 with the floating point number 0.5. Python helpfully
coerces the result to a floating point type. However, be careful:
-> 3/2
1
-> 3./2
1.5
Here we first divide an integer by an integer, so no coercion happens and Python returns only the
integer part of the division. Sometimes this is the desired behaviour, indeed we shall use this later
on. If you really wanted three halves then you need to add in the decimal point as shown. We can
also make lists of all shapes and sizes:
-> mylist = [1,2,'buckle my shoe',3,4]
-> mylist[0] = 1 # Comments begin with # and lists are zero-indexed
-> mylist[1:3] # a slice of our list, starting with mylist[1] and ending just before mylist[3]
[2,'buckle my shoe']
-> sum(mylist[:2]) # summing a slice of the list. Omitting the first index is short for mylist[0:2]
3
-> for j in range(5):
... print(j ** 2,end=' ') # Don't start a newline. For Python 2.x use 'print j ** 2,' here
...
0 1 4 9 16
-> [j ** 2 : for j in range(5)] # My first list comprehension
[0, 1, 4, 9, 16]
When you are done, press Ctrl-D or type exit() to return to the terminal. Interactive mode is useful
for hammering out short examples or seeing what different code segments do, but once you want to
start building real projects it makes more sense to put your work into a module – a text file with the
extension .py.
This tutorial will show you (besides a smattering of Python) how to do a couple of neat calendrical
tricks. Firstly, decide whether a given year is leap or not and secondly calculate the weekday from a
given calendar date. The underlying calculations are based on modular division, which is just the
remainders at the end of so many long division exercises in school. For example 3 divides 9 exactly,
that is with no remainder, where as 4 divides 9 twice with remainder 1, so we can say 9 modulo 3 =
0 and 9 modulo 4 = 1.
Leap years are awkward beasts, we mostly have one when the year is divisible by 4 to account for
the earth's inconvenient period of rotation about the sun, but this is not the whole story. The actual
period is close to 365.24 days, so the Gregorian calendar stipulates that years divisible by 100 are
not leap years, excepting when they are also divisible by 400. Thus 1600 and 2000 were leap years,
but 1800 and 1900 were not.
Without further ado let us begin by creating the module leap.py containing the following code:
//code
def isLeapYear(y):
if y % 4 == 0 and (y % 100 <> 0 or y % 400 == 0): # % is the modular division operation
print (y, 'is indeed a leap year')
else:
print(y, 'is not a leap year')
Right away we can see Python's laconic concision: our function isLeapYear() and the if-else
block are demarcated by the indentation (4 spaces is standard, but Python will work with anything,
so long as you're consistent) so no need for curly brackets or what-have-you. Also no need for endof-line semicolons. Comparisons are done with operators such as == (equals) and <> (not equal to).
A single = is used for assignment, which can be a source of confusion. We can import this module
by starting Python in the directory containing leap.py and issuing:
//code
-> import leap
Now everything defined in leap.py is available under the namespace leap. In particular we can now
call our function to check that this year is not a leap year:
//code
-> leap.isLeapYear(2014)
2014 is not a leap year
Good, it will be over sooner. Now, once you've determined all your favourite years' leap-inesses, let
me digress slightly as I turn my attention to further calendar-related conundrums.
Calculating the weekday from a given date is a rather non-trivial problem. However, British
mathematician, and inventor of Life, John Conway conceived of an ingenious method to work
around these difficulties. His discovery is known as the Doomsday Algorithm. Doomsdays are fixed
dates that all fall on the same weekday. The algorithm works by calculating which weekday this is
and then counting from the doomsday of the given month and year to the desired date. Whilst there
are about 52 doomsdays in a year, we only need 12 and the canonical set for non-leap years is: 3
Jan, 28 Feb, 7 Mar, 4 Apr, 9 May, 6 Jun, 11 Jul, 8 Aug, 5 Sep, 10 Oct, 7 Nov, 12 Dec. For a leap
year only the first two change: 4 Jan and 29 Feb.
Don't worry if the algorithm (see box) seems overwhelming – the Python code is much shorter than
even this tersest of accounts. Create doomsday.py with the following contents:
//code
#! /usr/bin/env python
import sys
def weekday(day,month,year):
weekdays = [j + "day" for j in ["Sun","Mon","Tues","Wednes","Thurs","Fri","Satur"]]
century = year/100
anchordays = [2,0,5,3]
doomsdays = [3,28,7,4,9,6,11,8,5,10,7,12]
if isLeapYear(year):
doomsdays[:2] = [4,29] # Replace the first 2 items
shortyear = year % 100
r = shortyear % 12
doomsday = (shortyear/12 + r + r/4 + anchordays[century % 4]) % 7
weekday = weekdays[(day - doomsdays[month - 1] + doomsday) % 7]
print weekday
def isLeapYear(y):
if y % 4 == 0 and (y % 100 <> 0 or y % 400 == 0):
return True
-# we don't need to have an else: return False here
if name == "main":
day, month, year = [int(j) for j in sys.argv[1:4]] # multiple assignments in one line
weekday(day,month,year)
Here we have tweaked our isleapyear() function so that it returns either the boolean value True or
no value at all, which as a paradoxical aside is the same as the special value None. If you chmod +x
this file, the first line allows your shell to run the script from the command line. In this situation the
internal variable name acquires the value main. The sys module provides a veritable
smorgasbord of system-related parameters and functions. Any parameters supplied on the command
line are made available via the list of strings sys.argv, with sys.argv[0] being the name of the
program itself. So from the command line we can, by the following incantation, run our labours as a
standalone program:
//code
$ ./doomsday.py 29 4 1992
Wednesday
Remember that the Gregorian calendar was introduced in 1582, and was adopted by different
countries at different times (Greece in particular waited until 1923 before succumbing to the
reform), so the above code won't fly for Julian-era dates.
Alas, all this may have been but wheel-reinvention: the Python calendar module can do all this and
more. Try
//code
-> import calendar
-> calendar.isleap(2014)
-> calendar.weekday(1992,4,29)
Well dear reader, at least I kept you off the streets a little while. Now go forth and explore the
pythonic jungle!
//boxout
For the sake of brevity, we shall provide only an outline of the algorithm. Our input is the triple
(d,m,y) – date, month, year.
We begin by labelling the weekdays as integers starting with Sunday being 0 and finishing with
Saturday being 6. Take the modulo 4 value of the first two digits of the year y – so if y is in this
century, then this is 0, last century will be 3. Now define a constant a which is 2, 0, 5 or 3 when the
previous value is 0, 1, 2, or 3 respectively.
Now begins the fun. We express the last two digits of the year in the form 12q + r, and denote by t
the number of times 4 divides r. Then we calculate (q + r + t + a) modulo 7 which gives us the
weekday number on which the Doomsdays fall for the year in question. So we choose the
doomsday of month m and subtract its date from our date d. Now add the result onto the doomsday's
weekday number, take the modulo 7 remainder and the resulting integer corresponds to the weekday
required.