calendar是与日历相关的模块,calendar模块文件里定义了很多类型,主要有Calendar,TextCalendar以及HTMLCalendar类型。其中,Calendar是TextCalendar与HTMLCalendar的基类。该模块文件还对外提供了很多方法,例如:calendar,month,prcal,prmonth之类的方法...

    页面导航: 英文教程的下载地址:

    本篇文章是根据英文教程《Python Tutorial》来写的学习笔记。该英文教程的下载地址如下:

    百度盘地址:http://pan.baidu.com/s/1c0eXSQG

    DropBox地址:点此进入DropBox链接

    Google Drive:点此进入Google Drive链接

    这是学习笔记,不是翻译,因此,内容上会与英文原著有些不同。以下记录是根据英文教程的第十三章来写的。(文章中的部分链接,包括下载链接,可能需要通过代理访问!)

    本篇文章也会涉及到Python的C源代码,这些C源码都是2.7.8版本的。想使用gdb来调试python源代码的话,就需要按照前面"Python基本的操作运算符"文章中所说的,使用configure --with-pydebug命令来重新编译安装python。

    如果Python的官方网站取消了Python-2.7.8.tgz的源码包的话,可以在以下两个链接中下载到:

    DropBox:Python-2.7.8.tgz的DropBox网盘链接

    Google Drive:Python-2.7.8.tgz的Google Drive网盘链接

    文章里棕色的注释是作者额外添加的,在源码里并没有。本篇文章的例子主要是针对Linux系统下Python 2.7.8版本的。

calendar模块:

    calendar是与日历相关的模块,该模块的源码定义在Python文件中:

[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46) 
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import calendar
>>> calendar
<module 'calendar' from '/usr/local/lib/python2.7/calendar.pyc'>
>>> quit()
[email protected]:~$ ls /usr/local/lib/python2.7/calendar.py
/usr/local/lib/python2.7/calendar.py
[email protected]:~$ 


    可以看到,在作者的系统里,calendar模块定义在/usr/local/lib/python2.7/calendar.py文件里:

....................................................
class Calendar(object):
    """
    Base calendar class. This class doesn't do any formatting. It simply
    provides data to subclasses.
    """
    ................................................

class TextCalendar(Calendar):
    """
    Subclass of Calendar that outputs a calendar as a simple plain text
    similar to the UNIX program cal.
    """
    ................................................

class HTMLCalendar(Calendar):
    """
    This calendar returns complete HTML pages.
    """
    ................................................

# Support for old module level interface
c = TextCalendar()
....................................................
monthcalendar = c.monthdayscalendar
prweek = c.prweek
week = c.formatweek
weekheader = c.formatweekheader
prmonth = c.prmonth
month = c.formatmonth
calendar = c.formatyear
prcal = c.pryear

....................................................


    在该模块文件里定义了很多类型,主要有Calendar,TextCalendar以及HTMLCalendar类型。其中,Calendar是TextCalendar与HTMLCalendar的基类。

    该模块文件还对外提供了很多方法,例如:calendarmonthprcalprmonth之类的方法。这些方法其实就是TextCalendar类里定义的方法,只不过有的改了些名字而已,例如:calendar方法其实就是TextCalendar类里的formatyear方法等等。

    下面会先对TextCalendar里的这些方法进行介绍(该类主要用于生成普通的文本格式的日历信息)。

calendar方法:

    calendar方法的使用,如下所示:

[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46) 
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import calendar
>>> calendar.calendar(2015)
'                                  2015\n\n      January ...............'
>>> print calendar.calendar(2015)
                                  2015

      January                   February                   March
Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su
          1  2  3  4                         1                         1
 5  6  7  8  9 10 11       2  3  4  5  6  7  8       2  3  4  5  6  7  8
12 13 14 15 16 17 18       9 10 11 12 13 14 15       9 10 11 12 13 14 15
19 20 21 22 23 24 25      16 17 18 19 20 21 22      16 17 18 19 20 21 22
26 27 28 29 30 31         23 24 25 26 27 28         23 24 25 26 27 28 29
                                                    30 31

       April                      May                       June
Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su
       1  2  3  4  5                   1  2  3       1  2  3  4  5  6  7
 6  7  8  9 10 11 12       4  5  6  7  8  9 10       8  9 10 11 12 13 14
