使用 Python datetime 处理日期和时间

举报
Yuchuan 发表于 2021/12/20 21:38:07 2021/12/20
【摘要】 处理日期和时间是编程中最大的挑战之一。在处理时区、夏令时和不同的书面日期格式之间,很难跟踪您所引用的日期和时间。幸运的是,内置的 Pythondatetime模块可以帮助您管理日期和时间的复杂性质。

目录

处理日期和时间是编程中最大的挑战之一。在处理时区、夏令时和不同的书面日期格式之间,很难跟踪您所引用的日期和时间。幸运的是,内置的 Pythondatetime模块可以帮助您管理日期和时间的复杂性质。

在本教程中,您将学习:

  • 为什么用日期和时间编程是一个挑战
  • Pythondatetime模块中有哪些函数可用
  • 如何以特定格式打印或读取日期和时间
  • 如何对日期和时间进行算术运算

另外,您将开发一个简洁的应用程序来倒计时到下一个 PyCon US 的剩余时间!

让我们开始吧!

用日期和时间编程

如果您曾经开发过需要跟踪多个地理区域的时间的软件,那么您可能会明白为什么随着时间的推移编程会如此痛苦。根本的脱节是计算机程序更喜欢完全有序和有规律的事件,但大多数人使用和指代时间的方式是非常不规则的。

注意:如果您想更多地了解为什么时间处理起来如此复杂,那么网络上有许多很棒的资源。以下是一些不错的起点:

这种不规则性的一个很好的例子是夏令时。在美国和加拿大,时钟在 3 月的第二个星期日快进一小时,在 11 月的第一个星期日快退一小时。然而,这只是自 2007 年以来的情况。在 2007 年之前,时钟在 4 月的第一个星期日提前,在 10 月的最后一个星期日后退。

当您考虑时区时,事情变得更加复杂。理想情况下,时区边界将完全遵循经度线。但是,由于历史和政治原因,时区线很少是直的。通常,相隔很远的地区发现自己处于同一时区,而相邻地区处于不同的时区。有一些时区的形状非常时髦

计算机如何计算时间

几乎所有的计算机都从一个叫做Unix 时代的时刻开始计算时间。这发生在 1970 年 1 月 1 日的 00:00:00 UTC。UTC 代表Coordinated Universal Time,是指经度为 0°的时间。UTC 通常也称为格林威治标准时间或 GMT。UTC 未针对夏令时进行调整,因此它每天始终保持二十四小时。

根据定义,Unix 时间以与 UTC 相同的速率流逝,因此 UTC 中的一秒步长对应于 Unix 时间中的一秒步长。您通常可以通过计算自 Unix 纪元以来的秒数来计算自 1970 年 1 月 1 日以来任何给定时刻的 UTC 日期和时间,闰秒除外。闰秒偶尔会添加到 UTC 以说明地球自转速度变慢,但不会添加到 Unix 时间。

注意:有一个与 Unix 时间相关的有趣错误。由于许多较旧的操作系统是 32 位的,因此它们将 Unix 时间存储在 32 位有符号整数中。

这意味着在 2038 年 1 月 19 日 03:14:07,整数将溢出,从而导致所谓的2038 年问题或 Y2038。与Y2K 问题类似,Y2038 需要更正以避免对关键系统造成灾难性后果。

几乎所有的编程语言,包括Python,都包含了 Unix 时间的概念。Python 的标准库包含一个名为的模块time,可以打印自 Unix 纪元以来的秒数:

>>>
>>> import time
>>> time.time()
1579718137.550164

在本例中,导入所述time模块和执行time()打印的Unix时间,或因为时代的秒(不包括闰秒)号码。

除了 Unix 时间,计算机还需要一种向用户传达时间信息的方法。正如您在上一个示例中看到的那样,人类几乎不可能解析 Unix 时间。相反,Unix 时间通常转换为 UTC,然后可以使用time zone offsets将其转换为本地时间。

互联网编号分配机构(IANA)维护一个数据库的所有时区偏移值的。IANA 还发布定期更新,其中包括时区偏移的任何更改。此数据库通常包含在您的操作系统中,但某些应用程序可能包含更新的副本。

该数据库包含所有指定时区的副本以及它们与 UTC 偏移的小时数和分钟数。因此,在冬季,当夏令时无效时,美国东部时区的偏移量为 -05:00,即与 UTC 的负五小时。其他地区有不同的偏移量,可能不是整数小时。例如,尼泊尔的 UTC 偏移量是 +05:45,即从 UTC 开始的正五小时四十五分钟。

如何报告标准日期

