Python 标准库详解:datetime 模块解析

1. 官方文档

datetime — 基本日期和时间类型 — Python 3.12.2 文档

tz — dateutil 3.9.0 documentation

2. 背景

2.1 处理时间数据的难点

计算机程序喜欢有序的、有规则的事件,但对于时间数据,其涉及的规则复杂且可能有变化,最典型例子就是夏令时和时区。理想情况下,时区边界应该精确地遵循经度线,然而由于历史和政治原因,时区线很少是直线的,有些时区的形状会很奇怪:相隔很远的区域处于同一时区,而相邻的区域处于不同的时区。夏令时的规则变化也很常见,比如,美国和加拿大在2007年前,夏令时的调整在每年的4月和10月进行,在2007年之后,改为在每年的3月和11月进行。

2.2 计算机如何保存时间数据

Unix 时间:计算机系统中广泛使用的时间标准之一,指从协调世界时(UTC)1970年1月1日0时0分0秒起至现在的总秒数(UTC 协调世界时,指的是0°经度的时间,也被称为格林尼治标准时间 GMT,UTC 没有夏令时的概念,始终保持每天24小时)。Unix时间是一个连续的整数,因此可以方便地进行时间计算和比较,且不受时区和夏令时等因素的影响。

Unix 时间对于人类来说几乎是难以理解的。Unix 时间通常被转换为 UTC,然后可以使用时区偏移量(zone offset)将其转换为本地时间。

Internet Assigned Numbers Authority (IANA) 负责维护一个包含所有时区偏移量的数据库。IANA 会定期发布更新这些信息的变化。该数据库通常包含在操作系统中,某些应用程序也可能含有更新的副本。

2.3 标准的时间记法

不同的语言和文化有不同的日期书写方式,例如,在美国,日期通常表示为:月日年,2020年1月12日通常被写成01-12-2020;在大多数欧洲和许多其他地区,日期通常表示为:日月年,2020年1月31日对应12-01-2020。

在跨文化交流时,这种差异有可能造成理解上的偏差。为了避免沟通问题,国际标准化组织(ISO)制定了 ISO 8601 标准,此标准规定所有的日期都应该按照最高到最低有效数据的顺序来写,即标准格式为年、月、日、时、分和秒。

YYYY-MM-DD HH:MM:SS

YYYY表示四位数字的年份,MM和DD表示两位数字的月和日,必要时以零开头,HH、MM和SS表示两位数的小时、分钟和秒,必要时以零开头。

2.4 程序中如何保存时间数据

你可能会想到一种直接的方法:将本地时间转换为UTC并存储该值,以供以后使用。

多数情况下,特别是在存储过去的日期时,这样做不会造成问题,因为能够获取到任何所需的信息用以进行运算。但是,如果用户在其本地时间中输入未来日期,由于时区和夏令时的规则可能发生变化,这样做可能会带来问题。

时区和夏令时的规则变化非常频繁,比如,在美国和加拿大,2007年之前的夏令时规则是:在4月的第一个星期日调快一小时,在10月的最后一个星期日调慢一小时;2007年之后的夏令时规则是:在3月的第二个星期日调快一小时,在11月的第一个星期日调慢一小时。如果用户所在位置的夏令时或时区规则在未来日期到来之前发生了变化,那么未来日期对应的 UTC 也将变化,之前保存的 UTC 将无法转换为正确的本地时间。

在这种情况下,您需要存储用户输入的本地时间(包括时区)以及用户保存时间时生效的 IANA 时区数据库的版本。

2.5 Python 标准库中关于时间的模块

Python 标准库中包含三个独立的模块来处理日期和时间:

calendar:用于创建和操作日历。它提供了许多方法来操作日期,如查找某个月份的天数、某个日期是星期几等。calendar 模块中最常用的函数是 calendar.month(),它可以输出指定月份的日历。

time:用于操作时间。它提供了许多函数以实现获取当前时间、将时间戳与日期值互相转换等功能。time 中许多函数返回一个 struct_time 实例,该对象是一个命名元组,可以通过索引或属性名访问时间信息,这一点与 datetime 实例类似,但 datetime 的功能更全面,特别是对时间值进行算术运算(arithmetic operation)的能力。

datetime:用于操作日期和时间。由于datetime 功能强大,calendar 也返回datetime类的实例。

datetime 提供了三个类:

  1. datetime.date:一个理想化的日期,它假设公历无限地延伸到未来和过去。该对象将年、月和日存储为属性。
  2. datetime.time:一个理想化的时间,假设每天有86400秒,没有闰秒。该对象存储时、分、秒、微秒和 tzinfo (时区信息)。
  3. datetime.datetime:是 datetime.date 和 datetime.time 的结合,具有这两个类的所有属性。

3. 知识点

感知型对象和简单型对象(Aware and Naive Objects)

