16. 练习:万年历

Hi, 大家好。我是茶桁。

上一节课最后,我让我家去预习一下日历和时间的相关模块,不知道大家有没有去预习。不管如何,这节课,让我们开始做一个练习:万年历。

没有预习的小伙伴也跟着一起,在本次练习完成的时候,相信你会对这些模块有了初步的了解。

好,让我们开始吧。

首先,我们需要来看看calendar.monthrange()这个函数,它属于calendar模块内,返回指定年份和月份的数据,月份的第一天是周几,和月份中的天数。

import calendar

res = calendar.monthrange(2023, 6)
print(res)

---
(3, 30)

我们接收了返回值,但是这个3和30分别是什么意思呢?我们打开日历看一下就明白了:

image-20230813010916395.png

如图所见,2023年的6月份一共是30天,第一天是周四。这也正是(3, 30)的含义。之所以是3而不是4,是因为是从0开始计算的,也就是说,周一是0。比如,2023年5月的第一天就是周一,我们来看看是不是这么回事:

res = calendar.monthrange(2023, 5)
print(res)

---
(0, 31)

那有了这个,我们要做一个当月的日历就简单了,还记得我们之前做过一个星星的矩阵吗?是一样的概念,这是这次直接换成了数字而已, 来,让我们从最基本框架开始(还是以6月份数据来做):

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天的?让我们来修复一下这个问题:

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了。

完成了,顺便还打印了一行星期几。可是问题是,没有和实际情况对齐对吧?没事,我们继续来改动。

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月来看看和实际情况是否相符, 并且,这次我们多加一些内容,将其中的年份和月份也都打印出来:

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  

我们来看看实际情况是不是如此:

image-20230813014347015.png

没错,确实如此。7月份的第一天从周六开始,一个月有31天,周一为最后一天。那说明,我们上面写的内容真实有效。

那现在要干嘛呢?当然是封装成一个函数,以yearmonth为参数,这样,不管我想要查询任意月份,只要我输入对应参数就可以了:

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()方法,其返回参数如下:

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)

那我们如何从中拿到我需要的内容?我们接着看:

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  

很明显,我们用yearmonth两个变量从得到的localtime里获取了其中的年份和月份信息。然后重新调用showdate()封装函数,将其传入。也就打印出了我们当前月份的日历。

可是这都是静态的,我们总不能就只看我们当月的月份。所以,我们接着扩展这个程序。

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月份吧。这又该怎么办呢?

别着急,我们继续研究下该怎么改善:

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解决了问题。

这就完了吗?并没有。在打印的过程当中,我发现一个问题,就是我们的月份信息不断的叠加,那导致打印区变的过长,最终都没打印完全。这并不是我们想要的,如图:

image-20230813021632548.png

所以,其实我都还没验证到底12月份之后是否正常变为2024年1月了。忍不了,这个问题也必须要解决。

那如何解决呢?我想起来,在Linux命令中有一个clear命令,其功能就是将当前窗口内容清理掉。那Python中又有很多和系统操作相同的功能,这次有没有呢?就算没有,我记得os.system()似乎可以调用系统命令的。

那,我们试试看:

import os
while True:
    os.system('clear')
    # 默认输出当前年月的日历信息
   ...

实际操作了一下,无法在Jupyter Notebook中实现,但是当你将代码存储成.py文件之后,在shell中执行是完全可以实现的。如下图:

ScreenFlow-1.gif

至此,我们本次的练习「万年历」就完成了。

大家可以下载我的源码来研究,第16课,包含一个.ipynb笔记本文件和一个.py完整文件。

有什么问题,评论区留言。

好了,下课,咱们下节课再见。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354

推荐阅读更多精彩内容