13 14 15 16 17 18 19      11 12 13 14 15 16 17      15 16 17 18 19 20 21
20 21 22 23 24 25 26      18 19 20 21 22 23 24      22 23 24 25 26 27 28
27 28 29 30               25 26 27 28 29 30 31      29 30

        July                     August                  September
Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su
       1  2  3  4  5                      1  2          1  2  3  4  5  6
 6  7  8  9 10 11 12       3  4  5  6  7  8  9       7  8  9 10 11 12 13
13 14 15 16 17 18 19      10 11 12 13 14 15 16      14 15 16 17 18 19 20
20 21 22 23 24 25 26      17 18 19 20 21 22 23      21 22 23 24 25 26 27
27 28 29 30 31            24 25 26 27 28 29 30      28 29 30
                          31

      October                   November                  December
Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su
          1  2  3  4                         1          1  2  3  4  5  6
 5  6  7  8  9 10 11       2  3  4  5  6  7  8       7  8  9 10 11 12 13
12 13 14 15 16 17 18       9 10 11 12 13 14 15      14 15 16 17 18 19 20
19 20 21 22 23 24 25      16 17 18 19 20 21 22      21 22 23 24 25 26 27
26 27 28 29 30 31         23 24 25 26 27 28 29      28 29 30 31
                          30

>>> 


    calendar方法本身返回的只是一段与日历相关的字符串信息,该字符串里包含了'\n'之类的换行符。要让这段字符串能在终端里以较好的方式进行显示的话,可以使用print语句,如上面的print calendar.calendar(2015)语句,就可以将日历信息以较好的排版显示出来。

    该方法对应的Python源码如下:

class TextCalendar(Calendar):
    ................................................
    def formatyear(self, theyear, w=2, l=1, c=6, m=3):
        """
        Returns a year's calendar as a multi-line string.
        """
        w = max(2, w)
        l = max(1, l)
        c = max(2, c)
        colwidth = (w + 1) * 7 - 1
        v = []
        a = v.append
        a(repr(theyear).center(colwidth*m+c*(m-1)).rstrip())
        a('\n'*l)
        header = self.formatweekheader(w)
        for (i, row) in enumerate(self.yeardays2calendar(theyear, m)):
            # months in this row
            months = range(m*i+1, min(m*(i+1)+1, 13))
            a('\n'*l)
            names = (self.formatmonthname(theyear, k, colwidth, False)
                     for k in months)
            a(formatstring(names, colwidth, c).rstrip())
            a('\n'*l)
            headers = (header for k in months)
            a(formatstring(headers, colwidth, c).rstrip())
            a('\n'*l)
            # max number of weeks for this row
            height = max(len(cal) for cal in row)
            for j in range(height):
                weeks = []
                for cal in row:
                    if j >= len(cal):
                        weeks.append('')
                    else:
                        weeks.append(self.formatweek(cal[j], w))
                a(formatstring(weeks, colwidth, c).rstrip())
                a('\n' * l)
        return ''.join(v)

....................................................
c = TextCalendar()
....................................................
calendar = c.formatyear


    可以看到,calendar方法本质上就是TextCalendar类里的formatyear方法。

    formatyear方法的第一个参数theyear表示需要返回哪一年的日历信息,第二个参数w用于控制每个星期的字符宽度,第三个参数l用于控制日历的行与行之间的换行数,第四个参数c用于控制月份之间的间隙宽度,最后一个参数m用于控制每排显示几个月。除了第一个参数外,取余的参数都是可选的,这些可选参数的具体含义,如下图所示:


图1

    我们可以使用pdb来调试分析TextCalendar类的formatyear方法:

