大家好,我是Samaritan。
Python中有不少基础又实用的模块,甚至是无需安装的标准库。其中大家最先想到的可能就是time了,反正我第一个接触的模块和用法就是:
import time
time.sleep(sec)
当然啦,还有random、math、tutle和smtplib(上一篇已经简单讲啦)等等也都是如此,有机会我再写博文引申出去。
因为今天我们文章的主题是:如何成为时间管理大师
咳咳…错了,是关于时间(和日期)的相关模块及其用法~
简单学过python基础语法的朋友可能就要吐槽这也太基础了吧?别急,“时间”也许就是你最熟悉的陌生人。我们就从time开始说起。
Python 程序能用很多方式处理日期和时间,转换日期格式是一个常见的功能。
Time有三种格式:
时间戳(time_stamp)类型为浮点(float)
自定义的格式型字符串时间(format_time)类型为字符串(str)
时间元组(struct_time)
举例说明:
import time
time.time()
#获取当前时间戳
time.strftime('%Y-%m%d %H:%M:%S')
#获取自定义格式的字符串时间,下面会介绍各种格式化符号
time.localtime()
#获取当前本地时间元组
print输出:
1591655104.5793982
time.struct_time(tm_year=2020, tm_mon=6, tm_mday=9, tm_hour=6, tm_min=25, tm_sec=4, tm_wday=1, tm_yday=161, tm_isdst=0)
2020-0609 06:25:04
需要知道:每个时间戳都以自从 1970 年 1 月 1 日午夜(历元)经过了多长时间来表示。时间间隔是以秒为单位的浮点小数。
时间日期格式化符号:
%y 两位数的年份表示(00-99)
%Y 四位数的年份表示(000-9999)
%m 月份(01-12)
%d 月内中的一天(0-31)
%H 24小时制小时数(0-23)
%I 12小时制小时数(01-12)
%M 分钟数(00=59)
%S 秒(00-59)
%a 本地简化星期名称
%A 本地完整星期名称
%b 本地简化的月份名称
%B 本地完整的月份名称
%c 本地相应的日期表示和时间表示
%j 年内的一天(001-366)
%p 本地A.M.或P.M.的等价符
%U 一年中的星期数(00-53)星期天为星期的开始
%w 星期(0-6),星期天为星期的开始
%W 一年中的星期数(00-53)星期一为星期的开始
%x 本地相应的日期表示
%X 本地相应的时间表示
%Z 当前时区的名称
%% %号本身
常用格式转换:
time.localtime(浮点)
#时间戳转时间元组
time.strftime(格式化符号, 时间元组)
#时间元组转格式化时间
time.strptime(格式化时间, 格式化符号)
#格式化时间转时间元组
time.mktime(时间元组)
#时间元组转时间戳
常用方法:
import time
#计时器
start = time.time()#获取当前时间戳
time.sleep(2)#防止太快结束看不清结果,睡眠2秒
end = time.time()#再次获取当前时间戳
print(end - start)#时间戳相减得到用时秒数
输出结果:
2.0007362365722656
#得到了程序共运行耗时(秒)
这样就是一个最简易的计时器啦。再来看看另一种计时方法:
start1 = time.perf_counter()
start2 = time.process_time()
time.sleep(2)
end1 = time.perf_counter()
end2 = time.process_time()
print(end1 - start1)
print(end2 - start2)
输出结果:
2.0001126840000003
0.0
看出区别了吗?
time.perf_counter()返回计时器的精准时间(系统的运行时间),包含整个系统的睡眠时间。由于返回值的基准点是未定义的,所以,只有连续调用的结果之间的差才是有效的。
time.process_time()返回当前进程执行 CPU 的时间总和,不包含睡眠时间。由于返回值的基准点是未定义的,所以,只有连续调用的结果之间的差才是有效的。
除了刚才说过的获取当前本地时间元组外,还有方法可以获得格林威治时间。
now = time.gmtime()
#接收时间戳并返回格林威治天文时间下的时间元组,默认当前时间。
date = time.strftime('%Y-%m%d %H:%M:%S',date)
#将时间戳转为当前格林威治时间字符串
print结果:
time.struct_time(tm_year=2020, tm_mon=6, tm_mday=8, tm_hour=23, tm_min=24, tm_sec=37, tm_wday=0, tm_yday=160, tm_isdst=0)
2020-0608 23:24:37
再来看看两个其他转换方式
asctime() 将以日期元组或者struct_time表示的时间转化为字符串,不指定参数则默认是当前时间
ctime() 以秒代表的时间转换为字符串, 不指定参数则默认是当前时间
time.asctime((2077,1,1,8,10,10,3,1,0))#有日期元组参数
time.asctime()#无参数
time.ctime(999999999)#有秒数(自1970年1月1日午夜)
time.ctime()#无参数
print结果:
Thu Jan 1 08:10:10 2077
Tue Jun 9 07:46:46 2020
Sun Sep 9 09:46:39 2001
Tue Jun 9 07:46:46 2020
可以看出这种方式下我们除了年月日时间外还可以得到星期几,无参数时time.ctime()相当于time.asctime()。
现在你已经学会了通过时间戳(秒数)进行加减来实现计时器等功能,那接着我们再来看看对日期又能如何操作呢?
Datetime模块为日期和时间处理同时提供了简单和复杂的方法。支持日期和时间算法的同时,实现的重点放在更有效的处理和格式化输出。
我们从几个现实中的问题来看看datetime模块是怎么帮助我们的吧。
通过生日来计算一个人的年龄很容易,但是如果想知道出生至今经过了多少天呢?
用datetime可以这样实现:
from datetime import datetime#从datetime模块导入datetime类方法
#年龄计算
today = datetime.now()#返回当前日期时间的datetime对象
birthday = datetime(1990,11,4).year#得到生日的年份
now = today.year#得到当天的年份
age = now - birthday#年份相减
print(f'{today}\n{birthday}\n{now}\n{age}')
输出结果:
2020-06-09 20:02:08.865202
1990
2020
30
方法datetime(年,月,日)可以得到一个该日期参数的datetime对象。
可以看出来对datetime对象.year用法可以得到年份,再让年份相减就是年龄啦。
还是以这个生日为例,想知道出生以来经过了多少天可以这么写:
from datetime import datetime
#出生天数计算
today = datetime.now()
birthday = datetime(1990,11,4)#得到生日日期
age = today - birthday#日期相减
days = age.days
print(f'{birthday}\n{age}\n{days}')
print(type(today))
print(type(age))
输出结果:
1990-11-04 00:00:00
10810 days, 20:19:28.656464
10810
<class 'datetime.datetime'>
<class 'datetime.timedelta'>
查看类型可以看出datetime对象之间可以直接将日期相减,从而得到了出生至今已经过去了10810天的结果。而age类型则是timedelta,该类是用来计算二个datetime对象的差值的。看看该模块还有哪些类呢?
datetime模块中包含如下类:
类名 | 功能说明 |
---|---|
date | 日期对象,常用的属性有year, month, day |
time | 时间对象 |
datetime | 日期时间对象,常用的属性有hour, minute, second, microsecond |
datetime_CAPI | 日期时间对象C语言接口 |
timedelta | 时间间隔,即两个时间点之间的长度 |
tzinfo | 时区信息对象 |
想了解全部细节,大家还要通过搜索或者查看官方文档,自行深入学习哦,这一篇主题不是详解深入学习就不全部列举了。我们回到现实问题上~
生活中常有纪念日或者重要的日子,为了不因忘记而被(女朋友/老婆)罚跪CPU,大家一般会建立日程提醒,其实也就是一种日期计算器告诉你两个设定的日期之间相差多久。
网上一搜也有很多网页版日期计算器,假如我想知道现在离赛博朋克2077发售还有多久?(别跳票)
把我们刚学会的用上,代码可以这么写:
from datetime import datetime, timedelta
now = datetime.now() # 当前时刻
cyberpunk = datetime(2020, 9, 17, 0, 0, 0) # 发售日零时
delta = cyberpunk - now # 计算日期间隔
allsec = delta.total_seconds() # 获取间隔的总秒数
wait = delta.days#获取间隔的总天数
daytohour = divmod((allsec - wait*86400), 3600)#从总秒数和天数换算小时数,不懂divmod()函数的下面我会讲
hourtomin = daytohour[1]#获取小时后的剩余秒数
hour = daytohour[0]#获取小时数
mintosec = divmod(hourtomin, 60)#从小时数余下的秒数换算分钟数
min = mintosec[0]#获取分钟数
sec = mintosec[1]#获取秒数
print(now)
print(cyberpunk)
print(allsec)
print(wait)
print(daytohour)
print(hourtomin)
print(mintosec)
print(f'距离cyberpunk2077发售还有;{wait}天{hour}小时{min}分钟{sec}秒')
输出结果:
2020-06-09 21:49:10.243045
2020-09-17 00:00:00
8561449.756955
99
(2.0, 649.7569549996406)
649.7569549996406
(10.0, 49.756954999640584)
距离cyberpunk2077发售还有;99天2.0小时10.0分钟49.756954999640584秒
这样我们就得到了还要焦急等待发售的时间~
我把每个重要的变量都打印出来,应该可以很直观的看明白啦,这里就提一下divmod()函数。
divmod(x,y)函数输入两个参数,返还一个包含商和余的元组,例如divmod(7,3),输出(2,1),所以代码中先得到总秒数和天数,再通过3600秒是1小时,60秒是1分钟将剩余秒数不断换算成日常时间单位。
Python提供了datetime.tzinfo这一抽象的基类,如果我们想使用时区,则必须继承这个类来实现自己的时区定义。
我们国家使用的是东八区,也就是比世界协调时间(UTC)/格林尼治时间(GMT)快8小时的时区,我国没有采用夏令时,所以直接返回0,而对于采纳了夏令时的国家,则需要将夏令时的偏移量考虑进去。
例如我们可以这样定义一个UTC时区和我们所在的东八区:
from datetime import timedelta,tzinfo
ZERO = timedelta(0)
HOUR = timedelta(hours=1)
class UTC(tzinfo):
'UTC'
#这个方法返回本地时间与UTC时间的时差
def utcoffset(self, dt):
return ZERO
#这个方法用来返回时区的名称
def tzname(self, dt):
return "UTC"
#这个方法返回夏令时偏移量,这里默认没有设置为0
def dst(self, dt):
return ZERO
class GMT8(tzinfo):
'东八区'
def utcoffset(self, dt):
return HOUR * 8
def tzname(self, dt):
return 'GMT-8'
def dst(self, dt):
return ZERO
再来看看datetime与时区的结合使用:
#接着上面代码
today = datetime.today()
todayutc = datetime.utcnow()
todayutc2 = datetime.now(UTC())
todaygmt = today.replace(tzinfo=GMT8())
todaygmt2 = datetime.now(GMT8())
print结果:
2020-06-09 22:45:26.187996
2020-06-09 14:45:26.187996
2020-06-09 14:45:26.187996+00:00
2020-06-09 22:45:26.187996+08:00
2020-06-09 22:45:26.187996+08:00
比较todaygmt的变量结果可以看出类方法now,在不考虑时区的时候,它的作用和类方法today基本是一致的,我们在使用now的时候传递一个tzinfo,两种方式是等效的。
比较todayutc的变量结果可以看出除了now()方法外,系统还提供了utcnow()方法,这个方法没有参数,返回的是UTC的时间,但是这个方法创建的datetime对象是不带时区信息的,和.today()方法相同。
另外,通过代码理解replace()方法:
from datetime import datetime, date
a = datetime(2077, 12, 30)
b = date(2077, 12, 30)
c = a.replace(day=26)
d = b.replace(month=1)
print结果:
2077-12-30 00:00:00
2077-12-30
2077-12-26 00:00:00
2077-01-30
可以看出date()方法和datetime()方法都能获取参数日期信息,但是date()方法不包含时间信息。而通过replace()可以替换对象的时间参数(年/月/日)。
当我想要获取下个月的今天,有多种方法实现,但最方便的是直接把月份进行加一操作(不考虑跨年和日期超过月份最大值的情况),这个时候replace函数就派上用场了:
today = datetime.today()
next_month = today.replace(month=today.month+1)
nextmonth = next_month.strftime('%Y/%m/%d %H:%M:%S')
print结果:
2020-07-09 23:05:06.003318
2020/07/09 23:05:06
replace()方法允许我们对datetime的任意字段进行替换,并返回一个新的datetime对象,这个新的对象在其他字段上与原有对象保持一致。
我们还可以继续使用strftime()方法,将时间信息格式化成我们希望的样式。
模块还提供了datetime.astimezone(tz)方法,使用这个方法可以在各个时区之间来回转换时间:
today = datetime.utcnow()
today1 = today.replace(tzinfo=UTC())
today2 = today1.astimezone(GMT8())
print结果:
2020-06-09 15:15:29.086825
2020-06-09 15:15:29.086825+00:00
2020-06-09 23:15:29.086825+08:00
可以看出使用了replace()方法+tzinfo后使结果带有了时区信息,再通过astimezone()方法切换时区。
有两点需要注意:
最后,还记得刚讲过的time吗?这里有2个方法通用时间戳获取datetime:
import time
from datetime import datetime
now = time.time()#获取时间戳
dt = datetime.fromtimestamp(now)#通用时间戳获取本地datetime
dtutc = datetime.utcfromtimestamp(now)#通用时间戳获取UTCdatetime
print结果:
1591716413.7337935
2020-06-09 23:27:41.630595
2020-06-09 15:26:53.733793
现在你已经学会了对时间和日期进行操作,那假如要具体到星期几又该怎么办呢?
来看看本次要讲的最后一个标准模块,calendar日历功能。
Calendar是与日历相关的模块,模块文件里定义了很多类型。主要有Calendar,TextCalendar以及HTMLCalendar类型。
其中,Calendar是TextCalendar与HTMLCalendar的基类。该模块文件还对外提供了很多方法,例如:calendar,month,prcal,prmonth之类的方法。
通过calendar.calendar(year,w=2,l=1,c=6)方法简单粗暴的直接获得一年的日历,直接上代码:
import calendar
print(calendar.calendar(2020))#获取2020年日历
输出结果:
2020
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 5 1 2 1
6 7 8 9 10 11 12 3 4 5 6 7 8 9 2 3 4 5 6 7 8
13 14 15 16 17 18 19 10 11 12 13 14 15 16 9 10 11 12 13 14 15
20 21 22 23 24 25 26 17 18 19 20 21 22 23 16 17 18 19 20 21 22
27 28 29 30 31 24 25 26 27 28 29 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.month()和calendar.monthcalendar():
print(calendar.month(2020,7))
print(calendar.monthcalendar(2020,7))
输出结果:
July 2020
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 31
[[0, 0, 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, 0, 0]]
可以看出两种方法的区别是前者直接获得日历,而后者将该月日历的每一周以嵌套列表的形式返回。
怎么样,觉得还不够实用吗?
没关系,跟我回忆一下以前做题的时候我们是如何判断闰年的:
year = int(input("输入一个年份: "))
if (year % 4) == 0:
if (year % 100) == 0:
if (year % 400) == 0:
print("{0} 是闰年".format(year)) # 整百年能被400整除的是闰年
else:
print("{0} 不是闰年".format(year))
else:
print("{0} 是闰年".format(year)) # 非整百年能被4整除的为闰年
else:
print("{0} 不是闰年".format(year))
总共11行代码,3个if判断语句嵌套在一起看着就麻烦!
现在你有了calendar.isleap()方法:
import calendar
calendar.isleap(2018)#判断2018是否为闰年
calendar.isleap(2000)#判断2000是否为闰年
print结果:
False
True
2行代码就解决了!函数返回布尔值,闰年为True,否则为False。
关于闰年,还有一个calendar.leapdays()方法,该函数返回某两年之间的闰年总数:
calendar.leapdays(2000, 2077)
print结果:
20
返回结果表示2000年到2077年之间有20个闰年。
我们还可以通过calendar.monthrange()方法查看某一个月的信息,该函数返回两个整数,第一个数为某月第一天为星期几,第二个数为该月有多少天。
calendar.monthrange(2020,8)#获得2020年8月的信息
print结果:
(5, 31)
返回的元组前一个数字5代表周六(从0开始为周一),后一个数字31代表8月最大天数。
最后来跟我一起回忆一下,time模块中我们介绍了时间元组如何转为时间戳(通过time.mktime()方法),而calendar中也有另一种方法可以达成这样的效果:
import time,calendar
now = time.localtime()
#获取当前本地时间元组
time = time.mktime(now)
#用time模块方法将时间元组转为时间戳
calendar = calendar.timegm(now)
#用calendar模块方法将时间元组转为时间戳
print结果:
time.struct_time(tm_year=2020, tm_mon=6, tm_mday=10, tm_hour=7, tm_min=21, tm_sec=24, tm_wday=2, tm_yday=162, tm_isdst=0)
1591773684
1591744884.0
单独了解了各个模块之后,慢慢可以将学会的模块和方法串联起来了,这样的感觉很不错不是吗?
至此python中标准库内的时间模块(time、datetime、calendar)就都介绍完毕。
时间模块在数据处理方面应用较多,学会将字符串格式的时间转变成可以进行时间差计算的格式,比如datetime类型、时间戳,都是非常实用的技巧。有时可能看起来有多种方法可以解决问题,想要灵活多变的运用这些模块和方法,只有不断思考和尝试直到熟练掌握时间格式之间的转换。
将以上所有的内容都掌握了,相信你在之后的工作中对于有时间格式的表格或是爬虫采集的时间信息处理起来也会感到更加得心应手。即使现在写爬虫的时候,我也必须经常用上time.sleep()以控制爬取速度,真的是很棒了!
如果觉得这样还不足以让你成为python时间管理大师的话,我会在下一期继续讲标准库以外关于时间的模块以及实用的方法作为补充~
题外话:时间是什么呢?
Time is an illusion for me。
我在这儿,也在那里。
以上,
感谢您的耐心阅读,欢迎关注我共同讨论python和爬虫~学习编程这条路,我们一起走一段。