Unix 时间是计算机计算时间的方式,但人类通过计算任意日期的秒数来确定时间的效率非常低。相反,我们以年、月、日等为单位工作。但即使有了这些约定,另一层复杂性源于不同的语言和文化有不同的日期书写方式。

例如,在美国,日期通常从月份开始,然后是日期,然后是年份。这意味着 2020 年 1 月 31 日被写为01-31-2020。这与日期的长格式书面版本非常匹配。

但是,欧洲大部分地区和许多其他地区的日期都是从日开始,然后是月,然后是年。这意味着 2020 年 1 月 31 日写为31-01-2020。在跨文化交流时,这些差异会导致各种混乱。

为帮助避免沟通错误,国际标准化组织 (ISO) 制定了ISO 8601。该标准规定所有日期都应按从最重要到最不重要的数据的顺序写入。这意味着格式为年、月、日、小时、分钟和秒:

YYYY-MM-DD HH:MM:SS

在这个例子中,YYYY代表一个四位数年份,MMDD有两位数的月和日,从零如果需要的话。之后,HHMM, 和SS代表两位数的小时、分钟和秒,必要时从零开始。

这种格式的优点是可以毫无歧义地表示日期。如果日期是有效的月数,则写为DD-MM-YYYYMM-DD-YYYY可能被误解的日期。稍后您将看到如何在 Python 中使用 ISO 8601 格式datetime

时间应该如何存储在你的程序中

大多数使用 time 的开发人员都听说过将本地时间转换为 UTC 并存储该值以供以后参考的建议。在许多情况下,尤其是当您存储过去的日期时,这些信息足以进行任何必要的算术运算。

但是,如果您的程序的用户输入了他们本地时间的未来日期,则可能会出现问题。时区和夏令时规则变化相当频繁,正如您在 2007 年美国和加拿大夏令时更改时看到的那样。如果用户位置的时区规则在他们输入的未来日期之前发生变化,则 UTC 将无法提供足够的信息来转换回正确的本地时间。

注意:有许多优秀的资源可帮助您确定在应用程序中存储时间数据的适当方式。这里有几个地方可以开始:

在这种情况下,您需要存储本地时间,包括用户输入的时区,以及用户保存时间时有效的 IANA 时区数据库版本。这样,您将始终能够将本地时间转换为 UTC。但是,这种方法并不总是允许您将 UTC 转换为正确的本地时间。

使用 Pythondatetime模块

如您所见,在编程中处理日期和时间可能很复杂。幸运的是,如今您很少需要从头开始实现复杂的功能,因为有许多开源库可以提供帮助。这在 Python 中绝对是这样,它在标准库中包含三个独立的模块来处理日期和时间:

  1. calendar输出日历并使用理想化的公历提供功能。
  2. datetime 提供用于操作日期和时间的类。
  3. time 提供不需要日期的时间相关功能。

在本教程中,您将专注于使用 Pythondatetime模块。的主要重点datetime是降低访问与日期、时间和时区相关的对象属性的复杂性。由于这些对象非常有用,calendar还从datetime.

time功能不如datetime. 许多函数time返回一个特殊的struct_time实例。该对象具有用于访问存储数据的命名元组接口,使其类似于 的实例datetime。但是,它不支持 的所有功能datetime,尤其是使用时间值执行算术的能力。

datetime 提供了三个类,它们构成了大多数人会使用的高级接口:

  1. datetime.date是一个理想化的日期,假定公历无限延伸到未来和过去。这个对象存储yearmonth以及day为属性。
  2. datetime.time是一个理想化的时间,假设每天有 86,400 秒,没有闰秒。此对象存储的hourminutesecondmicrosecond,和tzinfo(时区信息)。
  3. datetime.datetime是 adate和 a的组合time。它具有两个类的所有属性。

创建 Pythondatetime实例

表示日期和时间的三个类datetime具有相似的初始值设定项。它们可以通过传递关键字参数对于每个属性,诸如被实例化yeardatehour。您可以尝试下面的代码来了解每个对象是如何创建的:

>>>
>>> from datetime import date, time, datetime
>>> date(year=2020, month=1, day=31)
datetime.date(2020, 1, 31)
>>> time(hour=13, minute=14, second=31)
datetime.time(13, 14, 31)
>>> datetime(year=2020, month=1, day=31, hour=13, minute=14, second=31)
datetime.datetime(2020, 1, 31, 13, 14, 31)

在此代码中,您从中导入三个主要类,datetime并通过将参数传递给构造函数来实例化它们中的每一个。您可以看到这段代码有些冗长,如果您没有整数形式的所需信息,则无法使用这些技术来创建datetime实例。