[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46) 
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import calendar
>>> import pdb
>>> pdb.runcall(calendar.calendar, 2015)
> /usr/local/lib/python2.7/calendar.py(338)formatyear()
-> w = max(2, w)
(Pdb) n
> ..................................................
-> l = max(1, l)
(Pdb) n
> ..................................................
-> c = max(2, c)
(Pdb) n
> ..................................................
-> colwidth = (w + 1) * 7 - 1
(Pdb) n
> ..................................................
-> v = []
(Pdb) n
> ..................................................
-> a = v.append
(Pdb) n
> ..................................................
-> a(repr(theyear).center(colwidth*m+c*(m-1)).rstrip())
(Pdb) n
> ..................................................
-> a('\n'*l)
(Pdb) p v
['                                  2015']
(Pdb) n
> ..................................................
-> header = self.formatweekheader(w)
(Pdb) n
> ..................................................
-> for (i, row) in enumerate(self.yeardays2calendar(theyear, m)):
(Pdb) p header
'Mo Tu We Th Fr Sa Su'
(Pdb) p self.yeardays2calendar(theyear, m)
[[[[(0, 0), (0, 1), (0, 2), (1, 3), (2, 4), (3, 5), (4, 6)], 
[(5, 0), (6, 1), (7, 2), (8, 3), (9, 4), (10, 5), (11, 6)], 
[(12, 0), (13, 1), (14, 2), (15, 3), (16, 4), (17, 5), (18, 6)], 
[(19, 0), (20, 1), (21, 2), (22, 3), (23, 4), (24, 5), (25, 6)], 
[(26, 0), (27, 1), (28, 2), (29, 3), (30, 4), (31, 5), (0, 6)]], 
....................................................]]]]
(Pdb) 


    有关pdb调试的内容,可以参考 https://docs.python.org/2.7/library/pdb.html 的2.7.x版本的官方手册。

    从上面的调试里可以看到,formatyear方法里的列表v用于存储每一行的字符串信息,以及行与行之间的换行符。在此方法的最后,会将列表v里的所有字符串都连接到一起来构成完整的字符串数据。

    还可以看到,self.formatweekheader(w)会根据宽度w来生成对应的星期名所组成的头部信息。例如当w为2时,得到的header就会是'Mo Tu We Th Fr Sa Su',当w为3时,得到的header就会是'Mon Tue Wed Thu Fri Sat Sun'

     formatyear方法里面与日历相关的数据,都是由self.yeardays2calendar(theyear, m)生成的。self.yeardays2calendar返回的是一个列表,而这个结果列表中又包含了很多层别的列表,我们需要先理解这些层层包裹起来的列表的含义,才能去理解其他代码的作用。self.yeardays2calendar所返回的列表的具体含义,如下图所示:


图2

    在上图所示的一月份列表数据里,包含了很多(0,0),(1, 3),(2, 4)之类的元组。元组里的第一项表示一个月的第几天(如果为0则表示对应的元组无效,在日历的对应位置就会显示空白),元组里的第二项则表示星期几(0表示星期一,1表示星期二,2表示星期三,...... 6表示星期天)。

    例如:一月份列表里的(1, 3)这个元组就表示1月1日,星期四。一月份里的(2, 4)则表示1月2日,星期五。

    有了self.yeardays2calendar返回的列表数据,formatyear方法里的代码就可以根据上图所示的列表结构,来生成对应的日历字符串信息了。限于篇幅,请读者自行通过pdb调试器,配合上图的列表结构来分析formatyear方法里没介绍到的代码。

prcal方法:

    prcal方法的使用,如下所示:

[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46) 
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import calendar
>>> calendar.prcal(2015)
                                  2015

      January                   February                   March
Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su
          1  2  3  4                         1                         1
 5  6  7  8  9 10 11       2  3  4  5  6  7  8       2  3  4  5  6  7  8
12 13 14 15 16 17 18       9 10 11 12 13 14 15       9 10 11 12 13 14 15
19 20 21 22 23 24 25      16 17 18 19 20 21 22      16 17 18 19 20 21 22
26 27 28 29 30 31         23 24 25 26 27 28         23 24 25 26 27 28 29
                                                    30 31

       April                      May                       June
Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su
       1  2  3  4  5                   1  2  3       1  2  3  4  5  6  7
 6  7  8  9 10 11 12       4  5  6  7  8  9 10       8  9 10 11 12 13 14
13 14 15 16 17 18 19      11 12 13 14 15 16 17      15 16 17 18 19 20 21
20 21 22 23 24 25 26      18 19 20 21 22 23 24      22 23 24 25 26 27 28
27 28 29 30               25 26 27 28 29 30 31      29 30

        July                     August                  September
Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su
       1  2  3  4  5                      1  2          1  2  3  4  5  6
 6  7  8  9 10 11 12       3  4  5  6  7  8  9       7  8  9 10 11 12 13
