- 引言
- 本书涉及的内容
- 第 1 部分 Python 开发入门
- 第 1 章 Python 入门
- 第 2 章 开发 Web 应用
- 第 3 章 Python 项目的结构与包的创建
- 第 4 章 面向团队开发的工具
- 第 5 章 项目管理与审查
- 第 6 章 用 Mercurial 管理源码
- 第 7 章 完备文档的基础
- 第 8 章 模块分割设计与单元测试
- 第 9 章 Python 封装及其运用
- 第 10 章 用 Jenkins 持续集成
- 第 11 章 环境搭建与部署的自动化
- 第 12 章 应用的性能改善
- 第 13 章 让测试为我们服务
- 第 14 章 轻松使用 Django
- 第 15 章 方便好用的 Python 模块
- 附录 A VirtualBox 的设置
- 附录 B OS(Ubuntu)的设置
15.1 轻松计算日期
日期是大部分系统都要用到的,但是它的计算比较复杂,因此很容易出现 Bug。开发高品质软件时要尽量避免复杂的操作。使用 dateutil 模块可以让我们用简单的描述来完成复杂的日期计算。
dateutil
http://labix.org/python-dateutil
15.1.1 日期计算的复杂性
首先我们了解一下日期计算中容易出 Bug 的地方。
◉ “1 个月后”是哪一天
遇到“1 月 1 日的 1 个月后是哪一天”的问题时,大部分人都会回答“2 月 1 日”。那么,换成“1 月 31 日的 1 个月后是哪一天”,答案又是怎样呢?恐怕我们会得到“2 月 28 日或 29 日”“3 月 3 日”“2 月 31 日”等多种回答。在开发系统的过程中,如果用了“1 个月后”这种模糊不清的表述,开发者之间很可能产生认识上的分歧,最终开发结果就会出现 Bug。在这个例子中,“30 天之后”“下个月的最后一天”等表述都比“1 个月后”清楚得多,它们能准确指定一个日期,极大地避免歧义。
◉ 求下个月的最后一天
首先我们用 Python 标准模块 datetime 的 timedelta 来编写一个计算“下个月最后一天”的程序(LIST 15.1)。
LIST 15.1 datetime_dateutil_1.py
# coding: utf-8 from datetime import datetime, timedelta def main(): # 下个月最后一天= 下下个月的前一天 now_time = datetime.now() # 获取下下个月第一天的日期对象 first_day_of_after_two_month = datetime(now_time.year, now_time.month + 2, 1) # 获取下个月最后一天的日期对象 last_day_of_next_month = \ first_day_of_after_two_month - timedelta(days=1) # 输出结果 print last_day_of_next_month.date() if __name__ == '__main__': main()
执行上述代码会得到如 LIST 15.2 所示的结果。
LIST 15.2 执行结果
$ python datetime_dateutil_1.py 2012-02-29
这里我们通过“下下个月第一天的前一天”算出了“下个月的最后一天”。虽然这乍看上去没有什么问题,但实际上,在求“下下个月的第一天”时,我们忘记考虑年份的更迭了。now_time.month 可以取 1 ~ 12 的值,而这个值加上 2 有可能达到 13、14。因此,这个程序在遇到 11 月和 12 月时会发生例外。我们想要的运行情况是 now_time.month 为 11 和 12 时增算一年。问题修正后的代码如 LIST 15.3 所示。
LIST 15.3 datetime_dateutil_2.py
# coding: utf-8 from datetime import datetime, timedelta def main(): # 下个月最后一天= 下下个月的前一天 now_time = datetime.now() # 获取下下个月第一天的日期对象(考虑跨年问题) if now_time.month in [11, 12]: first_day_of_after_two_month = datetime(now_time.year + 1, now_time.month + 2 - 12, 1) else: first_day_of_after_two_month = datetime(now_time.year, now_time.month + 2, 1) # 输出下个月最后一天 last_day_of_next_month = \ first_day_of_after_two_month - timedelta(days=1) # 输出结果 print last_day_of_next_month.date() if __name__ == '__main__': main()
可见,日期计算常常会遇到复杂的边界问题,很容易出现 Bug。下面我们用 dateutil 模块来简化这段代码。
15.1.2 导入 dateutil
用 pip 命令安装 dateutil 模块,代码如 LIST 15.4 所示。2014 年 12 月初的 dateutil 最新版本为 2.3。
LIST 15.4 用 pip 命令安装 dateutil 模块
$ pip install python-dateutil==2.3
使用 dateutil 模块的 relativedelta 函数时,我们可以使用如 LIST 15.5 所示的代码获取前面提到的“下个月最后一天”。
LIST 15.5 datetime_dateutil_3.py
# coding: utf-8 from dateutil.relativedelta import relativedelta from datetime import datetime, timedelta def main(): # 下个月最后一天= 下下个月的前一天 now_time = datetime.now() # 输出下个月最后一天 last_day_of_next_month = \ now_time + relativedelta(months=2, day=1, days=-1) # 输出结果 print last_day_of_next_month.date() if __name__ == '__main__': main()
relativedelta 与 timedelta 一样,可以对 datetime 对象进行加减运算。关键字传值参数方面有 day、second、hour 等单数型和 days、seconds、hours 等复数型可供选择。单数型的传值参数为指定数值,复数型的传值参数为指定增减幅度。
◉ rrule
接下来要介绍的是 rrule,它可以获取符合指定规则的日期对象。比如 LIST 15.6,这段代码的作用是获取 2012 年 1 月 1 日到 2012 年 2 月 1 日的周一和周三的日期对象并输出。
LIST 15.6 dateutil_rrule.py
# coding: utf-8 from datetime import datetime from dateutil.rrule import rrule, DAILY, MO, WE def main(): # 生成rrule 对象 rrule_obj = rrule(DAILY, # 每天 byweekday=(MO, WE), # 周一、周三 dtstart=datetime(2012, 1, 1), # 2012 年1 月1 日起 until=datetime(2012, 2, 1)) # 2012 年2 月1 日止 # 逐个取出符合条件的日期对象并显示在屏幕上 for dt in rrule_obj: print dt if __name__ == '__main__': main()
rrule 函数的第一个传值参数为获取间隔,可以指定 YEARLY(每年)、MONTHLY(每月)、WEEKLY(每周)、DAILY(每天)、HOURLY(每小时)、MINUTELY(每分钟)、SECONDLY(每秒)。其他条件通过选项指定。以 LIST 15.6 中的代码为例,byweekday 指定了星期几,dtstart 指定了开始日期,until 指定了终止日期。实际执行结果如 LIST 15.7 所示。
LIST 15.7 执行结果
$ python dateutil_rrule.py 2012-01-02 00:00:00 2012-01-04 00:00:00 2012-01-09 00:00:00 2012-01-11 00:00:00 2012-01-16 00:00:00 2012-01-18 00:00:00 2012-01-23 00:00:00 2012-01-25 00:00:00 2012-01-30 00:00:00 2012-02-01 00:00:00
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论