幸运的是,datetime提供了其他几种创建datetime实例的便捷方式。这些方法不需要您使用整数来指定每个属性,而是允许您使用其他一些信息:

  1. date.today()创建一个datetime.date具有当前本地日期的实例。
  2. datetime.now()创建一个datetime.datetime具有当前本地日期和时间的实例。
  3. datetime.combine()datetime.datedatetime.timedatetime.datetime实例合并为一个实例。

datetime当您事先不知道需要传递给基本初始化程序的信息时,这三种创建实例的方法会很有帮助。您可以尝试使用此代码来查看备用初始值设定项是如何工作的:

>>>
>>> from datetime import date, time, datetime
>>> today = date.today()
>>> today
datetime.date(2020, 1, 24)
>>> now = datetime.now()
>>> now
datetime.datetime(2020, 1, 24, 14, 4, 57, 10015)
>>> current_time = time(now.hour, now.minute, now.second)
>>> datetime.combine(today, current_time)
datetime.datetime(2020, 1, 24, 14, 4, 57)

在这段代码中,你使用date.today()datetime.now()以及datetime.combine()创建的实例datedatetimetime对象。每个实例都存储在不同的变量中

  1. today是一个date只有年、月和日的实例。
  2. nowdatetime具有年、月、日、小时、分钟、秒和微秒的实例。
  3. current_time是一个time实例,其小时、分钟和秒设置为与 相同的值now

在最后一行,您将日期信息today与时间信息组合current_time以生成一个新datetime实例。

警告: datetime还提供datetime.utcnow(),它返回datetime当前 UTC 的一个实例。但是,Python文档建议不要使用此方法,因为它在结果实例中不包含任何时区信息。

在实例之间进行算术或比较时,使用datetime.utcnow()可能会产生一些令人惊讶的结果datetime。在后面的部分中,您将看到如何将时区信息分配给datetime实例。

使用字符串创建 Pythondatetime实例

创建date实例的另一种方法是使用.fromisoformat(). 要使用此方法,您提供一个字符串与您了解了在ISO 8601格式的日期更早。例如,您可以提供一个指定了年、月和日期的字符串:

2020-01-31

根据 ISO 8601 格式,此字符串表示 2020 年 1 月 31 日的日期。您可以date使用以下示例创建实例:

>>>
>>> from datetime import date
>>> date.fromisoformat("2020-01-31")
datetime.date(2020, 1, 31)

在此代码中,您用于date.fromisoformat()创建date2020 年 1 月 31 日的实例。此方法非常有用,因为它基于 ISO 8601 标准。但是,如果您有一个表示日期和时间但不是 ISO 8601 格式的字符串怎么办?

幸运的是,Pythondatetime提供了一个被调用的方法.strptime()来处理这种情况。该方法使用一种特殊的迷你语言来告诉 Python 字符串的哪些部分与datetime属性相关联。

要使用 构造一个datetime字符串.strptime(),您必须使用来自迷你语言的格式化代码告诉 Python 字符串的每个部分代表什么。你可以试试这个例子,看看它是如何.strptime()工作的:

>>>
 1>>> date_string = "01-31-2020 14:45:37"
 2>>> format_string = "%m-%d-%Y %H:%M:%S"

第 1 行,您创建date_string,它表示 2020 年 1 月 31 日下午 2:45:37 的日期和时间。在第 2 行,您创建format_string,它使用迷你语言指定如何将 的部分date_string转换为datetime属性。

在 中format_string,您包含几个格式代码以及所有的破折号 ( -)、冒号 ( :) 和空格,与它们在date_string. 要处理 中的日期和时间date_string,请包含以下格式代码:

Component 代码 价值
年(作为四位整数) %Y 2020
月份(以零填充的十进制数) %m 01
日期(以零填充的十进制数) %d 31
小时(作为带 24 小时制的零填充十进制) %H 14
分钟(作为补零的十进制数) %M 45
Second(作为零填充的十进制) %S 37

迷你语言中所有选项的完整列表超出了本教程的范围,但您可以在网络上找到一些很好的参考资料,包括 Python 的文档和名为strftime.org的网站。

现在已经定义了date_stringformat_string,您可以使用它们来创建datetime实例。以下是.strptime()工作原理的示例:

>>>
 3>>> from datetime import datetime
 4>>> datetime.strptime(date_string, format_string)
 5datetime.datetime(2020, 1, 31, 14, 45, 37)

在此代码中,您datetime第 3 行导入并在第 4 行使用datetime.strptime()withdate_string和。最后,第 5 行显示了创建的实例中的属性值。您可以看到它们与上表中显示的值匹配。format_stringdatetime.strptime()

注意:有更高级的方法来创建datetime实例,但它们涉及使用必须安装的第三方库。一个特别简洁的库称为dateparser,它允许您提供自然语言字符串输入。输入甚至支持多种语言:

