在平时开发工作中,我们经常需要用到日期时间,比如日志记录、日期时间的计算、时间字段的赋值等。Python 提供了 time 模块、datatime 模块及子模块、calendar 模块等内置模块,可实现对日期时间的设置、获取、转换等常见操作。
提起日期时间,有必要先梳理下相关术语的含义,有助于理解使用,常见的术语有:
表示时间开始的起点,不同平台上这个时间点的值不尽相同,对于 Unix 而言,epoch time 为 1970-01-01 00:00:00 UTC。
UTC 是 Universal Coordinated Time 的简称,也被称为 GMT(Greenwich Mean Time - 格林威治标准时间),UTC 是现代计时的基础,它为累计时间和本地时间之间的转换提供了一条通用的基线,UTC 时间通常用 Z 表示。
以格林威治(本初子午线)作为世界计算时间和经度的起点,UTC 的时区偏移量为 0。东 N 区的时间比 UTC 时间早 N 个小时,即 UTC time + N 小时就是东 N 区的本地时间;而西 N 区时间比 UTC 时间晚 N 个小时,即 UTC time - N 小时就是西 N 区的本地时间。
通常说的北京时间(本地时间的一种),指的是中国北京位于东八区,比 UTC 时间早8个小时,北京时间则用 UTC+8 表示。
也称为 Unix 时间或 POSIX 时间,时间戳是表达时间的一种方式,表示从格林尼治时间1970年1月1日0时0分0秒开始到现在所经过的毫秒数(需要注意,有些编程语言的相关方法返回的是秒数)。时间戳本质上是时间差值,该值与时区无关。
当使用展示时间格式表达时间含义时,需要将日期和时间分开表示,可分为年、月、日、时、分、秒等,比如当前北京时间为:2022/11/26 18:16:59。
目前用 ISO 8601 标准描述展示时间格式,全球日期和时间的写法为:8位日期格式,T或者空格,4-9位时间,使用Z或者+/- 2位时区偏移量,秒(可选),如下:
- 2022-11-26T19:10:59.123+08
- 2022-11-26 19:10:59-08
- 2022-11-26 19:10:59Z
- 2022-11-26 19:10:00.123+23:45
需要注意,展示时间格式与特定的日历也有一定的关联。世界上不同的地方存在着不同的历法,表达时间含义也会有所区别,比如:公历有平年和闰年的区别;一些宗教历法中会以某些重要人物作为开始时间(xxx二世)等等。
不过,我们也可以使用时间戳表达时间含义,只是没有展示时间格式看起来简单友好。
时区是一组用于确定本地时间的规则,与特定地理区域的累计时间相关。而累计时间是一种在计算机中标识时间的方式,从特定时间点(也称“纪元”)到当前时间累计的整数单位时间。
如果要关联到本地时间,时区规则中必须要考虑时区偏移量和任何夏令时的变更。
时区偏移量是一个数量,根据世界各地相对于本初子午线的位置,基于 UTC 时间进行加减得到。通常偏移量以1小时为间隔单位,但也可以存在不同,如 30 分钟或 45 分钟。时区偏移量的表示,通常在时间格式的后面+或-偏移量,比如北京时间的偏移量可表示为:2022-11-26 18:20:20+08:00。
夏令时(DST),也称为“夏季时间”,主要是为了让人们在晚上有更多日照时间。在不同国家夏令时是不同的(甚至一个国家部分地区DST也不一样),并且常常会由于一些特殊事件,而进行一次性的修改。同时,并非所有地区都遵守夏令时,通常靠近赤道的地区不需要夏令时的。在做时间转换时,了解当地什么时间引入夏令时,什么时间废除夏令时,以及夏令时什么时间开始,什么时间结束(每年可能有所不同),是非常重要的。
浮选时间仅仅代表一个名义时间,在世界各地的所有时区都会以相同的方式呈现,它没有对应明确的累计时间,我们通常称此类时间为浮选时间。
浮选日期和时间表示:
- # 和全球时间和日期格式一致, 但没有时区偏移量信息
- 2022-11-26T19:10:59.123
- 2022-11-26 19:10:59
浮选时间不会属于特定的时区,当绑定上对应的时区信息后,浮选时间会生成一系列可被接收的累计时间的值。举一些浮选时间事件的例子,有助于理解,比如:早上八点上班打卡、卡塔尔世界杯开幕时间、王者荣耀明天开启新赛季等等,这些都不需要考虑时区。
至此,了解和熟悉相关日期时间术语含义后,可以玩转起来了。
time 模块用于对时间类型的获取及转换操作,而时间类型大致分为:时间字符串、时间戳、时间元组,特点如下:
参数选项及含义,参考如下:
常用的时间日期格式有:%Y-%m-%d %H:%M:%S、%Y/%m/%d %H:%M:%S、%Y%m%d%H%M%S。
获取时间字符串的方法有:ctime()、asctime(),默认格式为:星期 月 日 时:分:秒 年。
- # WARNING:root:当前时间(字符串): Sun Nov 27 10:54:55 2022, type:
- logging.warning(f'当前时间(字符串): {time.ctime()}, type: {type(time.ctime())}')
- # WARNING:root:当前时间(字符串): Sun Nov 27 10:54:55 2022, type:
- logging.warning(f'当前时间(字符串): {time.asctime()}, type: {type(time.asctime())}')
时间戳的本质是时间差值,因为默认时间单位为秒,返回的是 float 类型,获取时间戳的方法有:time()、time_ns()。
- # WARNING:root:当前时间(时间戳,单位秒): 1669517695.3470187, type:
- logging.warning(f'当前时间(时间戳,单位秒): {time.time()}, type: {type(time.time())}')
- # WARNING:root:当前时间(时间戳,单位纳秒): 1669517695347018700, type:
- logging.warning(f'当前时间(时间戳,单位纳秒): {time.time_ns()}, type: {type(time.time_ns())}')
也称为时间数组,是以元组类型作为参数或返回结果,获取日期元组的方法有:mgtime()、localtime(),前者是 UTC 时间,后者是本地时间。
- # 返回UTC时间
- logging.warning(f'当前时间(时间元组): {time.gmtime()}, \ntype: {type(time.gmtime())}')
- # 返回本地时间
- logging.warning(f'当前时间(时间元组): {time.localtime()}, \ntype: {type(time.localtime())}')
日志打印如下:
时间字符串、时间戳、时间元组之间是可以相互转换的,转换方法如下:
<1>、时间字符串转时间元组
- # WARNING:root:当前时间(字符串转时间元组): time.struct_time(tm_year=2022, tm_mon=10, tm_mday=1, tm_hour=23, tm_min=59, tm_sec=59, tm_wday=5, tm_yday=274, tm_isdst=-1)
- format1 = "%Y%m%d%H%M%S"
- logging.warning(f'当前时间(字符串转时间元组): {time.strptime("20221001235959", format1)}')
<2>、时间元组转时间字符串(有2种转换方法)
- # WARNING:root:当前时间(时间元组转时间字符串): 2022-11-27 11:09:43
- format2 = "%Y-%m-%d %H:%M:%S"
- logging.warning(f'当前时间(时间元组转时间字符串): {time.strftime(format2, time.localtime())}')
-
- # WARNING:root:当前时间(时间元组转时间字符串): Sun Nov 27 11:09:43 2022
- logging.warning(f'当前时间(时间元组转时间字符串): {time.asctime(time.localtime())}')
<3>、时间元组转时间戳(会截掉小数点后的数值)
- # WARNING:root:当前时间(时间元组转时间戳): 1669518803.0
- logging.warning(f'当前时间(时间元组转时间戳): {time.mktime(time.localtime())}')
- # WARNING:root:当前时间(时间戳): 1669518803.9799008
- logging.warning(f'当前时间(时间戳): {time.time()}')
<4>、时间戳转时间元组
- # WARNING:root:当前时间(时间戳转时间元组): time.struct_time(tm_year=2022, tm_mon=11, tm_mday=27, tm_hour=11, tm_min=17, tm_sec=55, tm_wday=6, tm_yday=331, tm_isdst=0)
- logging.warning(f'当前时间(时间戳转时间元组): {time.localtime(time.time())}')
- # OSError: [Errno 22] Invalid argument
- logging.warning(f'当前时间(时间戳转时间元组): {time.localtime(time.time_ns())}')
有了上面四种转换之后,时间字符串转时间戳,或时间戳转时间字符串就简单了。
涉及时区的相关属性有:
- # -32400
- logging.warning(f'其他API(时区偏移秒数,考虑夏令时): {time.altzone}')
- # -28800
- logging.warning(f'其他API(时区偏移秒数,未考虑夏令时): {time.timezone}')
- # ('中国标准时间', '中国夏令时')
- logging.warning(f'其他API(当前标准时间&地区夏令时): {time.tzname}')
休眠和耗时统计的方法有:
- start = time.perf_counter_ns()
- time.sleep(10)
- end = time.perf_counter_ns()
- # WARNING:root:其他API(休眠10s,耗时统计): 10.0022121
- logging.warning(f'其他API(休眠10s,耗时统计): {end - start}')
与进程和线程时间计数相关的方法有(计时不包括休眠的时间):
- # 0.265625,float类型
- logging.warning(f'其他API(进程): {time.process_time()}')
- # 265625000,int类型
- logging.warning(f'其他API(进程): {time.process_time_ns()}')
- # 0.265625,float类型
- logging.warning(f'其他API(线程): {time.thread_time()}')
- # 265625000,int类型
- logging.warning(f'其他API(线程): {time.thread_time_ns()}')
该模块下又包含了日期/时间/日期时间/时间间隔/时区及时区信息等子模块。date/time/datetime 等模块更精细化分了时间;timedelta 模块提供了许多关于日期时间的计算操作;timezone/tzinfo 等模块是与时区相关,通常用到哪个模块才去导入该子模块。
datetime.date 模块提供了对日期(年月日)的 API。
构造和获取日期、星期几的方法有:
- # 2022-11-27
- print(f'日期API(构造日期): {date(2022, month=11, day=27)}')
- today = date.today()
- print(f'日期API(年-月-日): {today}')
- print(f'日期API(年-月-日,格式化): {date.isoformat(today)}')
- # Return a 3-tuple containing ISO year, week number, and weekday
- # 日期API(年月日,格式化): (2022, 47, 7)
- print(f'日期API(年月日,格式化): {date.isocalendar(today)}')
- # 星期天
- print(f'日期API(星期几): {today.weekday()+1}')
- print(f'日期API(星期几): {today.isoweekday()}')
日期格式的转换方法有:
- today = date.today()
- # Mon Nov 28 00:00:00 2022
- print(f'日期API(转日期字符串,默认格式): {date.ctime(today)}')
- # 2022/11/28
- print(f'日期API(转日期字符串,指定格式): {date.strftime(today, "%Y/%m/%d")}')
- # time.struct_time(tm_year=2022, tm_mon=11, tm_mday=28, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=0, tm_yday=332, tm_isdst=-1)
- print(f'日期API(转元组): {date.timetuple(today)}')
- # 738487
- print(f'日期API(转数字序列): {date.toordinal(today)}')
- print(f'日期API(数字序列转日期): {date.fromordinal(738487)}')
- # 2022-11-28,time为time模块的导入,而非datetime子模块time
- print(f'日期API(时间戳转日期): {date.fromtimestamp(time.time())}')
日期的属性:
- today = date.today()
- # 2022-11-28
- print(f'日期API(年-月-日): {today.year}-{today.month}-{today.day}')
- # 1 day, 0:00:00
- print(f'日期API(当前日期最小单位): {today.resolution}')
- # 9999-12-31
- print(f'日期API(当前日期最大值): {today.max}')
- # 0001-01-01
- print(f'日期API(当前日期最小值): {today.min}')
datetime.time 模块提供了对时间(年时分秒)的 API,如下:
- current_time = time(11, 59, 59)
- print(f'时间API(当前时间):{time.isoformat(current_time)}')
- print(f'时间API(当前时间):{time.fromisoformat(str(current_time))}')
- print(f'时间API(当前时间):{time.strftime(current_time,"%H/%M/%S")}')
- # 0:00:00.000001
- print(f'当前时间最小单位:{current_time.resolution}')
- # 【00:00:00,23:59:59.999999】
- print(f'当前时间区间为:【{current_time.min},{current_time.max}】')
- # 11/59/59
- print(f'当前时间为:{current_time.hour}/{current_time.minute}/{current_time.second}')
datetime.datetime模块提供了对时间(年月日时分秒)的API。
时间日期和星期的 API(本地时间):
- # 构造日期时间
- current_datetime = datetime(year=2022, month=11, day=28, hour=11, minute=59, second=59)
- # 2022-11-28 08:13:40.093939
- print(f'今天:{datetime.today()}')
- print(f'当前日期时间:{datetime.now()}')
- # 2022-11-28T08:13:40.093938
- print(f'当前时间(标准时间,带T):{datetime.isoformat(datetime.now())}')
- # 2022-11-28
- print(f'当前日期:{datetime.date(datetime.now())}')
- # 08:13:40.093938
- print(f'当前时间:{datetime.time(datetime.now())}')
- # 1
- print(f'当前日期时间的星期:{datetime.isoweekday(datetime.now())}')
- print(f'当前日期时间的星期:{datetime.weekday(datetime.now())+1}')
使用 UTC 时间,如下:
- """UTC时间"""
- printg(f'当前日期时间(utc):{datetime.utcnow()}')
- print(f'当前日期时间元组(utc):{datetime.utctimetuple(datetime.utcnow())}')
- print(f'当前时间戳(utc):{datetime.utcfromtimestamp(current_timestamp)}')
日期时间格式的转换,如下:
- now = datetime.now()
- # 20221128200116
- print(f'日期时间对象(转字符串):{datetime.strftime(now, "%Y%m%d%H%M%S")}')
- # 2022-11-28 20:01:16
- print(f'当前日期时间字符串(转日期时间对象):{datetime.strptime(str(now).split(".")[0], "%Y-%m-%d %H:%M:%S")}')
- # 1669636876.113206
- print(f'日期时间对象(转时间戳):{datetime.timestamp(now)}')
- # 2022-11-28 20:01:16.113206
- print(f'时间戳(转日期时间对象):{datetime.fromtimestamp(datetime.timestamp(now))}')
- # time.struct_time(tm_year=2022, tm_mon=11, tm_mday=28, tm_hour=20, tm_min=0, tm_sec=32, tm_wday=0, tm_yday=332, tm_isdst=-1)
- print(f'日期时间对象(转时间元组):{datetime.timetuple(now)}')
- # (2022, 48, 1)
- print(f'日期时间对象(转时间元组):{datetime.isocalendar(datetime.now())}')
- # 2022-11-28 00:00:00
- print(f'当前日期:{datetime.fromisocalendar(year=2022, week=48, day=1)}')
- # 738487
- print(f'日期时间对象(转数字序列):{datetime.toordinal(now)}')
- # 2022-11-28 00:00:00
- print(f'数字序列(转日期时间对象):{datetime.fromordinal(datetime.toordinal(now))}')
该模块用来计算日期时间加减及两个日期时间的间隔数。
日期时间的加减,如下:
- """日期时间的加减"""
- ctime = datetime.now()
- print(f'当前日期时间减去1天:{ctime + timedelta(days=-1)}')
- print(f'当前日期时间减去1秒:{ctime + timedelta(seconds=-1)}')
- print(f'当前日期时间减去1微秒:{ctime + timedelta(microseconds=-1)}')
- print(f'当前日期时间减去1毫秒:{ctime + timedelta(milliseconds=-1)}')
- print(f'当前日期时间减去1分钟:{ctime + timedelta(minutes=-1)}')
- print(f'当前日期时间减去1小时:{ctime + timedelta(hours=-1)}')
- print(f'当前日期时间减去1周:{ctime + timedelta(weeks=-1)}')
- # 加法类似
- print(f'当前日期时间加上1天:{ctime + timedelta(days=1)}')
两个日期时间的间隔数计算,如下:
- """日期时间的间隔计算"""
- ctime = datetime.now()
- befor_time = ctime + timedelta(days=-1, hours=5, seconds=30)
- print(f'两个时间的间隔天数:{(ctime - befor_time).days}')
- print(f'两个时间的间隔分钟数:{(ctime - befor_time).min}')
- print(f'两个时间的间隔秒数:{(ctime - befor_time).seconds}')
- print(f'两个时间的间隔微秒数:{(ctime - befor_time).microseconds}')
- print(f'两个时间的间隔总秒数:{(ctime - befor_time).total_seconds()}')
该模块用来获取时区信息的,如下:
- # 构造时区: timezone(offset, name=None)
- china_tz = timezone(timedelta(hours=8), 'Asia/Shanghai')
- dt = datetime.now(china_tz)
- print(china_tz.tzname(dt)) # Asia/Shanghai
- print(china_tz.utcoffset(dt)) # 8:00:00
- print(china_tz.dst(dt)) # None
- print(china_tz.fromutc(dt)) # 2022-11-28 16:06:35.398835+08:00
- # 属性
- print(timezone.utc) # utc 时区
- print(timezone.min) # UTC-23:59
- print(timezone.max) # UTC+23:59
该模块可提供不同格式的日历打印,星期的设置和判断,闰年的判断等功能。
通过 calendar.Calendar() 方式创建日历对象,该对象实例可用来:判断星期几、获取当月的完整日期等。
比如,判断星期几:
- # 0表示周一,直到6(周日)
- cal = calendar.Calendar()
- for wd in cal.iterweekdays():
- print(f'今天是周:{wd + 1}')
比如,获取当月完整日期的迭代器:
- cal = calendar.Calendar()
- # 打印了 2022-10-31、2022-11-01、2022-11-02 ..... 2022-12-02、2022-12-03、2022-12-04
- for mds in cal.itermonthdates(2022, 11):
- print(f'{mds}')
也可以返回列表形式,如下:
- cal = calendar.Calendar()
- '''
- 以一周为列表
- [datetime.date(2022, 10, 31), datetime.date(2022, 11, 1), datetime.date(2022, 11, 2), datetime.date(2022, 11, 3), datetime.date(2022, 11, 4), datetime.date(2022, 11, 5), datetime.date(2022, 11, 6)]
- [datetime.date(2022, 11, 7), datetime.date(2022, 11, 8), datetime.date(2022, 11, 9), datetime.date(2022, 11, 10), datetime.date(2022, 11, 11), datetime.date(2022, 11, 12), datetime.date(2022, 11, 13)]
- [datetime.date(2022, 11, 14), datetime.date(2022, 11, 15), datetime.date(2022, 11, 16), datetime.date(2022, 11, 17), datetime.date(2022, 11, 18), datetime.date(2022, 11, 19), datetime.date(2022, 11, 20)]
- [datetime.date(2022, 11, 21), datetime.date(2022, 11, 22), datetime.date(2022, 11, 23), datetime.date(2022, 11, 24), datetime.date(2022, 11, 25), datetime.date(2022, 11, 26), datetime.date(2022, 11, 27)]
- [datetime.date(2022, 11, 28), datetime.date(2022, 11, 29), datetime.date(2022, 11, 30), datetime.date(2022, 12, 1), datetime.date(2022, 12, 2), datetime.date(2022, 12, 3), datetime.date(2022, 12, 4)]
- '''
- for mdc in cal.monthdatescalendar(2022, 11):
- print(f'{mdc}')
当然,也可以使用如下 API 获取当月的日期(需要遍历迭代器获取):
还有也可以返回列表格式,年和月的都有对应的 API:
- cal = calendar.Calendar()
- cal = calendar.Calendar()
- for yd in cal.yeardatescalendar(2022):
- print(f'{yd}')
- for ydc in cal.yeardayscalendar(2022):
- print(f'{ydc}')
- for ydc2 in cal.yeardays2calendar(2022):
- print(f'{ydc2}')
<1>、生成纯文本日历
需要先通过 calendar.TextCalendar() 构造纯文本日历对象,以下 API 可实现日历打印:
<2>、生成HTML日历
需要先通过 calendar.HTMLCalendar() 构造 HTML 日历对象,以下 API 可实现日历打印:
<3>、其他方式
使用 calendar 模块直接提供的 API,本质上也是纯文本日历,如下所示:
- # 打印年份日历
- print(calendar.calendar(2022))
- calendar.prcal(2022)
- # 打印月份日历
- print(calendar.month(2022, 11))
- calendar.prmonth(2022, 11)
以 2022 年的年历为例,打印内容如下:
- 2022
-
- 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 1 2 3 4 5 6 1 2 3 4 5 6
- 3 4 5 6 7 8 9 7 8 9 10 11 12 13 7 8 9 10 11 12 13
- 10 11 12 13 14 15 16 14 15 16 17 18 19 20 14 15 16 17 18 19 20
- 17 18 19 20 21 22 23 21 22 23 24 25 26 27 21 22 23 24 25 26 27
- 24 25 26 27 28 29 30 28 28 29 30 31
- 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 1 1 2 3 4 5
- 4 5 6 7 8 9 10 2 3 4 5 6 7 8 6 7 8 9 10 11 12
- 11 12 13 14 15 16 17 9 10 11 12 13 14 15 13 14 15 16 17 18 19
- 18 19 20 21 22 23 24 16 17 18 19 20 21 22 20 21 22 23 24 25 26
- 25 26 27 28 29 30 23 24 25 26 27 28 29 27 28 29 30
- 30 31
-
- 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 1 2 3 4 5 6 7 1 2 3 4
- 4 5 6 7 8 9 10 8 9 10 11 12 13 14 5 6 7 8 9 10 11
- 11 12 13 14 15 16 17 15 16 17 18 19 20 21 12 13 14 15 16 17 18
- 18 19 20 21 22 23 24 22 23 24 25 26 27 28 19 20 21 22 23 24 25
- 25 26 27 28 29 30 31 29 30 31 26 27 28 29 30
-
- 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 1 2 3 4 5 6 1 2 3 4
- 3 4 5 6 7 8 9 7 8 9 10 11 12 13 5 6 7 8 9 10 11
- 10 11 12 13 14 15 16 14 15 16 17 18 19 20 12 13 14 15 16 17 18
- 17 18 19 20 21 22 23 21 22 23 24 25 26 27 19 20 21 22 23 24 25
- 24 25 26 27 28 29 30 28 29 30 26 27 28 29 30 31
- 31
对于简单的日历文本,可以设置星期和判断星期几:
- # 星期,默认0为星期一,直到6(星期天)
- print(f'今天是星期:{calendar.weekday(2022, 11, 28)}') # 0
- print(f'今天是星期:{calendar.firstweekday()}') # 0
- calendar.setfirstweekday(calendar.TUESDAY) # 设置每周开始(周二)
- print(f'今天是星期:{calendar.firstweekday()}') # 1
可以判断闰年:
- # 闰年
- print(f'2022年是闰年:{calendar.isleap(2022)}') # False
- print(f'2010~2022之间是闰年的个数:{calendar.leapdays(2010, 2022)}') # 3
也可以判断具体月份有多少天:
print(f'11月有多少天:{calendar.monthrange(2022, 10)[1]}') # 30
至此,关于日期时间的基础知识和模块API等内容都梳理完毕。实际项目中,都会有自己实现和管理的时间工具类,而这些工具类的基础也恰恰是这些东西,因此也比较重要。
【人生苦短,学习Python!Python模块系列将持续更新和记录......】