Hi, 大家好。我是茶桁。
上一节课最后,我让我家去预习一下日历和时间的相关模块,不知道大家有没有去预习。不管如何,这节课,让我们开始做一个练习:万年历。
没有预习的小伙伴也跟着一起,在本次练习完成的时候,相信你会对这些模块有了初步的了解。
好,让我们开始吧。
首先,我们需要来看看calendar.monthrange()
这个函数,它属于calendar
模块内,返回指定年份和月份的数据,月份的第一天是周几,和月份中的天数。
1 2 3 4 5 6 7 import calendar res = calendar.monthrange(2023 , 6 )print (res) --- (3 , 30 )
我们接收了返回值,但是这个 3 和 30
分别是什么意思呢?我们打开日历看一下就明白了:
如图所见,2023 年的 6 月份一共是 30
天,第一天是周四。这也正是(3, 30)
的含义。之所以是 3 而不是
4,是因为是从 0 开始计算的,也就是说,周一是 0。比如,2023 年 5
月的第一天就是周一,我们来看看是不是这么回事:
1 2 3 4 5 res = calendar.monthrange(2023 , 5 )print (res) --- (0 , 31 )
那有了这个,我们要做一个当月的日历就简单了,还记得我们之前做过一个星星的矩阵吗?是一样的概念,这是这次直接换成了数字而已,
来,让我们从最基本框架开始(还是以 6
月份数据来做 ):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 days = res[1 ] week = res[0 ] + 1 d = 1 while d <= days: for i in range (1 , 8 ): print ('{:0>2d}' .format (d), end=" " ) d+=1 print () --- 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
这样,我们就将天数打印出来了。可是,明眼人一眼就看出了问题,这一月只有
30 天,怎么得到的 35 天的?让我们来修复一下这个问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 days = res[1 ] week = res[0 ] + 1 print ('一 二 三 四 五 六 日' ) d = 1 while d <= days: for i in range (1 , 8 ): if d > days: print ('' , end='' ) else : print ('{:0>2d}' .format (d), end=" " ) d+=1 print () --- 一 二 三 四 五 六 日 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
我们在代码中加了一层判断,如果循环中的d
大于days
了,那我们就直接输出空格,否则才正确输出格式化的数字,那么这样就可以不输出31-35
了。
完成了,顺便还打印了一行星期几。可是问题是,没有和实际情况对齐对吧?没事,我们继续来改动。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 days = res[1 ] week = res[0 ] + 1 print ('一 二 三 四 五 六 日' ) d = 1 while d <= days: for i in range (1 , 8 ): if d > days or (d==1 and i<week): print (' ' , end='' ) else : print ('{:0>2d}' .format (d), end=" " ) d+=1 print () --- 一 二 三 四 五 六 日 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
我们在之前判断d
大于days
的判断上再加上一层,不仅如此,当d==1
并且i
小于week
的时候,也都是出制表符,那自然最开始和最末尾不该出现数字的地方都被制表符补齐了。
我们再来多做一次实验,将月份改成 7 月来看看和实际情况是否相符,
并且,这次我们多加一些内容,将其中的年份和月份也都打印出来:
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 31 32 year = 2023 month = 7 res = calendar.monthrange(year, month) days = res[1 ] week = res[0 ] + 1 print (f'========= {year} 年 {month} 月 =========' )print ('一 二 三 四 五 六 日' )print ('=' *32 ) d = 1 while d <= days: for i in range (1 , 8 ): if d > days or (d==1 and i<week): print (' ' , end='' ) else : print ('{:0>2d}' .format (d), end=" " ) d+=1 print () --- ========= 2023 年 7 月 ========= 一 二 三 四 五 六 日 ================================ 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
我们来看看实际情况是不是如此:
没错,确实如此。7 月份的第一天从周六开始,一个月有 31
天,周一为最后一天。那说明,我们上面写的内容真实有效。
那现在要干嘛呢?当然是封装成一个函数,以year
和month
为参数,这样,不管我想要查询任意月份,只要我输入对应参数就可以了:
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 31 32 33 def showdate (year, month ): res = calendar.monthrange(year, month) days = res[1 ] week = res[0 ] + 1 print (f'========= {year} 年 {month} 月 =========' ) print ('一 二 三 四 五 六 日' ) print ('=' *32 ) d = 1 while d <= days: for i in range (1 , 8 ): if d > days or (d==1 and i<week): print (' ' , end='' ) else : print ('{:0>2d}' .format (d), end=" " ) d+=1 print () showdate(2023 , 12 ) --- ========= 2023 年 12 月 ========= 一 二 三 四 五 六 日 ================================ 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
我们尝试调用了一下封装好的函数,输出 2023 年 12
月份日历,大家可以看看自己手机里的日历,绝对真实可靠。
好了,现在我们要完成万年历的制作了。
万年历,自然是有一个初始值,那这个初始值必须是当前时间最妥当。不然你们试试打开你们的日历,看是不是打开默认都是指向的「今天」。
那么首先,让我们获取一下当前系统的年月,这个就需要用到我们的time
模块里的localtime()
方法,其返回参数如下:
1 time.struct_time(tm_year=2023 , tm_mon=8 , tm_mday=13 , tm_hour=1 , tm_min=50 , tm_sec=38 , tm_wday=6 , tm_yday=225 , tm_isdst=0 )
那我们如何从中拿到我需要的内容?我们接着看:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import time dd = time.localtime() year = dd.tm_year month = dd.tm_mon showdate(year, month) --- ========= 2023 年 8 月 ========= 一 二 三 四 五 六 日 ================================ 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
很明显,我们用year
和month
两个变量从得到的localtime
里获取了其中的年份和月份信息。然后重新调用showdate()
封装函数,将其传入。也就打印出了我们当前月份的日历。
可是这都是静态的,我们总不能就只看我们当月的月份。所以,我们接着扩展这个程序。
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 31 32 33 34 35 36 import time ...while True : showdate(year, month) print (' < 上一月 下一月 > ' ) c = input ('请输入您的选择 "<" or ">":' ) if c == '<' : month -= 1 elif c == '>' : month += 1 else : print ('您输入内容错误,请重新输入"<"或者">"来选择。' ) --- ========= 2023 年 8 月 ========= 一 二 三 四 五 六 日 ================================ 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 < 上一月 下一月 > > ========= 2023 年 9 月 ========= 一 二 三 四 五 六 日 ================================ 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 < 上一月 下一月 >
我们在程序运行中没有图形界面,无法接收鼠标信息,那就用输入<
和>
来代替一下,其逻辑是相同的。
可以看到,我们做了一个判断,当输入<
的时候,我月份数字减少,当我们输入>
的时候,月份数字增加。所以当我们输入>
的时候,表示下一月,数字增加,也就打印出了
9 月份的月份信息。
可是问题又来了,我们总不能无限加或者无限减下去吧,12 月份之后不可能是
13 月份吧。这又该怎么办呢?
别着急,我们继续研究下该怎么改善:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import time ...while True : ... if c == '<' : month -= 1 if month < 1 : month = 12 year -= 1 elif c == '>' : month += 1 if month > 12 : month = 1 year += 1 elif c == 'exit' : break else : print ('您输入内容错误,请重新输入"<"或者">"来选择。' )
既然月份是固定的数字,那就是最好办的,我们让变量控制在范围内不就好了。如果超过数字了,那就改变年份,将月份回滚为最小值或者最大值不就好了。两个简单的if
解决了问题。
这就完了吗?并没有。在打印的过程当中,我发现一个问题,就是我们的月份信息不断的叠加,那导致打印区变的过长,最终都没打印完全。这并不是我们想要的,如图:
所以,其实我都还没验证到底 12 月份之后是否正常变为 2024 年 1
月了。忍不了,这个问题也必须要解决。
那如何解决呢?我想起来,在Linux
命令中有一个clear
命令,其功能就是将当前窗口内容清理掉。那
Python
中又有很多和系统操作相同的功能,这次有没有呢?就算没有,我记得os.system()
似乎可以调用系统命令的。
那,我们试试看:
1 2 3 4 5 import oswhile True : os.system('clear' ) ...
实际操作了一下,无法在 Jupyter Notebook
中实现,但是当你将代码存储成.py
文件之后,在shell
中执行是完全可以实现的。如下图:
至此,我们本次的练习「万年历」就完成了。
大家可以下载我的源码来研究,第 16
课,包含一个.ipynb
笔记本文件和一个.py
完整文件。
有什么问题,评论区留言。
好了,下课,咱们下节课再见。