>>>
 1>>> import dateparser
 2>>> dateparser.parse("yesterday")
 3datetime.datetime(2020, 3, 13, 14, 39, 1, 350918)
 4>>> dateparser.parse("morgen")
 5datetime.datetime(2020, 3, 15, 14, 39, 7, 314754)

在此代码中,您用于通过传递两个不同的时间字符串表示dateparser来创建两个datetime实例。在第 1 行,您导入dateparser. 然后,在第 2 行,您使用.parse()参数"yesterday"来创建datetime过去二十四小时的实例。在撰写本文时,这是 2020 年 3 月 13 日下午 2:39。

第 3 行,您使用.parse()了参数"morgen"Morgen是明天的德语单词,因此在未来 24 小时内dateparser创建一个datetime实例。在撰写本文时,这是 3 月 15 日下午 2:39。

开始你的 PyCon 倒计时

现在您有足够的信息开始为明年的PyCon US 制作倒计时时钟!PyCon US 2021 将于 2021 年 5 月 12 日在宾夕法尼亚州匹兹堡举行。随着 2020 年的活动被取消,许多 Pythonistas 对明年的聚会格外兴奋。这是跟踪您需要等待多长时间并同时提高datetime技能的好方法!

首先,创建一个名为的文件pyconcd.py并添加以下代码:

# pyconcd.py

from datetime import datetime

PYCON_DATE = datetime(year=2021, month=5, day=12, hour=8)
countdown = PYCON_DATE - datetime.now()
print(f"Countdown to PyCon US 2021: {countdown}")

在此代码,您导入datetimedatetime和定义常量,PYCON_DATE即存储下一个PYCON美国的日期。您不希望 PyCon 的日期发生变化,因此您将变量全部大写以表明它是一个常量。

接下来,您计算 与 之间的差异datetime.now(),即当前时间PYCON_DATE。取两个datetime实例之间的差异返回一个datetime.timedelta实例。

timedelta实例表示两个datetime实例之间的时间变化。名称中的delta是对希腊字母 delta 的引用,它在科学和工程中用于表示变化。稍后您将了解有关如何使用timedelta更一般的算术运算的更多信息。

最后,截至 2020 年 4 月 9 日晚上 9:30 之前的打印输出为:

Countdown to PyCon US 2021: 397 days, 10:35:32.139350

距离 PyCon US 2021 仅剩 397 天!此输出有点笨拙,因此稍后您将看到如何改进格式。如果您在不同的日子运行此脚本,您将获得不同的输出。如果您在 2021 年 5 月 12 日上午 8 点之后运行脚本,您将获得负的剩余时间!

使用时区

正如您之前看到的,存储日期发生的时区是确保您的代码正确的一个重要方面。Pythondatetime提供了tzinfo,它是一个抽象基类,允许datetime.datetimedatetime.time包含时区信息,包括夏令时的概念。

但是,datetime不提供与 IANA 时区数据库交互的直接方式。Pythondatetime.tzinfo文档建议使用名为dateutil. 您可以安装dateutil使用pip

$ python -m pip install python-dateutil

请注意,您从 PyPI 安装的包python-dateutil的名称 , 与您用于导入包的名称不同,即dateutil.

使用dateutil到时区添加到Pythondatetime

dateutil如此有用的一个原因是它包含一个到 IANA 时区数据库的接口。这消除了为您的datetime实例分配时区的麻烦。试试这个例子,看看如何设置一个datetime实例以使用您的本地时区:

>>>
>>> from dateutil import tz
>>> from datetime import datetime
>>> now = datetime.now(tz=tz.tzlocal())
>>> now
datetime.datetime(2020, 1, 26, 0, 55, 3, 372824, tzinfo=tzlocal())
>>> now.tzname()
'Eastern Standard Time'

在本例中,您导入 tzfromdateutildatetimefrom datetime。然后,您可以使用datetime来创建一个设置为当前时间的实例.now()

您还将tz关键字传递给.now()并设置为tz等于tz.tzlocal()。在 中dateutiltz.tzlocal()返回 的具体实例datetime.tzinfo。这意味着它可以代表所有需要的时区偏移和夏令时信息datetime

您还可以使用 打印时区的名称.tzname(),它会打印'Eastern Standard Time'. 这是 Windows 的输出,但在 macOS 或 Linux 上,'EST'如果您在冬季处于美国东部时区,则您的输出可能会读取。

您还可以创建与计算机报告的时区不同的时区。为此,您将使用tz.gettz()并传递您感兴趣的时区的官方IANA 名称。以下是如何使用的示例tz.gettz()

