史上最详解Python日期和时间处理(一)

好吧,我承认这有标题党的嫌疑,不过看了那么多文章,的确没有找到一篇让我满意的关于日期和时间处理的详解文章,于是决心自己动手亲写一篇,希望能对得起这个霸气的标题。言归正传,在Python编程中,日期和时间处理是非常繁琐的一块,不仅概念众多,且有很多不同的module, 尤其涉及时区处理的时候会将问题进一步复杂化。本文将对Python在日期和时间处理上进行一步步详细讲解,总共会分为上下两篇,其中时区处理是较为棘手的一块内容,单独拿出来作为下篇。此上篇将主要讲解时间和日期处理中的基本概念,和常用的场景。

本篇目录如下:

时间和日期的表示

在Python中表示日期和时间,最基本的有三种形式:

  • 时间、日期对象(Object)
  • 时间戳(Timestamp)
  • 字符串(String)

在这三种基本形式中又会衍生出一些其它的概念,下面来分别讲解下:

先看张脑图来理清它们之间的关系 1.1-python日期与时间的表示

时间、日期对象

Python中用于表示日期和时间的对象有很多种,在详细讲解这些对象之前先要明确一个概念,即这些对象都分为两种:一种是"原始的(naive)",另一种是"有知的(aware)"。区别在于原始的时间没有时区概念,只是单纯的表示一个日期和时间,而有知的时间会包含一些额外信息,如时区,是否夏令时等信息,而这些信息会通过tzinfo子类来进行封装,我们将在(下)中详细讲解。

  • datetime对象:这个是最常用的日期时间对象,可以即表示日期又表示时间。datetime对象可以直接用于日期和时间相关的计算(比如计算5天之前的时间)。另外,datetime既可以表示原始时间,也可以表示有知时间,如果要表示有知时间,则在初始化时需要传递tzinfo类型的参数。

    1
    2
    3
    4
    5
    6
    7
    8
    In [16]: import datetime

    In [17]: datetime.datetime.now() # 返回一个datetime对象
    Out[17]: datetime.datetime(2018, 7, 28, 11, 17, 38, 972555)

    # 也可以直接初始化一个datetime对象
    In [20]: datetime.datetime(2018, 7, 5, 10, 20)
    Out[20]: datetime.datetime(2018, 7, 5, 10, 20)

  • time对象:与datetime类似,但只用于表示时间,不表示日期

    1
    2
    3
    # 这里初始化一个datetime.time对象用于表示时间11点28分05秒
    In [19]: datetime.time(11, 28, 5)
    Out[19]: datetime.time(11, 28, 5)

  • date对象:与datetime类似,但只表示日期,不表示时间

    1
    2
    In [23]: datetime.date(2018, 07, 10)
    Out[23]: datetime.date(2018, 7, 10)

  • time tuple: time tuple又叫struct time,是一种用于表示日期和时间的数据结构,此数据结构主要用于time module中的相关函数。time tuple中有9个元素,如下表所示:

    索引(Index) 属性(Attribute) 值(Values)
    0 tm_year(年) 比如2011
    1 tm_mon(月) 1 - 12
    2 tm_mday(日) 1 - 31
    3 tm_hour(时) 0 - 23
    4 tm_min(分) 0 - 59
    5 tm_sec(秒) 0 - 61
    6 tm_wday(weekday) 0 - 6(0表示周日)
    7 tm_yday(一年中的第几天) 1 - 366
    8 tm_isdst(是否是夏令时) 默认为-1

    1
    2
    3
    4
    In [21]: import time

    In [22]: time.localtime() # 返回一个time tuple用于表示当前时间的local time
    Out[22]: time.struct_time(tm_year=2018, tm_mon=7, tm_mday=28, tm_hour=11, tm_min=26, tm_sec=19, tm_wday=5, tm_yday=209, tm_isdst=0)

总结:以上前三个对象(datetime, date, time)均来自datetime module(注意区分datetime module和datetime 对象),而最后一个time tuple主要用于time module中的相关操作。

时间戳

时间戳(Timestamp)是表示当前时间距离元年时间(epoch, 1970年1月1日00:00:00 UTC)的偏移量,这个偏移量在Python中用秒数来计算,但有些编程语言如JavaScript是用毫秒来计算的,需要注意。**另外需要知道时间戳没有时区概念,是不分时区的。**这就是为什么我们在数据库中通常存储的是时间戳,当需要向用户显示时间的时候,再转化为对应时区的时间。