13 14 15 16 17 18 19      10 11 12 13 14 15 16      14 15 16 17 18 19 20
20 21 22 23 24 25 26      17 18 19 20 21 22 23      21 22 23 24 25 26 27
27 28 29 30 31            24 25 26 27 28 29 30      28 29 30
                          31

      October                   November                  December
Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su
          1  2  3  4                         1          1  2  3  4  5  6
 5  6  7  8  9 10 11       2  3  4  5  6  7  8       7  8  9 10 11 12 13
12 13 14 15 16 17 18       9 10 11 12 13 14 15      14 15 16 17 18 19 20
19 20 21 22 23 24 25      16 17 18 19 20 21 22      21 22 23 24 25 26 27
26 27 28 29 30 31         23 24 25 26 27 28 29      28 29 30 31
                          30

>>> 


    可以看到,上面的calendar.prcal(2015)其实就等效于print calendar.calendar(2015)语句。我们还可以从prcal方法的Python源码里看到这一点(下面的代码也定义在/usr/local/lib/python2.7/calendar.py文件里):

class TextCalendar(Calendar):
    ................................................
    def pryear(self, theyear, w=0, l=0, c=6, m=3):
        """Print a year's calendar."""
        print self.formatyear(theyear, w, l, c, m)

....................................................
c = TextCalendar()
....................................................
prcal = c.pryear


    从上面的代码里可以看到,calendar模块的prcal方法,本质上就是TextCalendar类的pryear方法。该方法会直接通过print语句将formatyear返回的指定年份的日历字符串信息给显示出来。

    pryear也有5个参数,它会将这些参数直接传递给formatyear,至于这些参数的具体含义,在之前介绍formatyear方法时已经详细的讲解过了,读者可以参考前面的图1

month与prmonth方法:

    这两个方法的使用,如下所示:

[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46) 
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import calendar
>>> calendar.month(2015, 9)
'   September 2015\nMo Tu We Th Fr Sa Su\n .........'
>>> print calendar.month(2015, 9)
   September 2015
Mo Tu We Th Fr Sa Su
    1  2  3  4  5  6
 7  8  9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30

>>> calendar.prmonth(2015, 9)
   September 2015
Mo Tu We Th Fr Sa Su
    1  2  3  4  5  6
 7  8  9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
>>> 


    可以看到:上面的calendar.prmonth(2015, 9)等效于print calendar.month(2015, 9) 。其中,calendar.month方法可以将指定的月份的日历信息,以字符串的形式作为结果返回。而calendar.prmonth方法则会通过print语句将month方法所返回的字符串,以较好的排版在终端里显示出来。

    这两个方法对应的Python源代码如下(都定义在/usr/local/lib/python2.7/calendar.py文件里):

class TextCalendar(Calendar):
    ................................................
    def prmonth(self, theyear, themonth, w=0, l=0):
        """
        Print a month's calendar.
        """
        print self.formatmonth(theyear, themonth, w, l),

    def formatmonth(self, theyear, themonth, w=0, l=0):
        """
        Return a month's calendar string (multi-line).
        """
        w = max(2, w)
        l = max(1, l)
        s = self.formatmonthname(theyear, themonth, 7 * (w + 1) - 1)
        s = s.rstrip()
        s += '\n' * l
        s += self.formatweekheader(w).rstrip()
        s += '\n' * l
        for week in self.monthdays2calendar(theyear, themonth):
            s += self.formatweek(week, w).rstrip()
            s += '\n' * l
        return s

....................................................
c = TextCalendar()
....................................................
prmonth = c.prmonth
month = c.formatmonth
....................................................


    从上面的代码里可以看到,calendar模块的month方法本质上就是TextCalendar类的formatmonth方法,而calendar模块的prmonth方法对应就是TextCalendar类的prmonth方法。

    prmonthformatmonth方法具有相同的参数,prmonth会直接将参数传递给formatmonth,再将formatmonth返回的日历字符串通过print语句显示出来。这两个方法都可以接受四个参数:第一个参数theyear用于指定年份。第二个参数themonth用于指定月份。最后两个参数w与l是可选,w与l的含义可以参考前面的图1

    我们可以通过pdb调试器来对formatmonth方法进行调试分析:

