4.3.2 第二层设计

接下来需要对第二层上的每个模块进行精化。

首先看 getYear 函数。这个函数的功能只是输入年份数据,可以直接用 Python 的基本 语句实现,无需分解为新的功能模块。具体代码如下:

def getYear():
    print "This program prints the calendar of a given year." 
    year = input("Please enter the year (after 1900): ") 
    return year

接着考虑 firstDay 函数的设计。这个函数的功能是计算 year 年 1 月 1 日是星期几,因为年历是按星期来组织每一天的显示位置的,而只要知道 1 月 1 日的显示位置,其后所有日期的显示位置也就确定了。

在 calendar 程序的规格说明中说明了,我们以 1900 年 1 月 1 日(星期一)作为基准日, 只要算出 year 年 1 月 1 日距离基准日的天数,就能知道这一天是星期几。因为从基准日开 始,过 1 天是星期二,过 2 天是星期三,…,过 6 天是星期日,过 7 天又是星期一,…。一 般地,过 n 天是星期(n+1)%7(值为 0 表示星期天)。

那么,从基准日到 year 年 1 月 1 日总共过了多少天呢?只需一点常识,就能得出下面 的公式:

(year – 1900) * 365 + k

其中 k 是从 1900 到 year(不含)之间的闰年个数。

看上去闰年个数 k 还不清楚如何求得,我们按惯例假设一个新函数 leapyears()能够

返回所需的 k。于是可以设计 firstDay 函数如下:

def firstDay(year):
    k = leapyears(year)
    n = (year – 1900) * 365 + k 
    return (n + 1) % 7

最后考虑 printCalendar 函数的设计,该函数的任务是在合适的位置按日历格式显示一年 12 个月的日历。由于问题有点复杂,我们照例进行任务分解。12 个月的日历输出显然可以用一个 for 循环来实现,循环体是显示一个月日历的代码。每个月需要先打印标题(月份和星期的名称),然后再打印日期,假设函数 heading()和 oneMonth()分别执行这两个任务,则 printCalendar 的代码如下:

def printCalendar(year,w):
    print
    print "=========== " + str(year) + " =========="
    first = w
    for month in range(12): 
        heading(month)
        first = oneMonth(year,month,first)

函数体的第一行用于打印年份信息,接下去是打印 12 个月的日历的 for 语句。打印每个月 的日历需要知道该月 1 日是星期几。printCalendar 的参数 w 是前面算出来的 1 月 1 日 的星期信息,2 月到 12 月的 1 日则由 oneMonth 函数返回至此,我们完成了第二层设计, 可以用图 4.8 中的结构图表示到目前为止的设计结果。注意,为简明起见,图中省略了各模 块之间的界面数据。

图 4.8 calendar 程序的第二层结构图