1
2
3
# 获取当前时间的时间戳
In [24]: time.time()
Out[24]: 1532750668.210267

日期时间字符串

通常在需要向用户展示时间的时候,我们都需要将时间戳或者时间对象转化为字符串形式,从而在标准输出中能够打印出相应的时间。我们可以通过将时间戳或者对象进行转化和格式化来得到相应的字符串。需要说明的是,字符串表示时间有一个标准形式称为ISO8601,可以通过专门的函数来获得。

1
2
3
4
5
6
7
8
9
In [29]: dt = datetime.datetime.now()

# 获取ISO8601标准时间字符串
In [30]: dt.isoformat()
Out[30]: '2018-07-28T12:11:33.582380'

# 自定义格式的时间字符串
In [31]: dt.strftime("%Y%m%d-%H:%M:%S")
Out[31]: '20180728-12:11:33'

日期时间处理场景

时间处理主要有以下几个场景:

  • 获取当前时间
  • 获取特定时间:例如获取5天前的时间。
  • 不同时间表示的转化:例如将datetime对象转化为时间字符串
  • 时区处理:这个将在《下篇》中详细讲述

下面分别介绍下这几种处理场景:

获取当前时间

获取当前时间可以通过获取datetime对象、时间戳或time tuple三种方式来获取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 最常用的获取当前日期时间的方法,其有一个tz参数用于设置时区,默认为None,所以最终获取的是一个原始naive时间
In [36]: dt = datetime.datetime.now()

# 注意获取的是当地时间的时间表示,但其本身是一个原始naive时间
In [37]: dt
Out[37]: datetime.datetime(2018, 7, 28, 12, 58, 57, 676468)

# 获取当前时间的UTC时间表示,注意这仍然是一个原始naive时间,也就是用当前的UTC时间4点59分32秒来构造一个naive时间对象,但这个对象并没有包含时区信息
In [38]: utc_now = datetime.datetime.utcnow()

In [39]: utc_now
Out[39]: datetime.datetime(2018, 7, 28, 4, 59, 32, 258087)

# 获取当前时间的时间戳
In [40]: time.time()
Out[40]: 1532753948.563503

# 获取当地时间的time tuple
In [41]: time.localtime()
Out[41]: time.struct_time(tm_year=2018, tm_mon=7, tm_mday=28, tm_hour=13, tm_min=0, tm_sec=2, tm_wday=5, tm_yday=209, tm_isdst=0)

获取特定时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
In [42]: dt
Out[42]: datetime.datetime(2018, 7, 28, 12, 58, 57, 676468)

# 获取两天前的时间
In [43]: dt - datetime.timedelta(days=2)
Out[43]: datetime.datetime(2018, 7, 26, 12, 58, 57, 676468)

# 获取一周前的时间
In [44]: dt - datetime.timedelta(weeks=1)
Out[44]: datetime.datetime(2018, 7, 21, 12, 58, 57, 676468)

# 获取3小时前的时间
In [45]: dt - datetime.timedelta(hours=3)
Out[45]: datetime.datetime(2018, 7, 28, 9, 58, 57, 676468)

通过timedelta基本可以满足我们获取特定时间的需求,但是这里不涉及时区相关的转化。而且只是原始naive时间之间的转化

不同时间表示的转化

先来看一张关系图: 1.2-不同时间表示的关系图

从上图可以看出总共有5对关系,每对关系都是可以双向转化的(除timetuple无法直接转化为datetime,需要先转化为timestamp或者time string),所以总共有9种转化,下面将分别介绍下它们之间是如何互相转化的。

datetime object & timestamp

从时间戳timestamp ==> datetime object

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
In [2]: import time

In [3]: from datetime import datetime

In [4]: ts = time.time()

# 直接通过fromtimestamp就可以获得时间戳对应的datetime对象
In [5]: dt = datetime.fromtimestamp(ts)

In [6]: ts
Out[6]: 1532775234.192805

# 注意这个datetime对象获取的是本地时间的表示,但是仍然是原始naive time
In [7]: dt
Out[7]: datetime.datetime(2018, 7, 28, 18, 53, 54, 192805)

# 获取对应utc的时间表示,仍然是原始naive时间
In [11]: utc_dt = datetime.utcfromtimestamp(ts)
In [12]: utc_dt
Out[12]: datetime.datetime(2018, 7, 28, 10, 53, 54, 192805)

从datetime object ==>时间戳timestamp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 定义一个函数用于返回datetime object对应的时间戳
In [8]: def get_timestamp(datetime_obj):
...: if not isinstance(datetime_obj, datetime):
...: raise ValueError()
...: return (datetime_obj - datetime(1970, 1, 1)).total_seconds()
...:

In [9]: get_timestamp(dt)
Out[9]: 1532804034.192805

# 也可以先转化为time tuple,再通过mktime函数转化为timestamp,但是精度不如上一版高
In [13]: def get_timestamp_v2(datetime_obj):
...: if not isinstance(datetime_obj, datetime):
...: raise ValueError()
...: return time.mktime(datetime_obj.timetuple())
...:

In [14]: get_timestamp_v2(dt)
Out[14]: 1532775234.0

datetime object & time string

datetime object ==> time string 关于格式化的说明可以参考官网文档

1
2
3
4
5
6
7
8
9
10
In [15]: dt
Out[15]: datetime.datetime(2018, 7, 28, 18, 53, 54, 192805)

# 自定义格式
In [16]: dt.strftime("%Y-%m-%dT%H:%M:%S")
Out[16]: '2018-07-28T18:53:54'

# 转化为ISO8601格式字符串的快捷方式
In [17]: dt.isoformat()
Out[17]: '2018-07-28T18:53:54.192805'

time string ==> datetime object

1
2
3
4
5
6
In [18]: time_str = '2018-07-28T18:53:54'

In [19]: dt2 = datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S")

In [20]: dt2
Out[20]: datetime.datetime(2018, 7, 28, 18, 53, 54)

datetime object & time tuple

datetime object ==> time tuple

1
2
In [21]: dt.timetuple()
Out[21]: time.struct_time(tm_year=2018, tm_mon=7, tm_mday=28, tm_hour=18, tm_min=53, tm_sec=54, tm_wday=5, tm_yday=209, tm_isdst=-1)

time tuple ==> datetime object 无法直接转化,需要先转化为time str或者timestamp

time tuple & timestamp

time tuple ==> timestamp

1
2
3
4
5
6
7
8
In [25]: time_tuple
Out[25]: time.struct_time(tm_year=2018, tm_mon=7, tm_mday=28, tm_hour=18, tm_min=53, tm_sec=54, tm_wday=5, tm_yday=209, tm_isdst=-1)

# 注意:该函数精度只能到秒
In [26]: ts = time.mktime(time_tuple)

In [27]: ts
Out[27]: 1532775234.0

timestamp ==> time tuple

1
2
3
4
5
6
7
8
9
10
11
12
13
14
In [27]: ts
Out[27]: 1532775234.0

# 转为本地时间的time tuple表示,
In [29]: tt = time.localtime(ts)

In [30]: tt
Out[30]: time.struct_time(tm_year=2018, tm_mon=7, tm_mday=28, tm_hour=18, tm_min=53, tm_sec=54, tm_wday=5, tm_yday=209, tm_isdst=0)

# 转为UTC时间的time tuple表示
In [31]: utc_tt = time.gmtime(ts)

In [32]: utc_tt
Out[32]: time.struct_time(tm_year=2018, tm_mon=7, tm_mday=28, tm_hour=10, tm_min=53, tm_sec=54, tm_wday=5, tm_yday=209, tm_isdst=0)

time tuple & time string

time tuple ==> time string

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
In [33]: tt
Out[33]: time.struct_time(tm_year=2018, tm_mon=7, tm_mday=28, tm_hour=18, tm_min=53, tm_sec=54, tm_wday=5, tm_yday=209, tm_isdst=0)

# 注意:这里strftime并非time tuple的方法,而是time module下的函数
In [34]: tt.strftime("%Y-%m-%dT%H:%M:%S")
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-34-1be0f512fc77> in <module>()
----> 1 tt.strftime("%Y-%m-%dT%H:%M:%S")

AttributeError: 'time.struct_time' object has no attribute 'strftime'

# 如下是正解
In [35]: time.strftime("%Y-%m-%dT%H:%M:%S", tt)
Out[35]: '2018-07-28T18:53:54'

time string ==> time tuple

1
2
3
4
5
6
7
In [36]: time_str
Out[36]: '2018-07-28T18:53:54'

In [37]: tt = time.strptime(time_str, "%Y-%m-%dT%H:%M:%S")

In [38]: tt
Out[38]: time.struct_time(tm_year=2018, tm_mon=7, tm_mday=28, tm_hour=18, tm_min=53, tm_sec=54, tm_wday=5, tm_yday=209, tm_isdst=-1)

timestamp & time string

无法相互转化,只能通过先转化为datetime object或者time tuple之后才能再转化

References