Python 常用日期处理 -- calendar 与 dateutil 模块

本文紧承上一篇 Python 常用日期处理,因制于篇幅的大小需求才临时分立新篇,这里要简单提到 calendar 和 dateutil 模块的使用,其中 calendar 是 Python 内置的。相比于上一篇而言,此处主旨会更明确一些,只记录三个应用案例,分别是

  1. 用 dateutil 灵活的解析 datetime 字符串
  2. 给定起始日期后的连续日期
  3. 给定起始日期后连续的月末日期

dateutil 灵活的解析 datetime 字符串

使用 Python 内容的  date 或 datetime, 构造它们的实例时需要逐个的传入年月日或时分秒,或者要调用  fromisoformat() 方法解析严格的字符串表示格式。而 dateutil.parser 的 parse() 方法就显得特别的聪明和随意,它可以智能的解析更丰富的字符串表示方式。详细的支持格式请参考官方文档的 parse examples,恐怕官方文档也未列举完全,只要觉得合理的时间字符串就可以尝试去解析。下方是一些例子

 1>>> from dateutil.parser import parse<br/>
 2>>>
 3>>> parse('2018-02-28')
 4datetime.datetime(2018, 2, 28, 0, 0)
 5>>> parse('2018-02-28T12:08:23')
 6datetime.datetime(2018, 2, 28, 12, 8, 23)
 7>>> parse('2018-02-28T12:08:23PM')  # 下午
 8datetime.datetime(2018, 2, 28, 12, 8, 23)
 9>>> parse('2018-02-28T12:08:23+05:00') # 加上时区偏移
10datetime.datetime(2018, 2, 28, 12, 8, 23, tzinfo=tzoffset(None, 18000))
11>>> parse('Jan 18, 2018')
12datetime.datetime(2018, 1, 18, 0, 0)
13>>> parse('Oct. 10, 2008 10:43am CST')  # 加上时区
14datetime.datetime(2008, 10, 10, 10, 43, tzinfo=tzlocal())
15>>> parse('Wed Jul 08 17:08:48 GMT 2009')
16datetime.datetime(2009, 7, 8, 17, 8, 48, tzinfo=tzutc())
17>>> parse("09-25-2003")
18datetime.datetime(2003, 9, 25, 0, 0)
19>>> parse("13-02-2003")   # 第一段大于 12 不可能是月份,所以推断为日期
20datetime.datetime(2003, 2, 13, 0, 0)
21>>>parse('09:23pm')
22datetime.datetime(2019, 4, 30, 21, 23)

parser.parse() 返回的总是 datetime 类型,这也很好理解,因为 datetime 有完整的时间信息,可由此得到 date 或 time 实例。

给定起始日期后的连续日期

从一个给定日期一天天往后推可以直接用 Python 内置的 datetime(date 和 timedelta),还用不上 calendar 模块

 1>>> from datetime import date, timedelta
 2>>> start_date = date(2018, 2, 25)
 3>>> for i in range(1, 10):
 4...     print(start_date + timedelta(days = i))
 5...
 62018-02-26
 72018-02-27
 82018-02-28
 92018-03-01
102018-03-02
112018-03-03
122018-03-04
132018-03-05
142018-03-06

使用 timedelta 来计算日期偏移只能支持以下的度量

timedelta(days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0)

不能按月,年来换算,而 dateutil.relativedelta 就更强大了,看下它的构造函数

relativedelta(dt1=None, dt2=None, years=0, months=0, days=0, leapdays=0, weeks=0, hours=0, minutes=0, seconds=0, microseconds=0, year=None, month=None, day=None, weekday=None, yearday=None, nlyearday=None, hour=None, minute=None, second=None, microsecond=None)

我们可以换用  relativedelta 来重写上面的代码

 1>>> from datetime import date
 2>>> from dateutil.relativedelta import relativedelta
 3>>> start_date = date(2018, 2, 25)
 4>>> for i in range(1, 10):
 5...     print(start_date + relativedelta(days = i))
 6...
 72018-02-26
 82018-02-27
 92018-02-28
102018-03-01
112018-03-02
122018-03-03
132018-03-04
142018-03-05
152018-03-06

如果想要推算下一个,下一个月,或下下年就需要用 relativedelta

给定起始日期后连续的月末日期

假如用 relativedelta 按月推算日期的话就要涉及到 calendar 模块了,因为无论 30 天往下推算或是 relativedelta(months = 1) 得到的都可能不是自己想要的结果。例如

  • 2019-01-30 + relativedelta(months = 1) 是 2019-02-28
  • 2019-02-28 + relativedelta(months = 1) 是 2019-03-28

日期部分飘忽不定,这种按月推演出的结果没多大意思,一般来说我们可能需要的是每个月月末日期,可以使用 calendar 来确定指定年月的最小和最大日期。

 1>>> from datetime import date
 2>>> from dateutil.relativedelta import relativedelta
 3>>> from calendar import monthrange
 4>>>
 5>>> monthend = date(2019, 1, 31)
 6>>> for i in range(1, 10):
 7...     dd = monthend + relativedelta(months = i)
 8...     dd = dd.replace(day = monthrange(dd.year, dd.month)[1])
 9...     print(dd)
10...
112019-02-28
122019-03-31
132019-04-30
142019-05-31
152019-06-30
162019-07-31
172019-08-31
182019-09-30
192019-10-31

Python 自带的  calendar 模块还有许多好玩的函数,如对星期,月份的遍历,真正的日历输出为文本或 HTML 代码的功能,详情请见 Python CALENDAR Tutorial with Example.

来份简单的例子

 1>>> import calendar
 2>>> c = calendar.TextCalendar(calendar.SUNDAY)
 3>>> str = c.formatmonth(2019, 4)
 4>>> print(str)
 5     April 2019
 6Su Mo Tu We Th Fr Sa
 7    1  2  3  4  5  6
 8 7  8  9 10 11 12 13
 914 15 16 17 18 19 20
1021 22 23 24 25 26 27
1128 29 30

显示出与 bash 命令 cal -h 4 2019 一样的内容。

永久链接 https://yanbin.blog/python-datetime-calendar-dateutil/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。