[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46) 
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import calendar
>>> import pdb
>>> pdb.runcall(calendar.month, 2015, 9)
> /usr/local/lib/python2.7/calendar.py(322)formatmonth()
-> w = max(2, w)
(Pdb) n
> ..................................................
-> l = max(1, l)
(Pdb) n
> ..................................................
-> s = self.formatmonthname(theyear, themonth, 7 * (w + 1) - 1)
(Pdb) n
> ..................................................
-> s = s.rstrip()
(Pdb) p s
'   September 2015   '
(Pdb) n
> ..................................................
-> s += '\n' * l
(Pdb) n
> ..................................................
-> s += self.formatweekheader(w).rstrip()
(Pdb) p self.formatweekheader(w)
'Mo Tu We Th Fr Sa Su'
(Pdb) n
> ..................................................
-> s += '\n' * l
(Pdb) n
> ..................................................
-> for week in self.monthdays2calendar(theyear, themonth):
(Pdb) p self.monthdays2calendar(theyear, themonth)
[[(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6)], 
[(7, 0), (8, 1), (9, 2), (10, 3), (11, 4), (12, 5), (13, 6)], 
[(14, 0), (15, 1), (16, 2), (17, 3), (18, 4), (19, 5), (20, 6)], 
[(21, 0), (22, 1), (23, 2), (24, 3), (25, 4), (26, 5), (27, 6)], 
[(28, 0), (29, 1), (30, 2), (0, 3), (0, 4), (0, 5), (0, 6)]]
(Pdb) c
'   September 2015\nMo Tu We Th Fr Sa Su\n .........'
>>> 


    可以看到,formatmonth方法里的self.formatmonthname会返回日历头部的年月信息,例如:'   September 2015   '的字符串。self.formatweekheader则会返回星期名所组成的头部结构,例如:'Mo Tu We Th Fr Sa Su'的字符串。

    最后,self.monthdays2calendar会返回一个包含了指定月份的所有日期数据的列表,该列表的数据结构,可以参考前面图2里的一月份的列表数据,以及该列表数据的相关说明。

firstweekday与setfirstweekday方法:

    这两个方法的使用,如下所示:

[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46) 
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import calendar
>>> calendar.firstweekday()
0
>>> calendar.prmonth(2015, 9)
   September 2015
Mo Tu We Th Fr Sa Su
    1  2  3  4  5  6
 7  8  9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
>>> calendar.setfirstweekday(1)
>>> calendar.firstweekday()
1
>>> calendar.prmonth(2015, 9)
   September 2015
Tu We Th Fr Sa Su Mo
 1  2  3  4  5  6  7
 8  9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
>>> calendar.setfirstweekday(2)
>>> calendar.firstweekday()
2
>>> calendar.prmonth(2015, 9)
   September 2015
We Th Fr Sa Su Mo Tu
                   1
 2  3  4  5  6  7  8
 9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30
>>> 


    从上例中可以看到,firstweekday方法返回的值,用于控制在日历里哪个星期排在最前面,0表示星期一(Monday),1表示星期二(Tuesday),2表示星期三(Wednesday),以此类推。

    要设置firstweekday方法所返回的值,就可以使用setfirstweekday方法。例如:setfirstweekday(1)执行后,firstweekday就会返回1。setfirstweekday(2)执行后,firstweekday就会返回2,等等。

    如果没使用setfirstweekday方法进行过设置的话,firstweekday方法返回的缺省值会是0 。

    这两个方法相关的Python源代码如下(都定义在/usr/local/lib/python2.7/calendar.py文件里):

# Constants for weekdays
(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7)

....................................................

class Calendar(object):
    """
    Base calendar class. This class doesn't do any formatting. It simply
    provides data to subclasses.
    """

    def __init__(self, firstweekday=0):
        self.firstweekday = firstweekday # 0 = Monday, 6 = Sunday

    def getfirstweekday(self):
        return self._firstweekday % 7

    def setfirstweekday(self, firstweekday):
        self._firstweekday = firstweekday

    firstweekday = property(getfirstweekday, setfirstweekday)
    ................................................

class TextCalendar(Calendar):
    """
    Subclass of Calendar that outputs a calendar as a simple plain text
    similar to the UNIX program cal.
    """
    ................................................

# Support for old module level interface
c = TextCalendar()

firstweekday = c.getfirstweekday

