返回介绍

15.1 轻松计算日期

发布于 2024-01-21 17:11:03 字数 4371 浏览 0 评论 0 收藏 0

日期是大部分系统都要用到的,但是它的计算比较复杂,因此很容易出现 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文