Python 时间处理标准库:time 与 datetime 模块

Python 中内置了许多和操作时间有关的 API,它们分布在 timedatetime 等标准库中,用法繁多且容易混淆,本文将力求清晰地阐述这些 API 的关键部分和区别,帮助你了解并掌握其用法。

下文将分别介绍每个模块的主要目的、核心对象、常用方法以及用途,并在最后做分析对比,如果已经了解这些细节可以直接跳转到结尾的总结对比部分。

另外本文将避免涉及字符串格式化、时区、冬夏令时等更复杂深入的话题。

time 模块

概括来说,time 模块通过系统底层的计时器获取自 epoch 以来经过的总秒数(可能为浮点数),即我们常说的 POSIX 时间戳(timestamp)。它的用法较为低阶,适合用做精确计时。对 Unix 系统来说, epoch1970年1月1日 00:00:00(UTC),因此该模块也可以将时间戳转换为具体的日期时间,但表示日期时间的对象结构非常简单,不适合进行复杂的操作和表示。

核心对象

time 模块的 API 中只有一个类: time.struct_time

struct_time 是一个转换 epoch 以来经过秒数得到的结构化的时间对象,它提供了类似 namedtuple 的 API,可以通过下标或属性名称获取对象的年月日时分秒等属性。调用 gmtime()localtime()strptime() 等方法可得到 struct_time 实例。

1
2
3
4
5
6
7
>>> st = time.localtime()
>>> st
time.struct_time(tm_year=2021, tm_mon=4, tm_mday=29, tm_hour=12, tm_min=39, tm_sec=14, tm_wday=3, tm_yday=119, tm_isdst=0)
>>> st.tm_mon
4
>>> st[1]
4

从示例中可以看到,struct_time 实例实质是一个数字组成的类元祖序列,该模块中接收 struct_time 实例作为参数的函数都可以直接接收一个同样长度的元祖。它只能简单的记录通过换算时间戳得到的年月日时分等属性,没有提供支持额外操作的其他方法,因此实践中的用途非常有限。

1
2
3
4
5
6
7
>>> st1 = time.localtime()
>>> st2 = time.localtime()
>>> st2 - st1
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    st2 - st1
TypeError: unsupported operand type(s) for -: 'time.struct_time' and 'time.struct_time'

常见用途与函数

  1. 计时

    • time.time() 以浮点数的形式返回自 epoch 以来经过的时间秒数。常见用法是通过计算两次调用之间的间隔来得出程序执行时间。

      1
      2
      
      >>> time.time()
      1619665423.683973
      
    • time.sleep(seconds) 暂停调用线程的执行,暂停时间为给定的秒数。经常用于测试模拟,实际的暂停时间可能超出给定秒数。

    • time.perf_counter() 是计算较短时间间隔的更好方法,结果更为精确,在计算执行时间时可替代上述的 time.time()

      1
      2
      3
      4
      
      >>> start = time.perf_counter()
      >>> end = time.perf_counter()
      >>> end - start
      2.731515233999744
      
  2. struct_time 和时间戳之间进行转换

    • time.gmtime([secs]) 将给定秒数转换为一个 UTC 时区 struct_time 对象,若未提供秒数将使用 time.time() 得到的返回值。

      1
      2
      3
      4
      5
      
      >>> now = time.time()
      >>> time.gmtime(now)
      time.struct_time(tm_year=2021, tm_mon=4, tm_mday=29, tm_hour=4, tm_min=51, tm_sec=54, tm_wday=3, tm_yday=119, tm_isdst=0)
      >>> time.gmtime()
      time.struct_time(tm_year=2021, tm_mon=4, tm_mday=29, tm_hour=4, tm_min=51, tm_sec=56, tm_wday=3, tm_yday=119, tm_isdst=0)
      
    • time.localtime([secs]) 将给定秒数转换为一个本地时区的 struct_time 对象,若未提供秒数将使用 time.time() 得到的返回值。

      1
      2
      
      >>> time.localtime()
      time.struct_time(tm_year=2021, tm_mon=4, tm_mday=29, tm_hour=12, tm_min=53, tm_sec=38, tm_wday=3, tm_yday=119, tm_isdst=0)
      
    • time.mktime(t) 将一个 struct_time 对象转换为秒数,该对象将被当做本地时区处理,效果刚好与 time.localtime([secs]) 相反。

      1
      2
      
      >>> time.mktime(time.localtime())
      1619672313.0
      
  3. struct_time 和字符串之间进行转换

    • time.strftime(format[, t]) 将一个 struct_time 对象按指定的 format 编码格式化为字符串,t 的默认值是 time.localtime() 的返回值。

      1
      2
      
      >>> time.strftime('%H:%M:%S')
      '13:10:37'
      
    • time.strptime(string[, format]) 将一个字符串按指定的 format 编码解析为 struct_time 对象,format 的默认值为 "%a %b %d %H:%M:%S %Y"

      1
      2
      3
      
      >>> time.strptime("30 Nov 00", "%d %b %y")   
      time.struct_time(tm_year=2000, tm_mon=11, tm_mday=30, tm_hour=0, tm_min=0,
                       tm_sec=0, tm_wday=3, tm_yday=335, tm_isdst=-1)
      

      如上示例,解析时未提供的时间单位将使用默认值填充。