def setfirstweekday(firstweekday):
    try:
        firstweekday.__index__
    except AttributeError:
        raise IllegalWeekdayError(firstweekday)
    if not MONDAY <= firstweekday <= SUNDAY:
        raise IllegalWeekdayError(firstweekday)
    c.firstweekday = firstweekday


    TextCalendar作为Calendar的子类,它继承了Calendar里的所有的属性,方法和成员。

    在Calendar类里有一个_firstweekday成员,该成员的值决定了日历中哪个星期需要排在最前面。

    要访问_firstweekday成员的值,可以使用Calendar类里的getfirstweekday方法,此方法会将_firstweekday成员的值与7进行取余运算,取余的结果作为返回值,这是因为_firstweekday的有效值是0到6的整数(0表示星期一,6表示星期天)。

    在Calendar类中,还有一个firstweekday的property(属性)。当对该属性进行读操作时,会自动调用getfirstweekday方法去读取_firstweekday成员的值。当对该属性进行设置操作时,则会自动调用setfirstweekday方法去设置_firstweekday成员的值。

    从Calendar的__init__初始化函数里可以看到,firstweekday的默认初始值是0,因此,缺省情况下,星期一会排在日历的最前面。

    从上面的代码里还可以看到:calendar模块的firstweekday方法,本质上就是TextCalendar的基类即Calendar类里的getfirstweekday方法。

    而calendar模块的setfirstweekday方法会先对参数进行检测,如果参数是0到6的整数的话,则将参数设置到Calendar类的firstweekday属性里,该属性会自动调用setfirstweekday方法将参数的值设置到_firstweekday成员里。

    以下是pdb调试器的输出情况:

[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46) 
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import calendar
>>> import pdb
>>> pdb.runcall(calendar.firstweekday)
> /usr/local/lib/python2.7/calendar.py(136)getfirstweekday()
-> return self._firstweekday % 7
(Pdb) c
2
>>> pdb.runcall(calendar.setfirstweekday, 3)
> /usr/local/lib/python2.7/calendar.py(572)setfirstweekday()
-> try:
(Pdb) s
> /usr/local/lib/python2.7/calendar.py(573)setfirstweekday()
-> firstweekday.__index__
(Pdb) s
> /usr/local/lib/python2.7/calendar.py(576)setfirstweekday()
-> if not MONDAY <= firstweekday <= SUNDAY:
(Pdb) s
> /usr/local/lib/python2.7/calendar.py(578)setfirstweekday()
-> c.firstweekday = firstweekday
(Pdb) s
--Call--
> /usr/local/lib/python2.7/calendar.py(138)setfirstweekday()
-> def setfirstweekday(self, firstweekday):
(Pdb) s
> /usr/local/lib/python2.7/calendar.py(139)setfirstweekday()
-> self._firstweekday = firstweekday
(Pdb) c
>>> 


    可以看到,pdb调试器的输出结果与我们之前的分析是一致的:calendar.firstweekday最终会通过Calendar类里的getfirstweekday方法,去读取_firstweekday成员的值。而calendar.setfirstweekday则最终会通过Calendar类里的setfirstweekday方法,去设置_firstweekday成员的值。

isleap与leapdays方法:

    这两个方法的使用,如下所示:

[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46) 
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import calendar
>>> calendar.isleap(2000)
True
>>> calendar.isleap(2004)
True
>>> calendar.isleap(2008)
True
>>> calendar.isleap(2012)
True
>>> calendar.isleap(2007)
False
>>> calendar.isleap(2500)
False
>>> calendar.leapdays(2000, 2016)
4
>>> 


    从上例里可以看到,isleap方法用于判断指定的年份是否是闰年,如果是闰年则返回True,否则返回False。因此,上面的2000、2004、2008及2012都是闰年,而2007与2500都不是闰年。

    leapdays方法则可以计算出两个指定的年份之间,一共存在多少个闰年。例如上面的calendar.leapdays(2000, 2016)执行后返回4,就说明2000年到2016年(不包括2016在内)之间一共包含4个闰年,分别是2000、2004、2008以及2012年。

    isleap与leapdays方法相关的Python源代码如下(也都定义在/usr/local/lib/python2.7/calendar.py文件里):

def isleap(year):
    """Return True for leap years, False for non-leap years."""
    # 如果某年份能被4整除,同时不能被100整除时,
    # 那么该年份就是闰年,
    # 例如:2004,2008以及2012年都是闰年。
    # 如果某年份能被4整除,同时还能被400整除的话,
    # 那么该年份也是闰年,例如:2000年也是闰年。
    return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)