datetime 对象可以根据是否包含时区信息分为“感知型”和“简单型”两类。

  • 感知型对象:具有政治性时间调整信息(如时区和夏令时),能够定位自身相对于其他感知型对象的精确时间点。 感知型对象是一个没有解释空间的固定时间点。对于要求感知型对象的应用程序,datetime 和 time 对象具有一个可选的时区信息属性 tzinfo,它接受抽象类 tzinfo 的子类的一个实例。 tzinfo 对象会捕获与 UTC 时间的差值、时区名称以及夏令时是否生效等信息。
  • 感知型 datetime 实例可以明确地将自己与其他感知型 datetime 实例进行比较,并且在进行算术运算时返回正确的时间差。

  • 简单型对象:没有足够多的信息,不能定位自身相对于其他 datetime 对象的时间点。 一个简单型对象所代表的是世界标准时间(UTC)、当地时间还是某个其他时区的时间,完全取决于具体程序,就像一个特定数字所代表的是米、英里还是质量完全取决于具体程序一样。 简单型对象更易于理解和使用,代价则是忽略了某些现实性考量。
    1. date 类型的对象都是简单型的。
    2. time 或 datetime 类型的对象可以是感知型或者简单型,对于一个x,以下两个条件同时成立时,x是感知型的:(1)x.tzinfo 不为 None(2)x.tzinfo.utcoffset() 不返回 None。
    3. 感知型和简单型之间的区别不适用于 timedelta 对象。
    4. 简单型 datetime 对象会被许多 datetime 方法当作本地时间来处理,如果你有一个表示 UTC 的简单型 datetime,请使用 datetime.replace(tzinfo=timezone.utc) 将其改为感知型。

    4. datetime 类

    4.1 构造函数

    1. hour 为24小时制。
    2. fold 用于指示在由本地时间转换为 UTC 时间时,可能会重复出现的本地时间。这种情况通常发生在夏令时结束时,当时钟被调回一小时时,同一本地时间会出现两次。为了避免数据丢失,datetime.datetime 对象中包含了一个 fold 属性,用于表示这个重复的本地时间是第一次出现还是第二次出现。fold 可以取值 0 或 1,0 表示第一次出现,1 表示第二次出现。

    4.2 类方法

    方法

    描述

    datetime.today()

    返回一个简单型datetime 对象,表示当前地方时间。

    注:tzinfo 为 None。

    此方法的功能等价于 now(),但是不带 tz 形参。

    datetime.now(tz=None)

    返回 datetime 对象,表示当前地方时间。

    (1)如果可选参数 tz 为 None 或未指定,这就类似于 today();

    (2)如果 tz 不为 None,它必须是 tzinfo 子类的一个实例,并且当前日期和时间将被转换到 tz 时区;

    (3)使用带 UTC(tz=timezone.utc)的 datetime.now() 得到当前 UTC datetime对象。

    datetime.utcnow()

    返回一个简单型 datetime 对象(其中 tzinfo 为 None),表示当前 UTC。

    注:不推荐使用此方法,简单型 datetime 对象会被许多 datetime 方法当作本地时间来处理,因此最好使用感知型日期时间对象来表示 UTC 时间,推荐使用 datetime.now(timezone.utc)。

    datetime.combine(date, time, 

    tzinfo=time.tzinfo)

    返回一个新的 datetime 对象,其日期部分等于给定的 date 对象的值,而其时间部分等于给定的 time 对象的值。

    (1)如果提供了 tzinfo 参数,其值会被用来设置结果的 tzinfo 属性,否则将使用 time 参数的 tzinfo 属性。

    (2)如果 date 参数是一个 datetime 对象,则其时间部分和 tzinfo 属性将被忽略。

    (3)对于任意 datetime 对象 d,d == datetime.combine(d.date(), d.time(), d.tzinfo)。

    datetime.strptime(date_string, format)

    返回一个对应于 date_string,根据 format 进行解析得到的datetime对象。

    (1)如果 format 不包含微秒或时区信息,等价于:

    datetime(*(time.strptime(date_string, format)[0:6]))

    (2)如果 date_string 和 format 无法被 time.strptime() 解析或它返回一个不是时间元组的值则将引发 ValueError

    datetime.fromisoformat(date_string)

    返回一个对应于以任何有效的 8601 格式给出的 date_string 的 datetime。

    datetime.fromisocalendar(year, week, day)

    返回以 year, week 和 day 值指明的 ISO 历法日期所对应的 datetime。

    datetime.fromordinal(ordinal)

    返回一个简单型 datetime 对象,表示格列高利历序号对应的日期。

    注:结果中的 hour, minute, second 和 microsecond 值均为 0,tzinfo 值为 None。

    datetime.fromtimestamp(timestamp, tz=None)

    返回一个简单型 datetime 对象,表示 unix time 对应的本地日期和时间。

    datetime.utcfromtimestamp(timestamp)

    返回一个简单型 datetime 对象,表示 unix time 对应的 UTC。

    注:tzinfo 值为 None。

    4.3 实例属性

    属性

    描述

    datetime.year

    datetime.month

    1 至 12(含)

    datetime.day

    1到指定年月的天数间的数字。

    datetime.hour

    range(24)

    datetime.minute

    range(60)。

    datetime.second

    range(60)。

    datetime.microsecond

    range(1000000)

    datetime.tzinfo

    传给 datetime 构造器的 tzinfo 参数,如果没有传入值则为 None

    datetime.fold

    取值范围是 [0,1],用于在重复的时间段中区分时间。(当夏令时结束时回拨或由于政治原因导致当前时区的 UTC 时差减少时,就会出现重复的时间段。)

    4.4 实例方法

    datetime.date()

    返回具有同样 year, month 和 day 值的 date 对象。

    datetime.time()

    返回具有同样 hour, minute, second, microsecond 和 fold 值的 time 对象,tzinfo 值为 None。

    datetime.timetz()

    返回具有同样 hour, minute, second, microsecond, fold 和 tzinfo 属性性的 time 对象。 

    datetime.replace(year=self.year, 

    month=self.month, day=self.day, 

    hour=self.hour, minute=self.minute, 

    second=self.second, 

    microsecond=self.microsecond, 

    tzinfo=self.tzinfo, *, fold=0)

    返回一个具有同样属性值的 datetime,除非通过任何关键字参数为某些属性指定了新值。

    注:可以通过指定 tzinfo=None 来从一个感知型 datetime 创建一个简单型 datetime 而不必转换日期和时间数据。

    datetime.astimezone(tz=None)

    返回一个具有新的 tzinfo 属性 tz 的 datetime 对象,并会调整日期和时间数据使得结果对应的 UTC 时间与 self 相同,但为 tz 时区的本地时间。

    (1)如果 self 为简单型,假定其为基于系统时区表示的时间。

    (2)如果调用时不传入参数 tz (或传入 tz=None) ,将假定目标时区为系统的本地时区。 转换后 datetime 实例的 .tzinfo 属性将被设为一个 timezone 实例,时区名称和时差值将从 OS 获取。

    (3)如果给出了 tz,tz 必须是一个 tzinfo 子类的实例,并且其 utcoffset() 和 dst() 方法不可返回 None。 

    (4)如果只是想要附加一个时区对象 tz 到一个 datetime 对象 dt 而不调整日期和时间数据,请使用 dt.replace(tzinfo=tz)。 如果只想从一个感知型 datetime 对象 dt 中移除时区对象,请使用 dt.replace(tzinfo=None)。

    datetime.utcoffset()

    utcoffset 指本地时间与 UTC 的时间差,如果 tzinfo 为 None,则返回 None,否则返回 self.tzinfo.utcoffset()。

    注:如果返回结果即不为 None,也不为一个幅度小于一天的 timedelta 对象,将引发异常。

    datetime.dst()

    dst 指夏令时,如果 tzinfo 为 None,则返回 None,否则返回 self.tzinfo.dst(self)。

    注:如果返回结果即不为 None,也不为一个幅度小于一天的 timedelta 对象,将引发异常。

    datetime.tzname()

    tzname 指时区名称,如果 tzinfo 为 None,则返回 None,否则返回 self.tzinfo.tzname(self)。

    注:如果返回结果即不为 None 也不为一个字符串,则引发异常。

    datetime.weekday()

    返回一个整数代表星期几,星期一为 0,星期天为 6。

    注:相当于 self.date().weekday()。

    datetime.isoweekday()

    返回一个整数代表星期几,星期一为 1,星期天为 7。

    注:相当于 self.date().isoweekday()。

    datetime.strftime(format)

    返回一个由显式格式字符串所控制的,代表日期和时间的字符串。 

    datetime.ctime()

    返回一个表示日期和时间的字符串,无论输入的是感知型还是简单型,输出字符串均不包含时区信息。

    注:d.ctime() 等效于:

    time.ctime(time.mktime(d.timetuple()))

    datetime.isoformat(

    sep='T', timespec='auto')

    返回一个以 ISO 8601 格式表示的日期和时间字符串

    (1)microsecond 不为 0

    YYYY-MM-DDTHH:MM:SS.ffffff,

    (2)microsecond 为 0

    YYYY-MM-DDTHH:MM:SS

    如果 utcoffset() 返回值不为 None,则添加一个字符串来给出 UTC 时差:

    (1)microsecond 不为 0

    YYYY-MM-DDTHH:MM:SS.ffffff+HH:MM[:SS[.ffffff]]

    (2)microsecond 为 0

    YYYY-MM-DDTHH:MM:SS+HH:MM[:SS[.ffffff]]

    可选参数 timespec 要包含的额外时间组件值 (默认为 'auto')。它可以是以下值之一:

    1. 'auto': 如果 microsecond 为 0 则与 'seconds' 相同,否则与 'microseconds' 相同。

    2. 'hours': 以两个数码的 HH 格式 包含 hour。

    3. 'minutes': 以 HH:MM 格式包含 hour 和 minute。

    4. 'seconds': 以 HH:MM:SS 格式包含 hour, minute 和 second。

    5. 'milliseconds': 包含完整时间,但将秒值的小数部分截断至毫秒。 格式为 HH:MM:SS.sss。

    6. 'microseconds': 以 HH:MM:SS.ffffff 格式包含完整时间。

    无效的 timespec 参数将引发 ValueError。

    datetime.isocalendar()

    返回一个由三部分组成的 named tuple: year, week 和 weekday。

    注:相当于 self.date().isocalendar()。

    datetime.toordinal()

    返回  datetime 对象的日期对应的 Gregorian 日历的序数(格列高利历序号)。

    注:Gregorian 日历是一种常用的日历系统,从公元 1 年 1 月 1 日开始,每一天都有一个唯一的序数。

    datetime.timestamp()

    返回一个浮点数,表示 datetime 对象对应的 unix time(秒表示的时间,也称 POSIX)。

    (1)对于感知型 datetime 实对象,返回值的计算方式相当于:(dt – datetime(1970, 1, 1, tzinfo=timezone.utc)).total_seconds()

    (2)对于表示 UTC 时间的简单型 datetime 对象(且系统时区不是 UTC),没有直接的方法能获取 unix time,不过可以使用:dt.replace(tzinfo=timezone.utc).timestamp()

    datetime.timetuple()

    返回 datetime 对象对应的 time.struct_time。

    d.timetuple() 等价于:time.struct_time((d.year, d.month, d.day, d.hour, d.minute, d.second,d.weekday(), yday, dst))

    其中,yday = d.toordinal() – date(d.year, 1, 1).toordinal() + 1 是日期在当前年份中的序号,起始序号 1 表示 1月 1 日。dst 根据 dst() 方法来设定:如果 tzinfo 为 None 或 dst() 返回 None,则将 dst 设为 -1;否则,如果 dst() 返回一个非零值则将 dst 设为 1;其他情况下设为 0。

    datetime.utctimetuple()

    返回 datetime 对象对应的 UTC time.struct_time。

    (1)对于简单型 datetime 对象,功能类似于 d.timetuple(),区别是 tm_isdst 会强制设为 0 而不管 d.dst() 返回什么结果(DST 对于 UTC 时间无效)。

    (2)对于感知型 datetime 对象,先通过 d.utcoffset() 等信息将 d 标准化为 UTC 时间,然后返回该标准化时间所对应的 time.struct_time,tm_isdst 为 0。

    4.4 算术运算

    运算

    描述

    datetime2 = datetime1 + timedelta

    如果 timedelta.days > 0 则在时间线上前进,如果 timedelta.days < 0 则在时间线上后退。

    (1)输出结果具有与输入的 datetime 相同的 tzinfo 属性,并且操作完成后 datetime2 – datetime1 == timedelta。

    (2)即使输入的是一个感知型对象,该方法也不会进行时区调整。

    datetime2 = datetime1 – timedelta

    如果 timedelta.days > 0 则在时间线上后退,如果 timedelta.days < 0 则在时间线上前进。

    计算时间差

    timedelta = datetime1 – datetime2

    仅对两个操作数均为简单型或均为感知型时有定义,如果一个是感知型而另一个是简单型,将会引发 TypeError。

    (1)如果两个比较数都是感知的,并且具有相同的tzinfo属性,则忽略tzinfo和fold属性,基于基本的日期时间进行计算或比较。

    (2)如果两个比较数都是感知的并且具有不同的tzinfo属性,则先将比较数转换为UTC(dt.replace(tzinfo=None) – dt.utcoffset()),再进行比较。

    相等性比较

    datetime1 == datetime2

    datetime1 != datetime2

    简单型和感知型 datetime 对象绝对不会相等,datetime 对象和不为 datetime 实例的 date 对象绝对不会相等(即使二者表示相同的日期)。

    注:重复间隔中的日期时间实例永远不等于其他时区中的日期时间实例。

    顺序比较

    datetime1 < datetime2

    datetime1 > datetime2

    datetime1 <= datetime2

    datetime1 >= datetime2

    简单型和感知型对象之间,以及 datetime 对象与非 datetime 实例的 date 对象之间的顺序比较会引发 TypeError。

     5. date 类

    类属性、类方法、实例属性、实例方法与 datetime 类似,详细内容参考官方文档。

    6. time 类

    类属性、类方法、实例属性、实例方法与 datetime 类似,详细内容参考官方文档。

    7. timedelta类

    timedelta 对象表示一段持续的时间,即两个 datetime 或 date 实例之间的差值。

    7.1 构造函数

    1)所有参数都是可选的,且默认为 0。 这些参数可以是整数,也可以是浮点数;可以是正数也可以是负数。

    2)尽管构造函数中有很多时间单位,实际上,只有 days, seconds 和 microseconds 会存储在内部:

    -999999999 <= days <= 999999999

    0 <= seconds < 3600*24 (一天的秒数)

    0 <= microseconds < 1000000(1秒的微秒数)

    3)其他时间单位(星期、分钟、小时、毫秒)会被换算为天、秒、微秒(标准化),换算规则如下:

    1星期会转换成7天;

    1分钟会转换成60秒;1小时会转换成3600秒;

    1毫秒会转换成1000微秒。

    代码1:标准化为 days, seconds 和 microseconds

    只有 days, seconds 和 microseconds 会存储在内部,对 days, seconds 和 microseconds 以外的参数执行“合并”操作

    from datetime import timedelta
    delta = timedelta(days=50, seconds=27, microseconds=10, milliseconds=29000, minutes=5, hours=8, weeks=2)
    delta
    # Only days, seconds, and microseconds remain
    # datetime.timedelta(days=64, seconds=29156, microseconds=10)

    代码2:负数值场景

    from datetime import timedelta
    d = timedelta(microseconds=-1)
    d
    # datetime.timedelta(days=-1, seconds=86399, microseconds=999999)
    (d.days, d.seconds, d.microseconds)
    # (-1, 86399, 999999)

    7.2 类属性

    属性

    描述

    timedelta.min

    最小的(负数)timedelta 对象:timedelta(-999999999)

    timedelta.max

    最大的(正数)timedelta 对象:timedelta(days=999999999, hours=23, minutes=59, seconds=59, microseconds=999999)

    timedelta.resolution

    两个不相等的 timedelta 对象最小的间隔:timedelta(microseconds=1)

    7.3 实例属性

    属性

    描述

    days

    取值范围:-999999999 至 999999999 ,含999999999

    seconds

    取值范围:0 至 86399,包含86399

    microseconds

    取值范围:0 至 86399,包含86399

    7.4 实例方法

    timedelta.total_seconds()

    返回占用了多少秒。等价于 td / timedelta(seconds=1)。

    对于秒以外的间隔单位,使用除法形式,如: td / timedelta(microseconds=1)。

    8. tzinfo | timezone

    8.1 tzinfo

    tzinfo是一个抽象基类,也就是说该类不应被直接实例化。需要定义tzinfo的子类来捕获有关特定时区的信息。

    tzinfo 的 (某个实体子类) 的实例可以被传给 datetime 和 time 对象的构造器。 这些对象会将它们的属性视为对应的本地时间,并且 tzinfo 对象支持展示本地时间与 UTC 的差值、时区名称以及 DST 差值的方法,都是与传给它们的日期或时间对象的相对值。

    8.2 timezone

    timezone 类是 tzinfo 的子类,它的实例代表一个由与 UTC 的固定时差定义的时区。

    类属性:timezone.utc (表示UTC时区,等价于 timezone(timedelta(0)))

    实例方法:

    timezone.utcoffset(dt)

    返回当 timezone 实例创建时指定的时差。

    dt 参数会被忽略。

    返回值是一个 timedelta 实例,其值等于本地时间与 UTC 之间的时差。

    timezone.tzname(dt)

    返回当 timezone 实例被构造时指定的固定值。

    timezone.fromutc(dt)

    返回 dt + offset。 dt 参数必须为一个感知型 datetime 实例,其中 tzinfo 值设为 self。

    代码示例:

    tz1 = timezone.utc
    tz2 = timezone(timedelta(hours=8), name='China')
    
    dt1 = datetime.now(tz=tz1)
    # datetime.datetime(2024, 3, 27, 7, 8, 9, 605318, tzinfo=datetime.timezone.utc)
    dt2 = datetime.now(tz=tz2)
    # datetime.datetime(2024, 3, 27, 15, 8, 9, 605318, tzinfo=datetime.timezone(datetime.timedelta(seconds=28800), 'China'))
    dt3 = datetime.now()
    # datetime.datetime(2024, 3, 27, 15, 8, 9, 605318)
    
    tz2.utcoffset(None)
    # datetime.timedelta(seconds=28800)
    tz2.tzname(None)
    # 'China'
    tz2.fromutc(dt2)
    # datetime.datetime(2024, 3, 27, 23, 8, 9, 605318, tzinfo=datetime.timezone(datetime.timedelta(seconds=28800), 'China'))

    应用1:创建 datetime 实例

    1. 基于构造函数(Instantiate Class

    import datetime
    
    datetime.date(year=2024, month=3, day=7)
    # datetime.date(2024, 3, 7)
    datetime.time(hour=14, minute=45, second=30)
    # datetime.time(14, 45, 30)
    datetime.datetime(year=2024, month=3, day =7, hour=14, minute=45, second=30)
    # datetime.datetime(2024, 3, 7, 14, 45, 30)

    2. 无具体时间值可提供时,可基于 datetime.datetime 类方法:today()/now()/combine() 创建 datetime 实例

    from datetime import date, time, datetime
    
    datetime.today()
    # datetime.datetime(2024, 3, 7, 15, 40, 8, 156908)
    
    now = datetime.now()
    # datetime.datetime(2024, 3, 7, 15, 40, 8, 158902)
    
    today_date = date.today()
    # datetime.date(2024, 3, 7)
    
    current_time = time(now.hour, now.minute, now.second)
    # datetime.time(15, 40, 8)
    
    combined_result = datetime.combine(today_date, current_time)
    # datetime.datetime(2024, 3, 7, 15, 40, 8)

    3. 将时间戳(用字符串表示的时间)转换为 datetime 对象

    (1)针对满足 ISO 8601 格式的字符串,可使用类方法 datetime.fromisoformat() 

    datetime.fromisoformat("2020-01-31 15:30:12")
    # datetime.datetime(2020, 1, 31, 15, 30, 12)

    (2)针对一般格式的字符串,可使用类方法 datetime.strptime() 

    from datetime import datetime
    
    date_string = "01-10-2020 14:45:37"
    format_string = "%m-%d-%Y %H:%M:%S"
    datetime.strptime(date_string, format_string)
    # datetime.datetime(2020, 1, 31, 14, 45, 37)

    4. 基于第三方库

    某些第三方库也可以创建 datetime 实例,一个典型的例子是 dateparser 库,它可以将各种日期和时间格式的字符串转换为Python datetime对象,从而使 Python 程序可以更方便地处理日期和时间。

    dateparser库支持多种语言的日期和时间格式,包括英语、中文、德语、法语、西班牙语等等,它可以自动识别日期和时间字符串中的格式,并将其转换为datetime对象,此外,它还可以处理相对时间,例如“2天前”、“3周后”等等。

    # pip install dateparser
    import dateparser
    
    dateparser.parse('today')
    # datetime.datetime(2024, 3, 7, 15, 14, 44, 597584)
    dateparser.parse('yesterday')
    # datetime.datetime(2024, 3, 6, 15, 14, 44, 600549)
    dateparser.parse('tomorrow')
    # datetime.datetime(2024, 3, 8, 15, 14, 44, 602573)
    
    dateparser.parse('morgen')  # Morgen is the German word for tomorrow
    # datetime.datetime(2024, 3, 8, 15, 14, 44, 602573)
    import dateparser
    
    date_string1 = "01-10-2020 14:45:37"
    date_string2 = "01 10 2020 14:45:37"
    
    dateparser.parse(date_string1)
    # datetime.datetime(2020, 1, 10, 14, 45, 37)
    dateparser.parse(date_string2)
    # datetime.datetime(2020, 1, 10, 14, 45, 37)
    

    应用2:设计一个倒计时程序

    1. 知识点:处理时区的Python 库 dateutil

    1.1 dateutil 库介绍

    如果需要比较世界不同地区的时间,需要关注时区信息,即,使用感知型 datetime 实例(Aware Instance)。Python datetime 提供了 tzinfo,它是一个抽象基类,允许使用datetime.datetime,datetime.time 包含时区以及夏令时信息。但是,datetime 不提供与 IANA 时区数据库交互的直接方式,因此,在涉及时区信息的场景下,建议使用名为 dateutil 的第三方库(可以使用pip安装dateuti)。

    dateuti 官方文档:tz — dateutil 3.9.0 documentation

    安装:pip install python-dateutil

    1.2 dateutil.tz 常用方法

    tz.tzlocal()

    获取当前时区,返回 datetime.tzinfo 的具体实例,含有表示 datetime 所需的所有必要的时区偏移量和夏令时信息。

    tz.gettz(name=None)

    传入时区的官方IANA名称,获取指定时区;如果未提供参数或者传入空字符串,返回当前时区。

    IANA名称参考:https://en.wikipedia.org/wiki/List_of_tz_database_time_zones

    tz.UTC

    表示协调世界时(UTC)时区。

    from dateutil import tz
    import datetime
    
    # <tz.local>
    now = datetime.datetime.now(tz=tz.tzlocal())
    now           # datetime.datetime(2024, 3, 12, 9, 55, 52, 94805, tzinfo=tzlocal())
    now.tzname()  # '中国标准时间'
    
    
    # <tz.gettz>
    London_tz = tz.gettz("Europe/London")
    now = datetime.datetime.now(tz=London_tz)
    now           
    # datetime.datetime(2024, 3, 12, 1, 55, 52, 100788, tzinfo=tzfile('Europe/Belfast'))
    # Windows系统,datetime.tzinfo 属性赋值 tzfile('Europe/Belfast')
    now.tzname()  
    # 'GMT'
    # 此输出在Windows、macOS和Linux上是相同的。
    
    
    # <tz.UTC>
    now = datetime.datetime.now(tz=tz.UTC)
    now              # datetime.datetime(2024, 3, 12, 1, 55, 52, 104777, tzinfo=tzutc())
    now.ctime()      # 'Tue Mar 12 01:55:52 2024'
    now.isoformat()  # '2024-03-12T01:55:52.104777+00:00'
    
    

    1.3 dateutil.parser 常用方法

    dateutil.parser 提供了一个通用的 date/time 字符串解析器,能够解析大多数已知格式表示的日期(和/或)时间。

    dateutil.parser.parse(timestr, parserinfo=None, **kwargs)

    解析时间戳(可包含时区信息),返回datetime对象。

    代码示例:

    from dateutil import parser
    
    DATE1 = parser.parse('2024/01/03')
    DATE1  # datetime.datetime(2024, 1, 3, 0, 0)
    
    DATE2 = parser.parse('2024/01/03', parser.parserinfo(dayfirst=True))
    DATE2  # datetime.datetime(2024, 3, 1, 0, 0)

    参数说明:

    parserinfo

    说明:

    1)默认情况:dayfirst 和 yearfirst 的默认值都为Flase,即默认格式为月日年,MDY。第一个数字表示月;

    2)如果 yearfirst 为 True,dayfirst 保持默认值 False,对应 YMD;

    3)如果 yearfirst 为 False,dayfirst 保持默认值 True,对应 DMY;

         

    kwargs

    此处不做展开,下图展示部分内容,更多信息请参考官方文档​​​​​​

    官方文档:parser — dateutil 3.9.0 documentation

         

    对于省略的日期/时间元素,应用以下规则

    1)如果不指定 AM 或 PM,则假定为24小时制,但如果指定 AM 或 PM,则必须指定12小时制中的一个小时(0 <= hour <= 12),不指定会报错(datetime 对象默认使用24小时制)。

    2)如果省略时区,返回简单型 datetime 对象。

    3)如果缺少任何其他元素,使用 parser 的 default 参数(datetime 对象)中的对应信息,如果天数超过每月的有效天数,则该值将回落到月末。

    1.4 dateutil.relativedelt

    relativedelta 1)可将 datetime 中某时间元素的值替换为指定值;2)表示时间间隔。

    有两种不同的方法来构建 relativedelta 实例。

    1)传递两个 date/datetime 对象

    2)传递以下任意数量的关键字参数

    关键字参数有绝对形式和相对形式两种。单数是绝对的,复数是相对的。按顺序 Year -> Month -> Day -> Hours -> Minutes -> Seconds -> Microseconds,每个参数首先应用绝对形式(将每个属性设置为该值),然后应用相对形式(向属性加或减对应值)。最用,应用weekday。

    代码示例:

    from dateutil.relativedelta import relativedelta
    
    # 方法二:传参数
    dt1 = datetime.now()
    delta = relativedelta(day=1, days=1)
    dt1
    # datetime.datetime(2024, 3, 27, 11, 59, 40, 611335)
    dt2 = dt1 + delta
    # datetime.datetime(2024, 3, 2, 11, 59, 40, 611335)
    
    # 方法一:传入两个 datetime 实例
    relativedelta(dt1, dt2)
    # relativedelta(days=+25)
    relativedelta(date(2000, 1, 1), date(2003, 4, 1))
    # relativedelta(years=-3, months=-3)
    from dateutil.relativedelta import relativedelta, FR
    from datetime import date
    
    NOW = datetime.now()
    NOW
    # datetime.datetime(2024, 3, 27, 12, 13, 0, 320883)
    
    NOW+relativedelta(months=+1)                      
    # Next month
    # datetime.datetime(2024, 4, 27, 12, 13, 0, 320883)
    NOW+relativedelta(months=+1, weeks=+1)            
    # Next month, plus one week.
    # datetime.datetime(2024, 5, 4, 12, 13, 0, 320883)
    NOW+relativedelta(months=+1, weeks=+1, hour=10) 
    # Next month, plus one week, at 10am.
    # datetime.datetime(2024, 5, 4, 10, 13, 0, 320883)
    
    # 本周五和上周五
    NOW+relativedelta(weekday=FR)
    # datetime.datetime(2024, 3, 29, 12, 13, 0, 320883)
    NOW+relativedelta(weekday=FR(-1))
    # datetime.datetime(2024, 3, 22, 12, 13, 0, 320883)
    NOW+relativedelta(weekday=FR(+1))
    # datetime.datetime(2024, 3, 29, 12, 13, 0, 320883)
    date(2024,3,29)+relativedelta(weekday=FR)
    # datetime.date(2024, 3, 29)
    date(2024,3,29)+relativedelta(weekday=FR(+1))
    # datetime.date(2024, 3, 29)
    from dateutil.relativedelta import relativedelta
    from datetime import date
    
    # adding one month will never cross the month boundary.
    date(2001,1,27)+relativedelta(months=+1)
    # datetime.date(2001, 2, 27)
    date(2001,1,31)+relativedelta(months=+1)
    # datetime.date(2001, 2, 28)
    date(2001,1,31)+relativedelta(months=+2)
    # datetime.date(2001, 3, 31)
    date(2003,1,30)+relativedelta(months=+1)
    # datetime.date(2003, 2, 28)
    date(2003,5,31)+relativedelta(months=-1)
    # datetime.date(2003, 4, 30)
    
    # The logic for years is the same, even on leap years.
    date(2000,2,29)+relativedelta(years=+1)
    # datetime.date(2001, 2, 28)
    
    date(2023, 1, 1)+relativedelta(yearday=260)
    # datetime.date(2024, 9, 17)
    date(2023, 3, 1)+relativedelta(yearday=260)
    # datetime.date(2024, 9, 17)
    date(2000, 1, 1)+relativedelta(yearday=260)
    # datetime.date(2000, 9, 16)
    date(2000, 1, 1)+relativedelta(nlyearday=260)
    # datetime.date(2000, 9, 17)

    2. 问题描述

    巴黎奥组委2024年3月8日向外界公布,备受瞩目的奥运会开幕式将于当地时间7月26日晚7点30分开始。届时巴黎处于夏令时,与北京的时差为6小时,这意味着国内的观众将在27日凌晨1点30分欣赏到这场开启于塞纳河的奥运开幕式。

    设计一个倒计时器,随时随地计算距离奥运会开幕式的时间。

    3. 代码

    代码版本1:计算时间差

    from datetime import datetime
    
    PYCON_DATE = datetime(year=2024, month=7, day=26, hour=19, minute=30)
    countdown = PYCON_DATE - datetime.now()
    print(f"Countdown to 2024 Summer Olympics: {countdown}")
    
    # Countdown to 2024 Summer Olympics: 137 days, 9:37:27.209829

    注:当前使用减号计算时间差,输出结果的最大单位是天。

    代码版本2(优化点:添加时区信息)

    from dateutil import parser, tz
    import datetime
    
    DATE = parser.parse('Jul 26, 2024 19:30:00')
    DATE = DATE.replace(tzinfo=tz.gettz("Europe/Paris"))
    now = datetime.datetime.now(tz=tz.tzlocal())
    
    countdown = DATE - now
    print(f"Countdown to 2024 Summer Olympics: {countdown}")
    # Countdown to 2024 Summer Olympics: 136 days, 13:35:18.014367

    代码版本3(优化点:时间差)

    from dateutil import parser, tz, relativedelta
    import datetime
    
    DATE = parser.parse('Jul 26, 2024 19:30:00')
    DATE = DATE.replace(tzinfo=tz.gettz("Europe/Paris"))
    now = datetime.datetime.now(tz=tz.tzlocal())
    
    countdown = relativedelta.relativedelta(DATE, now)
    print(f"Countdown to 2024 Summer Olympics: {countdown}")
    # Countdown to 2024 Summer Olympics: relativedelta(months=+4, days=+14, hours=+13, minutes=+13, seconds=+59, microseconds=+605048)

    注:使用 dateutil.relativedelta.relativedelta,时间最小的单位是月。

    代码版本4(优化点:美化输出)

    from dateutil import parser, tz, relativedelta
    import datetime
    
    DATE = parser.parse('Jul 26, 2024 19:30:00')
    DATE = DATE.replace(tzinfo=tz.gettz("Europe/Paris"))
    now = datetime.datetime.now(tz=tz.tzlocal())
    
    def time_amount(time_unit: str, countdown: relativedelta) -> str:
        t = getattr(countdown, time_unit)
        return f"{t} {time_unit}" if t != 0 else ""
    
    countdown = relativedelta.relativedelta(DATE, now)
    time_units = ["years", "months", "days", "hours", "minutes", "seconds"]
    output = (t for tu in time_units if (t := time_amount(tu, countdown)))
    print("Countdown to PyCon US 2021:", ", ".join(output))
    
    # Countdown to PyCon US 2021: 3 months, 29 days, 9 hours, 48 minutes, 25 seconds

    参考链接

    Using Python datetime to Work With Dates and Times – Real Python

    作者:abigailqin2017

    物联沃分享整理
    物联沃-IOTWORD物联网 » Python 标准库详解:datetime 模块解析

    发表回复