datetime 模块

datetime 模块支持日期和时间的运算,但实现的重点是为输出格式化和操作提供高效的属性提取。

datetime 模块提供了一些用于操作日期和时间的类。该模块的绝大部分功能都围绕着以下 4 个类(以及另外两个关于时区的类)的方法和属性来实现。一个容易让人混淆的点是,虽然它们全都是 Python 类,但在命名中并未遵循首字母大写的惯例,在导入时看上去就像是 datetime 下的子包或者子模块。

我们将简要介绍每一个类常用的实例构造方式、支持的操作符、实例方法以及实例属性。

date

表示日期类型。

实例构造方式

  • 实例化 date 类,需要传入日期对应的年月日参数。

    1
    2
    
    >>> date(2021, 4, 29)
    datetime.date(2021, 4, 29)
    
  • 调用 date.fromtimestamp(timestamp) 类方法,需要传入的参数为通过 time 模块获取的 epoch 以来秒数(即时间戳)。

    1
    2
    
    >>> date.fromtimestamp(time.time())
    datetime.date(2021, 4, 29)
    
  • 调用 date.today() 类方法,实质是以当前时间戳作为参数调用 date.fromtimestamp() 类方法。

  • 调用 date.fromisoformat(date_string) 类方法,这是一种较为直观的创建方法:

    1
    2
    
    >>> date.fromisoformat('2021-04-29')
    datetime.date(2021, 4, 29)
    

支持的操作符

  • 支持与另一 date 对象进行 ==<> 等比较操作。
  • 支持与 timedelta 对象进行加减操作,结果依然为 date 对象。
  • 支持与另一 date 对象进行相减操作,得到 timedelta 对象。
  • 支持哈希。

实例方法

  • strftime(self, fmt) 按指定的 fmt 格式化编码返回当前 date 对象的字符串表示。

    1
    2
    3
    
    >>> d1 = date.today()
    >>> d1.strftime('%Y-%m-%d')
    '2021-04-29'
    
  • isoformat(self) 返回当前 date 对象的 iso 字符串表示。

    1
    2
    
    >>> d1.isoformat()
    '2021-04-29'
    
  • timetuple(self) 将当前 date 对象转换成 time 模块的 struct_time 对象并返回,时分秒等属性使用默认值填充。

    1
    2
    
    >>> d1.timetuple()
    time.struct_time(tm_year=2021, tm_mon=4, tm_mday=29, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=3, tm_yday=119, tm_isdst=-1)
    
  • replace(self, year=None, month=None, day=None) 返回替换当前 date 对象的某一属性后的副本。

    1
    2
    
    >>> d1.replace(day=30)
    datetime.date(2021, 4, 30)
    
  • weekday(self) 返回当前 date 对象所属的星期,从 0 开始。

    1
    2
    
    >>> d1.weekday()
    3
    

实例属性

  • year
  • month
  • day

time

表示时间(时分秒)类型。

实例构造方式

time 不支持通过时间戳构造实例。

  • 实例化 time 类并传入对应参数。需要传入时间对应的时分秒微秒等参数,参数均有取值范围且默认值为 0。

    1
    2
    
    >>> date(2021, 4, 29)
    datetime.date(2021, 4, 29)
    
  • 通过调用fromisoformat(cls, time_string) 类方法,从 iso 字符串中创建一个实例:

    1
    2
    
    >>> time.fromisoformat('17:32:10')
    datetime.time(17, 32, 10)
    