def leapdays(y1, y2):
    """Return number of leap years in range [y1, y2).
       Assume y1 <= y2."""
    y1 -= 1
    y2 -= 1
    # (y2//4 - y1//4)可以计算出y1与y2之间
    # 一共有多少个数能被4整除,
    # (y2//100 - y1//100)可以计算出y1与y2之间
    # 一共有多少个数能被100整除,
    # (y2//400 - y1//400)可以计算出y1与y2之间
    # 一共有多少个数能被400整除,
    # 因此,下面的计算公式就可以计算出
    # y1到y2之间一共有多少个数
    # 能被4整除,同时不能被100整除(或者能被400整除)。
    # 也就计算出y1与y2之间
    # 一共包含了多少个闰年了。
    return (y2//4 - y1//4) - (y2//100 - y1//100) + (y2//400 - y1//400)


timegm方法:

    timegm方法的使用,如下所示:

[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46) 
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import time
>>> import calendar
>>> t = time.time()
>>> t
1442020787.238788
>>> tm = time.gmtime(t)
>>> tm
time.struct_time(tm_year=2015, tm_mon=9, tm_mday=12, tm_hour=1, tm_min=19, tm_sec=47, tm_wday=5, tm_yday=255, tm_isdst=0)
>>> calendar.timegm(tm)
1442020787
>>> 


    可以看到,time模块的gmtime方法可以将时间戳转为UTC标准时区的时间(该时间存储在time.struct_time类型的对象里)。而calendar模块的timegm方法则可以反过来,将时间转为UTC标准时区的时间戳。有关时间戳以及time模块相关的内容,请参考上一篇文章。

    calendar模块的timegm方法对应的Python源代码如下(定义在/usr/local/lib/python2.7/calendar.py文件里):

EPOCH = 1970
_EPOCH_ORD = datetime.date(EPOCH, 1, 1).toordinal()


def timegm(tuple):
    """Unrelated but handy function to calculate Unix timestamp from GMT."""
    year, month, day, hour, minute, second = tuple[:6]
    days = datetime.date(year, month, 1).toordinal() - _EPOCH_ORD + day - 1
    hours = days*24 + hour
    minutes = hours*60 + minute
    seconds = minutes*60 + second
    return seconds


    上面的datetime.date(year, month, 1).toordinal()方法可以将年月日转换为以001年1月1日为起点的天数,例如:datetime.date(1,1,1).toordinal()会返回1表示第一天(以001年1月1日为起点),datetime.date(1,1,2).toordinal()则会返回2表示第二天,以此类推。

    因此,上面的datetime.date(year, month, 1).toordinal()减去_EPOCH_ORD后,再加上day,并减去1,得到的结果就是year年month月day日距离1970年1月1日的天数。

    在得到days天数后,将days乘以24(一天有24小时),再加上hour就可以得到对应的小时数。最后将小时转为分,分再转为秒,就可以得到UTC标准时区的时间戳了。

    以下是pdb调试器的结果:

[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46) 
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import calendar
>>> import pdb
>>> pdb.runcall(calendar.timegm, (2015, 9, 12, 1, 19, 47))
> /usr/local/lib/python2.7/calendar.py(612)timegm()
-> year, month, day, hour, minute, second = tuple[:6]
(Pdb) n
> ..................................................
-> days = datetime.date(year, month, 1).toordinal() - _EPOCH_ORD + day - 1
(Pdb) n
> ..................................................
-> hours = days*24 + hour
(Pdb) p days
16690
(Pdb) n
> ..................................................
-> minutes = hours*60 + minute
(Pdb) p hours
400561
(Pdb) n
> ..................................................
-> seconds = minutes*60 + second
(Pdb) n
> ..................................................
-> return seconds
(Pdb) p seconds
1442020787
(Pdb) c
1442020787
>>> 


    datetime模块的C源码定义在Modules/datetimemodule.c文件里,该模块的date类的toordinal方法对应的底层C函数为date_toordinal

[email protected]:~$ gdb -q python
....................................................
>>> import datetime
>>> datetime.date(2015,9,12).toordinal()
....................................................

Breakpoint 1, date_toordinal (self=0xb7ca0538)
    at /mnt/zenglOX/Python-2.7.8/Modules/datetimemodule.c:2680
2680	                                     GET_DAY(self)));
(gdb) s
2679	    return PyInt_FromLong(ymd_to_ord(GET_YEAR(self), GET_MONTH(self),
(gdb) s
ymd_to_ord (year=2015, month=9, day=12)
    at /mnt/zenglOX/Python-2.7.8/Modules/datetimemodule.c:330
330	    return days_before_year(year) + days_before_month(year, month) + day;
(gdb) c
Continuing.
735853
>>> 


    限于篇幅,请读者自行通过gdb调试器对date_toordinal相关的C源码进行分析。

weekday与monthrange方法:

    这两个方法的使用,如下所示:

[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46) 
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import calendar
>>> calendar.weekday(2015, 9, 12)
5
>>> calendar.monthrange(2015, 9)
(1, 30)
>>> 


    weekday方法所返回的值,用来表示指定的日期是星期几,0表示星期一,6表示星期天。例如上面的calendar.weekday(2015, 9, 12)返回的结果为5,就表示2015年9月12日是星期六。

    monthrange方法则会返回一个元组,该元组的第一项表示指定月份的第一天是星期几(0表示星期一,6表示星期天),第二项则表示指定的月份有多少天。例如上面的calendar.monthrange(2015, 9)返回的结果为(1, 30),就表示2015年9月1日是星期二,并且2015年的9月份一共有30天。

    这两个方法对应的Python源代码如下(都定义在/usr/local/lib/python2.7/calendar.py文件里):

# Constants for months referenced later
January = 1
February = 2

# Number of days per month (except for February in leap years)
mdays = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]

....................................................

def weekday(year, month, day):
    """Return weekday (0-6 ~ Mon-Sun) for year (1970-...), month (1-12),
       day (1-31)."""
    return datetime.date(year, month, day).weekday()


def monthrange(year, month):
    """Return weekday (0-6 ~ Mon-Sun) and number of days (28-31) for
       year, month."""
    if not 1 <= month <= 12:
        raise IllegalMonthError(month)
    day1 = weekday(year, month, 1)
    ndays = mdays[month] + (month == February and isleap(year))
    return day1, ndays


    从上面的代码里可以看到,monthrange会先通过weekday方法得到指定月份的第一天是星期几,并将该星期作为结果元组的第一项。接着,monthrange方法会从mdays数组里得到指定的月份一共包含了多少天,并将其作为结果元组的第二项。当然,如果是闰年的二月份,则mdays里对应的值28,还需要再加上一天,即29天。

    weekday方法则会通过datetime模块里的date类中的weekday方法去完成具体的操作。date类的weekday方法会调用的底层C函数为date_weekday(该C函数定义在Modules/datetimemodule.c文件里):

[email protected]:~$ gdb -q python
....................................................
>>> import calendar
>>> import pdb
>>> pdb.runcall(calendar.weekday, 2015, 9, 12)
> /usr/local/lib/python2.7/calendar.py(113)weekday()
-> return datetime.date(year, month, day).weekday()
(Pdb) s
....................................................

Breakpoint 1, date_weekday (self=0xb7b715f8)
    at /mnt/zenglOX/Python-2.7.8/Modules/datetimemodule.c:2686
2686	    int dow = weekday(GET_YEAR(self), GET_MONTH(self), GET_DAY(self));
(gdb) n
2688	    return PyInt_FromLong(dow);
(gdb) p dow
$1 = 5
(gdb) c
Continuing.
--Return--
> /usr/local/lib/python2.7/calendar.py(113)weekday()->5
-> return datetime.date(year, month, day).weekday()
(Pdb) c
5
>>> 


    限于篇幅,这里不会对date_weekday相关的C源码进行介绍,请读者自行通过gdb调试器来进行分析。

结束语:

    以上就是和calendar模块相关的内容,该模块里没介绍到的代码,请读者自行通过pdb调试器与gdb调试器来进行分析。

    人的生存法则很简单,就是忍人所不忍,能人所不能。忍是一条线,能是一条线,两者的间距就是生存机会。

——  天道
 
上下篇

下一篇: Python用户自定义函数

上一篇: Python的time模块

相关文章

Python的变量类型

Python基本的I/O操作 (一)

Python基本的I/O操作 (三)

Python三角函数

Python元组类型及相关函数

Python循环语句