使用 Python datetime 处理日期和时间
目录
处理日期和时间是编程中最大的挑战之一。在处理时区、夏令时和不同的书面日期格式之间,很难跟踪您所引用的日期和时间。幸运的是,内置的 Pythondatetime
模块可以帮助您管理日期和时间的复杂性质。
在本教程中,您将学习:
- 为什么用日期和时间编程是一个挑战
- Python
datetime
模块中有哪些函数可用 - 如何以特定格式打印或读取日期和时间
- 如何对日期和时间进行算术运算
另外,您将开发一个简洁的应用程序来倒计时到下一个 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
代表一个四位数年份,MM
并DD
有两位数的月和日,从零如果需要的话。之后,HH
, MM
, 和SS
代表两位数的小时、分钟和秒,必要时从零开始。
这种格式的优点是可以毫无歧义地表示日期。如果日期是有效的月数,则写为DD-MM-YYYY
或MM-DD-YYYY
可能被误解的日期。稍后您将看到如何在 Python 中使用 ISO 8601 格式datetime
。
时间应该如何存储在你的程序中
大多数使用 time 的开发人员都听说过将本地时间转换为 UTC 并存储该值以供以后参考的建议。在许多情况下,尤其是当您存储过去的日期时,这些信息足以进行任何必要的算术运算。
但是,如果您的程序的用户输入了他们本地时间的未来日期,则可能会出现问题。时区和夏令时规则变化相当频繁,正如您在 2007 年美国和加拿大夏令时更改时看到的那样。如果用户位置的时区规则在他们输入的未来日期之前发生变化,则 UTC 将无法提供足够的信息来转换回正确的本地时间。
注意:有许多优秀的资源可帮助您确定在应用程序中存储时间数据的适当方式。这里有几个地方可以开始:
在这种情况下,您需要存储本地时间,包括用户输入的时区,以及用户保存时间时有效的 IANA 时区数据库版本。这样,您将始终能够将本地时间转换为 UTC。但是,这种方法并不总是允许您将 UTC 转换为正确的本地时间。
使用 Pythondatetime
模块
如您所见,在编程中处理日期和时间可能很复杂。幸运的是,如今您很少需要从头开始实现复杂的功能,因为有许多开源库可以提供帮助。这在 Python 中绝对是这样,它在标准库中包含三个独立的模块来处理日期和时间:
在本教程中,您将专注于使用 Pythondatetime
模块。的主要重点datetime
是降低访问与日期、时间和时区相关的对象属性的复杂性。由于这些对象非常有用,calendar
还从datetime
.
time
功能不如datetime
. 许多函数time
返回一个特殊的struct_time
实例。该对象具有用于访问存储数据的命名元组接口,使其类似于 的实例datetime
。但是,它不支持 的所有功能datetime
,尤其是使用时间值执行算术的能力。
datetime
提供了三个类,它们构成了大多数人会使用的高级接口:
datetime.date
是一个理想化的日期,假定公历无限延伸到未来和过去。这个对象存储year
,month
以及day
为属性。datetime.time
是一个理想化的时间,假设每天有 86,400 秒,没有闰秒。此对象存储的hour
,minute
,second
,microsecond
,和tzinfo
(时区信息)。datetime.datetime
是 adate
和 a的组合time
。它具有两个类的所有属性。
创建 Pythondatetime
实例
表示日期和时间的三个类datetime
具有相似的初始值设定项。它们可以通过传递关键字参数对于每个属性,诸如被实例化year
,date
或hour
。您可以尝试下面的代码来了解每个对象是如何创建的:
>>> 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
实例的便捷方式。这些方法不需要您使用整数来指定每个属性,而是允许您使用其他一些信息:
date.today()
创建一个datetime.date
具有当前本地日期的实例。datetime.now()
创建一个datetime.datetime
具有当前本地日期和时间的实例。datetime.combine()
将datetime.date
和datetime.time
的datetime.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()
创建的实例date
,datetime
和time
对象。每个实例都存储在不同的变量中:
today
是一个date
只有年、月和日的实例。now
是datetime
具有年、月、日、小时、分钟、秒和微秒的实例。current_time
是一个time
实例,其小时、分钟和秒设置为与 相同的值now
。
在最后一行,您将日期信息today
与时间信息组合current_time
以生成一个新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()
创建date
2020 年 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_string
和format_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_string
datetime
.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}")
在此代码,您导入datetime
从datetime
和定义常量,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.datetime
并datetime.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'
在本例中,您导入 tz
fromdateutil
和datetime
from datetime
。然后,您可以使用datetime
来创建一个设置为当前时间的实例.now()
。
您还将tz
关键字传递给.now()
并设置为tz
等于tz.tzlocal()
。在 中dateutil
,tz.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.tz
为datetime
实例提供时区。这是根据 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
实例具有tzinfo
与datetime.tzinfo
抽象基类的子类相等的属性。
Python的3.8及以下提供的一个具体实施tzinfo
所谓的timezone
。但是,timezone
仅限于表示与 UTC 的固定偏移量,该偏移量不能全年更改,因此当您需要考虑夏令时等更改时,它不是很有用。
Python 3.9包含一个名为的新模块zoneinfo
,该模块提供tzinfo
跟踪 IANA 数据库的具体实现,因此它包括夏令时等更改。然而,在 Python 3.9 被广泛使用之前,dateutil
如果您需要支持多个 Python 版本,依赖它可能是有意义的。
dateutil
还提供了您之前使用tzinfo
的tz
模块中的几个具体实现。您可以查看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}")
在此代码中,您导入 parser
和tz
来自dateutil
和datetime
来自datetime
。接下来,您使用parser.parse()
从字符串中读取下一个 PyCon US 的日期。这比普通的datetime
构造函数更具可读性。
parser.parse()
返回一个天真的datetime
实例,让你用.replace()
改变tzinfo
的America/New_York
时区。PyCon US 2021 将在位于美国东部时区的宾夕法尼亚州匹兹堡举行。该时区的规范名称是America/New_York
因为纽约市是该时区中最大的城市。
PYCON_DATE
是datetime
时区设置为美国东部时间的感知实例。由于 5 月 12 日是夏令时生效后,时区名称为'EDT'
, 或'Eastern Daylight Time'
。
接下来,您创建now
代表当前时刻并为其指定本地时区。最后,找到andtimedelta
之间的值并打印结果。如果您所在的地区不针对夏令时调整时钟,那么您可能会看到 PyCon 更改一个小时之前的剩余小时数。PYCON_DATE
now
用 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
.
的基本语法relativedelta
与timedelta
. 您可以提供关键字参数来产生任意数量的年、月、日、小时、秒或微秒的变化。您可以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 天添加到now
1 月 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
来创建一个新实例。然后,您使用和传递和作为两个参数。然后取这两个实例之间的差异并将结果作为实例返回。在这种情况下,差异是天,因为发生在 之前。tomorrow
days
relativedelta
now
tomorrow
dateutil
datetime
relativedelta
-1
now
tomorrow
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
。
在 PyCon 倒计时中显示 PyCon 日期
早些时候,您学习了datetime
使用.strptime()
. 此方法使用 Python 中的特殊迷你语言来指定日期字符串的格式。
Pythondatetime
有一个额外的方法被调用.strftime()
,它允许你将一个datetime
实例格式化为一个字符串。从某种意义上说,它是解析 using 的逆操作.strptime()
。您可以通过记住p
in.strptime()
代表parse和f
in.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 Africa或EuroPython。
要对 PyCon 更加兴奋,请查看PyCon US 2019 上的 Real Python以及如何充分利用 PyCon!
Python 的替代品datetime
和dateutil
当您处理日期和时间时,Pythondatetime
和dateutil
库的强大组合。dateutil
甚至在 Python 文档中也推荐使用。但是,还有许多其他库可用于在 Python 中处理日期和时间。其中一些依赖于datetime
and dateutil
,而另一些则是完全独立的替代品:
进一步阅读
由于随时间编程可能非常复杂,因此网络上有许多资源可以帮助您了解更多相关信息。幸运的是,这是许多使用每种编程语言的人都考虑过的问题,因此您通常可以找到信息或工具来帮助解决您可能遇到的任何问题。以下是我发现对编写本教程很有帮助的精选文章和视频列表:
- Daylight saving time and time zone best practices
- Storing UTC is not a Silver Bullet
- How to save datetimes for future events
- Coding Best Practices Using DateTime in the .NET Framework
- Computerphile: The Problem with Time & Timezones
- The Complexity of Time Data Programming
此外,Paul Ganssle 是 CPython 的核心贡献者和dateutil
. 他的文章和视频是 Python 用户的绝佳资源:
结论
在本教程中,您了解了使用日期和时间进行编程以及为什么它经常导致错误和混乱。您还了解了 Pythondatetime
和dateutil
模块以及如何在代码中使用时区。
现在你可以:
- 在您的程序中以一种面向未来的良好格式存储日期
- 创建
datetime
带有格式化字符串的Python实例 - 将时区信息添加到
datetime
实例中dateutil
- 使用
datetime
实例执行算术运算relativedelta
最后,您创建了一个脚本,它可以倒计时到下一次 PyCon US 的剩余时间,这样您就可以为最大的 Python 聚会感到兴奋。日期和时间可能很棘手,但是使用您的武器库中的这些 Python 工具,您已准备好解决最棘手的问题!
- 点赞
- 收藏
- 关注作者
评论(0)