支持的操作符

  • 支持与另一 time 对象进行 ==<> 等比较操作。
  • 支持哈希。

time 对象不支持与 timetimedelta 进行加减操作,如果我们想计算两个 time 对象之间的时间间隔,可以使用 datetime.combine() 将它们处理为日期相同的 datetime 对象再进行计算:

1
2
>>> datetime.combine(date.today(), t2) - datetime.combine(date.today(), t1) 
datetime.timedelta(seconds=4440)

实例方法

  • strftime(self, fmt) 按指定的 fmt 格式化编码返回当前 time 对象的字符串表示。

    1
    2
    3
    
    >>> t = time.fromisoformat('17:32:10')
    >>> t.strftime('%Hh %Mm %Ss')
    '17h 32m 10s'
    
  • isoformat(self) 返回当前 time 对象的 iso 字符串表示。

    1
    2
    3
    
    >>> t = time(hour=17, minute=27, second=55)
    >>> t.isoformat()
    '17:27:55'
    
  • replace(self,hour=None,minute=None,second=None,microsecond=None, tzinfo=True, *,fold=None) 返回替换当前 time 对象的某一属性后的副本。

    1
    2
    
    >>> t.replace(hour=20)
    datetime.time(20, 27, 55)
    

实例属性

  • hour
  • minute
  • second
  • 以及 micorsecondtzinfofold 等属性

datetime

表示包含日期时分的时间类型,是 date 的子类,因此也继承了 date 的所有属性和方法。它的实例还可以视作 datetime 实例的组合体,因此同时具备了两种对象的大部分方法和属性。

下文的介绍中不包含从 date 继承的方法和属性。

实例构造方式

  • 实例化 datetime 类并传入对应参数,接收参数为 datetime 实例化参数的组合,其中日期参数为必填参数,其他参数有默认值。

    1
    2
    
    >>> datetime(year=2021, month=4, day=29)
    datetime.datetime(2021, 4, 29, 0, 0)
    
  • 调用 datetime.now()datetime.utcnow() 类方法,区别为实例的对应时区不同。

    1
    2
    3
    4
    
    >>> datetime.now()
    datetime.datetime(2021, 4, 29, 16, 4, 53, 648203)
    >>> datetime.utcnow()
    datetime.datetime(2021, 4, 29, 8, 5, 1, 671572)
    
  • 调用 datetime.fromtimestamp(timestamp)datetime.utcfromtimestamp(timestamp) 类方法并传入时间戳,区别为实例的对应时区不同。

    1
    2
    3
    4
    5
    
    >>> import time
    >>> datetime.utcfromtimestamp(time.time())
    datetime.datetime(2021, 4, 29, 8, 6, 4, 798136)
    >>> datetime.fromtimestamp(time.time())
    datetime.datetime(2021, 4, 29, 16, 6, 26, 251251)
    
  • 通过调用 datetime.fromisoformat(time_string) 类方法,从 iso 字符串中创建一个实例:

    1
    2
    
    >>> datetime.fromisoformat('2021-04-29 16:09:32')
    datetime.datetime(2021, 4, 29, 16, 9, 32)
    
  • 通过调用 datetime.combine(date, time) 类方法,从 date 实例和 time 实例中创建一个新的 datetime 实例。

    1
    2
    
    >>> datetime.combine(date.today(), time(16, 12))
    datetime.datetime(2021, 4, 29, 16, 12)
    
  • 通过调用 datetime.strptime(date_string, format) 类方法,解析格式化字符串并创建一个新的实例。

支持的操作符

  • datetime 支持与 date 进行相等比较,但结果一定为 False ,除此之外只支持与另一 datetime 对象执行 ==<> 等比较操作。
  • 支持与 timedelta 相加,结果为 datetime;支持与 timedelta 对象进行加减,结果依然为 datetime 对象,与另一 datetime 对象进行相减,得到 timedelta 对象。
  • 同样支持哈希。

实例方法

