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 提供了三个类:
- datetime.date:一个理想化的日期,它假设公历无限地延伸到未来和过去。该对象将年、月和日存储为属性。
- datetime.time:一个理想化的时间,假设每天有86400秒,没有闰秒。该对象存储时、分、秒、微秒和 tzinfo (时区信息)。
- datetime.datetime:是 datetime.date 和 datetime.time 的结合,具有这两个类的所有属性。
3. 知识点
感知型对象和简单型对象(Aware and Naive Objects)
datetime 对象可以根据是否包含时区信息分为“感知型”和“简单型”两类。
- 感知型对象:具有政治性时间调整信息(如时区和夏令时),能够定位自身相对于其他感知型对象的精确时间点。 感知型对象是一个没有解释空间的固定时间点。对于要求感知型对象的应用程序,datetime 和 time 对象具有一个可选的时区信息属性 tzinfo,它接受抽象类 tzinfo 的子类的一个实例。 tzinfo 对象会捕获与 UTC 时间的差值、时区名称以及夏令时是否生效等信息。
感知型 datetime 实例可以明确地将自己与其他感知型 datetime 实例进行比较,并且在进行算术运算时返回正确的时间差。
- 简单型对象:没有足够多的信息,不能定位自身相对于其他 datetime 对象的时间点。 一个简单型对象所代表的是世界标准时间(UTC)、当地时间还是某个其他时区的时间,完全取决于具体程序,就像一个特定数字所代表的是米、英里还是质量完全取决于具体程序一样。 简单型对象更易于理解和使用,代价则是忽略了某些现实性考量。
- date 类型的对象都是简单型的。
- time 或 datetime 类型的对象可以是感知型或者简单型,对于一个x,以下两个条件同时成立时,x是感知型的:(1)x.tzinfo 不为 None(2)x.tzinfo.utcoffset() 不返回 None。
- 感知型和简单型之间的区别不适用于 timedelta 对象。
- 简单型 datetime 对象会被许多 datetime 方法当作本地时间来处理,如果你有一个表示 UTC 的简单型 datetime,请使用 datetime.replace(tzinfo=timezone.utc) 将其改为感知型。
4. datetime 类
4.1 构造函数
- hour 为24小时制。
- 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
(1)输出结果具有与输入的 datetime 相同的 tzinfo 属性,并且操作完成后 datetime2 - datetime1 == timedelta。
(2)即使输入的是一个感知型对象,该方法也不会进行时区调整。
datetime2 = datetime1 - timedelta
如果 timedelta.days > 0 则在时间线上后退,如果 timedelta.days
计算时间差
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
简单型和感知型对象之间,以及 datetime 对象与非 datetime 实例的 date 对象之间的顺序比较会引发 TypeError。
5. date 类
类属性、类方法、实例属性、实例方法与 datetime 类似,详细内容参考官方文档。
6. time 类
类属性、类方法、实例属性、实例方法与 datetime 类似,详细内容参考官方文档。
7. timedelta类
timedelta 对象表示一段持续的时间,即两个 datetime 或 date 实例之间的差值。
7.1 构造函数
1)所有参数都是可选的,且默认为 0。 这些参数可以是整数,也可以是浮点数;可以是正数也可以是负数。
2)尽管构造函数中有很多时间单位,实际上,只有 days, seconds 和 microseconds 会存储在内部:
-999999999
- 简单型对象:没有足够多的信息,不能定位自身相对于其他 datetime 对象的时间点。 一个简单型对象所代表的是世界标准时间(UTC)、当地时间还是某个其他时区的时间,完全取决于具体程序,就像一个特定数字所代表的是米、英里还是质量完全取决于具体程序一样。 简单型对象更易于理解和使用,代价则是忽略了某些现实性考量。