>>>
>>> from dateutil import tz
>>> from datetime import datetime
>>> London_tz = tz.gettz("Europe/London")
>>> now = datetime.now(tz=London_tz)
>>> now
datetime.datetime(2020, 1, 26, 6, 14, 53, 513460, tzinfo=tzfile('GB-Eire'))
>>> now.tzname()
'GMT'

在此示例中,您使用tz.gettz()检索英国伦敦的时区信息并将其存储在London_tz. 然后检索当前时间,将时区设置为London_tz

在 Windows 上,这为tzinfo属性提供了值tzfile('GB-Eire')。在 macOS 或 Linux 上,该tzinfo属性将类似于tzfile('/usr/share/zoneinfo/Europe/London),但根据dateutil提取时区数据的位置,它可能会略有不同。

您还用于tzname()打印时区的名称,即 now 'GMT',意思是格林威治标准时间。此输出在 Windows、macOS 和 Linux 上是相同的。

在前面的部分中,您了解到不应使用.utcnow()datetime当前 UTC 时间创建实例。现在您知道如何使用dateutil.tzdatetime实例提供时区。这是根据 Python 文档中的建议修改的示例:

>>>
>>> from dateutil import tz
>>> from datetime import datetime
>>> datetime.now(tz=tz.UTC)
datetime.datetime(2020, 3, 14, 19, 1, 20, 228415, tzinfo=tzutc())

在此代码中,您用于tz.UTC将时区设置datetime.now()为 UTC 时区。建议使用这种方法比使用utcnow(),因为utcnow()回报天真的 datetime实例,而这里展示的方法返回一个认识 datetime实例。

接下来,您将走一小段路来了解朴素实例与感知 datetime实例。如果您已经了解这一切,那么您可以跳过以使用时区信息改进您的 PyCon 倒计时。

比较 Naive 和 Aware Pythondatetime实例

Pythondatetime实例支持两种类型的操作,naive 和aware。它们之间的基本区别在于,naive 实例不包含时区信息,而感知实例包含时区信息。更正式地,引用 Python 文档:

一个有意识的对象代表一个特定的时间,它是不可解释的。一个天真的对象不包含足够的信息来明确地相对于其他日期/时间对象定位自己。(来源

这是使用 Python 的一个重要区别datetime。一个感知 datetime实例可以明确地将自己与其他感知datetime实例进行比较,并且在算术运算中使用时将始终返回正确的时间间隔。

datetime另一方面,朴素的实例可能是模棱两可的。这种歧义的一个例子与夏令时有关。实行夏令时的地区在春季将时钟向前拨一小时,在秋季拨开一小时。这通常发生在当地时间凌晨 2:00。在春天,从凌晨 2:00 到凌晨 2:59 的时间不会发生,而在秋天,从凌晨 1:00 到凌晨 1:59 的时间会发生两次

实际上,这些时区与 UTC 的偏移量全年都会发生变化。IANA 跟踪这些更改并将它们编入您计算机已安装的不同数据库文件中。使用像 一样的库dateutil,它在底层使用 IANA 数据库,是确保您的代码随着时间正确处理算术的好方法。

注意:在 Python 中,naivedatetime实例和感知实例之间的区别是由tzinfo属性决定的。感知datetime实例具有tzinfodatetime.tzinfo抽象基类的子类相等的属性。

Python的3.8及以下提供的一个具体实施tzinfo所谓的timezone。但是,timezone仅限于表示与 UTC 的固定偏移量,该偏移量不能全年更改,因此当您需要考虑夏令时等更改时,它不是很有用。

Python 3.9包含一个名为的新模块zoneinfo,该模块提供tzinfo跟踪 IANA 数据库的具体实现,因此它包括夏令时等更改。然而,在 Python 3.9 被广泛使用之前,dateutil如果您需要支持多个 Python 版本,依赖它可能是有意义的。

dateutil还提供了您之前使用tzinfotz模块中的几个具体实现。您可以查看dateutil.tz文档以获取更多信息。

这并不意味着您总是需要使用感知datetime实例。但是,如果您要相互比较时间,则有意识的实例至关重要,尤其是在比较世界不同地区的时间时。

改进你的 PyCon 倒计时

现在您知道如何向 Pythondatetime实例添加时区信息,您可以改进 PyCon 倒计时代码。早些时候,您使用标准datetime构造函数传递 PyCon 将启动的年、月、日和小时。您可以更新代码以使用该dateutil.parser模块,该模块为创建datetime实例提供了更自然的界面:

# pyconcd.py

from dateutil import parser, tz
from datetime import datetime

PYCON_DATE = parser.parse("May 12, 2021 8:00 AM")
PYCON_DATE = PYCON_DATE.replace(tzinfo=tz.gettz("America/New_York"))
now = datetime.now(tz=tz.tzlocal())

countdown = PYCON_DATE - now
print(f"Countdown to PyCon US 2021: {countdown}")

在此代码中,您导入 parsertz来自dateutildatetime来自datetime。接下来,您使用parser.parse()从字符串中读取下一个 PyCon US 的日期。这比普通的datetime构造函数更具可读性。

parser.parse()返回一个天真的datetime实例,让你用.replace()改变tzinfoAmerica/New_York时区。PyCon US 2021 将在位于美国东部时区的宾夕法尼亚州匹兹堡举行。该时区的规范名称是America/New_York因为纽约市是该时区中最大的城市。

PYCON_DATEdatetime时区设置为美国东部时间的感知实例。由于 5 月 12 日是夏令时生效后,时区名称为'EDT', 或'Eastern Daylight Time'

接下来,您创建now代表当前时刻并为其指定本地时区。最后,找到andtimedelta之间的值并打印结果。如果您所在的地区不针对夏令时调整时钟,那么您可能会看到 PyCon 更改一个小时之前的剩余小时数。PYCON_DATEnow

用 Python 进行算术运算 datetime

Pythondatetime实例支持多种类型的算术。正如您之前看到的,这依赖于使用timedelta实例来表示时间间隔。timedelta非常有用,因为它内置于 Python 标准库中。以下是如何使用的示例timedelta

>>>
>>> from datetime import datetime, timedelta
>>> now = datetime.now()
>>> now
datetime.datetime(2020, 1, 26, 9, 37, 46, 380905)
>>> tomorrow = timedelta(days=+1)
>>> now + tomorrow
datetime.datetime(2020, 1, 27, 9, 37, 46, 380905)

在此代码,您可以创建now,存储当前时间,并且tomorrow,这是一个timedelta+1天。接下来,您添加now并在未来的某一天tomorrow生成一个datetime实例。请注意,使用原始datetime实例,就像您在这里一样,意味着增量的day属性datetime增加一并且不考虑任何重复或跳过的时间间隔。

timedelta 实例还支持负值作为参数的输入:

>>>
>>> yesterday = timedelta(days=-1)
>>> now + yesterday
datetime.datetime(2020, 1, 25, 9, 37, 46, 380905)

在此示例中,您将 提供-1为 的输入timedelta,因此当您添加now和 时yesterday,结果是days属性减 1 。

timedelta实例支持所有参数的加法和减法以及正整数和负整数。您甚至可以提供正面和负面参数的混合。例如,您可能想要添加三天并减去四小时:

>>>
>>> delta = timedelta(days=+3, hours=-4)
>>> now + delta
datetime.datetime(2020, 1, 29, 5, 37, 46, 380905)

在此示例中,您添加了三天并减去了四小时,因此新的时间datetime是 1 月 29 日凌晨 5:37。timedelta在这种方式下非常有用,但它有一定的局限性,因为它不能添加或减去大于一天的间隔,例如一个月或一年。幸运的是,dateutil提供了一个更强大的替代品,称为relativedelta.

的基本语法relativedeltatimedelta. 您可以提供关键字参数来产生任意数量的年、月、日、小时、秒或微秒的变化。您可以timedelta使用以下代码重现第一个示例:

>>>
>>> from dateutil.relativedelta import relativedelta
>>> tomorrow = relativedelta(days=+1)
>>> now + tomorrow
datetime.datetime(2020, 1, 27, 9, 37, 46, 380905)

在本例中,您使用relativedelta代替timedelta来查找datetime与明天相对应的。现在,您可以尝试now在减去四小时三十分钟的同时加上五年、一个月和三天:

>>>
>>> delta = relativedelta(years=+5, months=+1, days=+3, hours=-4, minutes=-30)
>>> now + delta
datetime.datetime(2025, 3, 1, 5, 7, 46, 380905)

请注意,在此示例中,日期以 2025 年 3 月 1 日结束。这是因为将 3 天添加到now1 月 29 日,将一个月添加到 2 月 29 日,这仅存在于闰年。由于 2025 年不是闰年,因此日期会滚动到下个月。

您还可以使用relativedelta来计算两个datetime实例之间的差异。早些时候,您使用减法运算符来查找两个 Pythondatetime实例之间的差异,PYCON_DATE并且now. 使用relativedelta, 而不是使用减法运算符,您需要将两个datetime实例作为参数传递:

>>>
>>> now
datetime.datetime(2020, 1, 26, 9, 37, 46, 380905)
>>> tomorrow = datetime(2020, 1, 27, 9, 37, 46, 380905)
>>> relativedelta(now, tomorrow)
relativedelta(days=-1)

在此示例中,您通过将字段增加 1datetime来创建一个新实例。然后,您使用和传递和作为两个参数。然后取这两个实例之间的差异并将结果作为实例返回。在这种情况下,差异是天,因为发生在 之前。tomorrowdaysrelativedeltanowtomorrowdateutildatetimerelativedelta-1nowtomorrow

dateutil.relativedelta对象还有无数其他用途。您可以使用它们来查找复杂的日历信息,例如下一年的 10 月 13 日是星期五或当月最后一个星期五的日期。您甚至可以使用它们来替换datetime实例的属性并创建,例如,datetime未来一周的上午 10:00。您可以在dateutil 文档中阅读所有关于这些其他用途的信息

完成你的 PyCon 倒计时

你现在有足够的工具来完成你的 PyCon 2021 倒计时时钟,并提供一个很好的界面来使用。在本节中,您将使用relativedelta计算 PyCon 之前的剩余时间,开发一个函数以漂亮的格式打印剩余时间,并向用户显示 PyCon 的日期。

使用relativedelta在你的PYCON倒计时

首先,用 替换普通减法运算符relativedelta。使用减法运算符,您的timedelta对象无法计算大于一天的时间间隔。但是,relativedelta允许您显示剩余的年、月和日:

 1# pyconcd.py
 2
 3from dateutil import parser, tz
 4from dateutil.relativedelta import relativedelta
 5from datetime import datetime
 6
 7PYCON_DATE = parser.parse("May 12, 2021 8:00 AM")
 8PYCON_DATE = PYCON_DATE.replace(tzinfo=tz.gettz("America/New_York"))
 9now = datetime.now(tz=tz.tzlocal())
10
11countdown = relativedelta(PYCON_DATE, now)
12print(f"Countdown to PyCon US 2021: {countdown}")

您在此代码中所做的唯一更改是将第 11 行替换为countdown = relativedelta(PYCON_DATE, now). 此脚本的输出应告诉您 PyCon US 2021 将在大约一年零一个月后举行,具体取决于您运行脚本的时间。

但是,该输出不是很漂亮,因为它看起来像relativedelta(). 你可以通过用下面的代码替换前面代码中的第 11 行来构建一些更漂亮的输出:

11def time_amount(time_unit: str, countdown: relativedelta) -> str:
12    t = getattr(countdown, time_unit)
13    return f"{t} {time_unit}" if t != 0 else ""
14
15countdown = relativedelta(PYCON_DATE, now)
16time_units = ["years", "months", "days", "hours", "minutes", "seconds"]
17output = (t for tu in time_units if (t := time_amount(tu, countdown)))
18print("Countdown to PyCon US 2021:", ", ".join(output))

此代码需要 Python 3.8,因为它使用了新的walrus operator。通过使用传统for循环代替第 17 行,您可以使该脚本在旧版本的 Python上运行

在此代码中,您定义了time_amount(),它接受两个参数,relativedelta时间单位和应从中检索时间单位的实例。如果时间量不为零,则time_amount()返回一个包含时间量和时间单位的字符串。否则,它返回一个空字符串。

您可以使用time_amount()理解线17。该行创建了一个生成器,用于存储从 返回的非空字符串time_amount()。它采用海象运营商分配的返回值time_amount(),以t包括t只有当它是True

最后,第 18 行使用.join()on生成器打印最终输出。接下来,您将查看在脚本的输出中包含 PyCon 日期。

在 PyCon 倒计时中显示 PyCon 日期

早些时候,您学习了datetime使用.strptime(). 此方法使用 Python 中的特殊迷你语言来指定日期字符串的格式。

Pythondatetime有一个额外的方法被调用.strftime(),它允许你将一个datetime实例格式化为一个字符串。从某种意义上说,它是解析 using 的逆操作.strptime()。您可以通过记住pin.strptime()代表parsefin.strftime()代表format来区分这两种方法。

在您的 PyCon 倒计时中,您可以使用.strftime()打印输出让用户知道 PyCon US 的开始日期。请记住,您可以在strftime.org上找到要使用的格式代码。现在在PyCon 倒计时脚本的第 18 行添加此代码:

18pycon_date_str = PYCON_DATE.strftime("%A, %B %d, %Y at %H:%M %p %Z")
19print(f"PyCon US 2021 will start on:", pycon_date_str)
20print("Countdown to PyCon US 2021:", ", ".join(output))

在这段代码中,第18层的用途.strftime(),以创建代表PYCON美国的起始日期2021的输出包括一个字符串的工作日,月,日,年,小时,分钟,上午或下午,和时区:

Wednesday, May 12, 2021 at 08:00 AM EDT

第 19 行,您打印此字符串以供用户查看并带有一些解释性文本。最后一行打印到 PyCon 开始日期的剩余时间。接下来,您将完成您的脚本,以便其他人可以更轻松地重复使用。

完成你的 PyCon 倒计时

您需要采取的最后一步是遵循 Python最佳实践,并将产生输出的代码放入一个main()函数中。应用所有这些更改后,您可以查看完整的最终代码:

 1# pyconcd.py
 2
 3from dateutil import parser, tz
 4from dateutil.relativedelta import relativedelta
 5from datetime import datetime
 6
 7PYCON_DATE = parser.parse("May 12, 2021 8:00 AM")
 8PYCON_DATE = PYCON_DATE.replace(tzinfo=tz.gettz("America/New_York"))
 9
10def time_amount(time_unit: str, countdown: relativedelta) -> str:
11    t = getattr(countdown, time_unit)
12    return f"{t} {time_unit}" if t != 0 else ""
13
14def main():
15    now = datetime.now(tz=tz.tzlocal())
16    countdown = relativedelta(PYCON_DATE, now)
17    time_units = ["years", "months", "days", "hours", "minutes", "seconds"]
18    output = (t for tu in time_units if (t := time_amount(tu, countdown)))
19    pycon_date_str = PYCON_DATE.strftime("%A, %B %d, %Y at %H:%M %p %Z")
20    print(f"PyCon US 2021 will start on:", pycon_date_str)
21    print("Countdown to PyCon US 2021:", ", ".join(output))
22
23if __name__ == "__main__":
24    main()

在此代码中,您将print()用于生成器的代码移动到main(). 在第 23 行,您使用guard 子句来确保main()仅在此文件作为脚本执行时才运行。这允许其他人导入您的代码并重用PYCON_DATE,例如,如果他们愿意的话。

现在,您可以根据需要随意修改此脚本。一件巧妙的事情可能是允许用户now通过传递命令行参数来更改与之关联的时区。您还可以将其更改PYCON_DATE为离家更近的东西,例如PyCon AfricaEuroPython

要对 PyCon 更加兴奋,请查看PyCon US 2019 上的 Real Python以及如何充分利用 PyCon

Python 的替代品datetimedateutil

当您处理日期和时间时,Pythondatetimedateutil库的强大组合。dateutil甚至在 Python 文档中也推荐使用。但是,还有许多其他库可用于在 Python 中处理日期和时间。其中一些依赖于datetimeand dateutil,而另一些则是完全独立的替代品:

  • pytz提供类似于dateutil. 它使用的界面与标准有所不同datetime.tzinfo,因此如果您决定使用它,请注意潜在的问题
  • Arrowdatetime. 它的灵感来自moment.js,所以如果您来自 Web 开发,那么这可能是一个更熟悉的界面。
  • Pendulumdatetime. 它包括一个时区接口和一个改进的timedelta实现。
  • Maya提供了与datetime. 它依赖 Pendulum 来解析库的部分内容。
  • dateparser提供了一个接口来datetime从人类可读的文本生成实例。它很灵活,支持多种语言。

此外,如果您大量使用NumPyPandas或其他数据科学包,那么有几个选项可能对您有用:

  • NumPy提供了与内置 Pythondatetime库类似的 API,但 NumPy 版本可以在数组中使用。
  • Pandas通过使用 NumPy模块为DataFrames 中的时间序列数据提供支持,通常是基于时间的事件的顺序值datetime
  • cftime支持除公历之外的其他日历以及符合气候和预测 (CF) 公约的其他时间单位。xarray包使用它来提供时间序列支持。

进一步阅读

由于随时间编程可能非常复杂,因此网络上有许多资源可以帮助您了解更多相关信息。幸运的是,这是许多使用每种编程语言的人都考虑过的问题,因此您通常可以找到信息或工具来帮助解决您可能遇到的任何问题。以下是我发现对编写本教程很有帮助的精选文章和视频列表:

此外,Paul Ganssle 是 CPython 的核心贡献者和dateutil. 他的文章和视频是 Python 用户的绝佳资源:

结论

在本教程中,您了解了使用日期和时间进行编程以及为什么它经常导致错误和混乱。您还了解了 Pythondatetimedateutil模块以及如何在代码中使用时区。

现在你可以:

  • 在您的程序中以一种面向未来的良好格式存储日期
  • 创建datetime带有格式化字符串的Python实例
  • 时区信息添加datetime实例中dateutil
  • 使用datetime实例执行算术运算relativedelta

最后,您创建了一个脚本,它可以倒计时到下一次 PyCon US 的剩余时间,这样您就可以为最大的 Python 聚会感到兴奋。日期和时间可能很棘手,但是使用您的武器库中的这些 Python 工具,您已准备好解决最棘手的问题!

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。