除了从 date 继承的 strftime()timetuple()isoformat()replace()等方法外,还拥有以下方法:

  • timestamp(self) 返回一个浮点数格式的 POSIX 时间戳。

    1
    2
    3
    
    >>> dt = datetime.now()
    >>> dt.timestamp()
    1619685580.762657
    
  • date(self) 返回一个代表日期部分的 date 对象。

    1
    2
    
    >>> dt.date()
    datetime.date(2021, 4, 29)
    
  • time(self) 返回一个代表时分部分的 time 对象。

    1
    2
    
    >>> dt.time()
    datetime.time(16, 39, 40, 762657)
    

实例属性

同时具有datetime 实例的所有属性。

timedelta

表示两个 datetime 对象之间的差异。

实例构造方式

  • 实例化 timedelta 类并传入对应参数,接收参数与 datetime 类基本相同但不包括年,默认值均为 0。

    1
    2
    
    >>> timedelta(days=2)
    datetime.timedelta(days=2)
    
  • 对两个 datetime 执行相减:

    1
    2
    3
    4
    
    >>> dt1 = datetime.now()
    >>> dt2 = datetime.now()
    >>> dt2 -dt1
    datetime.timedelta(seconds=4, microseconds=476390)
    

支持的操作符

  • 只支持与另一 timedelta 进行比较,进行 ==<> 等比较操作。

  • timedelta 对象支持支持加减操作,datetimetimedelta 相加或相减仍然返回 datetime

  • timedelta 还支持乘除模除等操作符。

  • 支持哈希。

  • timedelta 是有符号的,支持 abs() 函数,可返回两个 datetime 之间的绝对间隔。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    >>> dt1 = datetime.now()
    >>> dt2 = datetime.now()
    >>> td = dt1 - dt2
    >>> td
    datetime.timedelta(days=-1, seconds=86395, microseconds=573188)
    >>> td.total_seconds()
    -4.426812
    >>> abs(td)
    datetime.timedelta(seconds=4, microseconds=426812)
    

实例方法

  • total_seconds(self) 返回该时间间隔的所有秒数。
1
2
3
>>> d = timedelta(minutes=3, seconds=35)
>>> d.total_seconds()
215.0

实例属性

timedelta 只通过 dayssecondsmicroseconds 这 3 种单位进行组合来保存时间间隔,可通过对应属性获取数值。

1
2
3
4
5
6
7
8
>>> d1 = timedelta(minutes=3, seconds=35)
>>> d1
datetime.timedelta(days=0, seconds=215, microseconds=0)
>>> d2 = timedelta(days=1)
>>> d2
datetime.timedelta(days=1)
>>> d2.seconds
0

总结对比

timedatetime 模块的区别:

  • time 模块,获取系统时间戳,主要用于计时或表示某一时间点,可以通过数值元祖表示结构化的日期时间,但不支持进一步的转换或操作。
  • datetime 模块,基于时间戳构建高阶的日期、时间、间隔等对象,支持丰富的转换方式和操作。

datetime 模块中不同对象的区别:

  • date 只表示日期。支持与 datetimedelta 进行加减操作.
  • time 只表示时分。不支持与 timetimedelta 进行加减操作,计算间隔需要先转换成 datetime 对象。
  • datetime 同时表示日期和时分的时间对象。同时具备 datetime 对象的行为和属性,可以从中解析出单独的 datetime 对象。
  • timedelta 表示两个时间之间的间隔。只通过 dayssecondsmicroseconds 这 3 种单位来表示。

字符串格式化与解析

字符串格式化与解析:

  • time.struct_timedatetime.datedatetime.timedatetime.datetime 等对象都可以通过 strftime() (string format)实例方法或函数转换为指定格式的字符串。

  • 特定格式的字符串仅可以通过 strptime()(string parse)类方法或函数直接转换为time.struct_timedatetime.datetime 对象。

ISO 格式字符串格式化与解析:

  • datetime.datedatetime.timedatetime.datetime 等对象都可以通过 isoformat() 实例方法转换为 ISO 8601 格式的字符串。

  • ISO 8601 格式的字符串可以通过 fromisoformat() 类方法直接转换为datetime.datedatetime.timedatetime.datetime 对象。

图表

用一张时序图总结上文内容:

datetime-sequence

  • [ts] 表示该参数具有默认值是可选的。
  • 请注意区分图中的实例方法、类方法以及模块函数:
    • 名称中以 time. 开头的均为 time 模块的函数
    • 名称中以 obj. 开头的均为 datetimedatetime 对象的实例方法
    • 其余名称的函数均为类方法
updatedupdated2021-09-162021-09-16