python中datetime模块详解

575 查看


timedelta

timedelta的实例化

一个timedalta对象代表了一个时间差,当两个datedatetime进行相减操作时会返回一个timedelta对象,或者,我们也可以手动对其进行实例化,其构造函数的原型如下:

class datetime.timedelta([days[, seconds[, microseconds[, milliseconds[, minutes[, hours[, weeks]]]]]]])

其中,所有的参数都是可选的,并且默认为0,一般情况下,我们常用的是其中三个参数dayssecondsmicroseconds,如果我们传递了其他的几个参数值,python会帮助我们自动转换成上面三个参数,转换的规则是:

  • 1 millisecond(毫秒) 转换成 1000 microseconds(微秒)
  • 1 minute 转换成 60 seconds
  • 1 hour 转换成 3600 seconds
  • 1 week转换成 7 days

如果我们在实例化的时候直接传递的是上面三个参数值,那么也要注意下它们的取值范围:

  • 0 <= microseconds < 1000000
  • 0 <= seconds < 3600*24 (一天的秒数)
  • -999999999 <= days <= 999999999

那么,如果我们在传递这三个参数的时候超出了这个范围会有什么问题吗,答案是不一定,例如:

>>> tmp = datetime.timedelta(seconds=86400)
>>> tmp.days
1
>>> tmp.seconds
0

可以看到,如果超过范围,python是会帮我们自动转换的,但是如果days参数超出范围会有什么结果呢?

>>> ee = datetime.timedelta(days=1000000000)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OverflowError: days=1000000000; must have magnitude <= 999999999

所以,只有days是不能超出范围的,否则会抛出OverflowError异常。

在实例化的时候,参数不仅仅可以是整数,也可以是浮点数、正数或者负数,当参数为负数的时候,要特别注意,因为生成的样式也许与我们设想的不太一致

>>> tmp = datetime.timedelta(microseconds=-1)
>>> tmp.days, tmp.seconds, tmp.microseconds
(-1, 86399, 999999)

所以,我们传递参数的时候,尽量避免传递负数的情况,同样,我们也应该极力避免传递的参数为浮点数,我们在使用的时候一般以秒作为单位就能满足99%的需求了。

timedelta的运算

+ 操作

>>> t1 = datetime.timedelta(seconds=60)
>>> t2 = datetime.timedelta(seconds=30)
>>> t3 = t1 + t2
>>> t3.seconds
90

- 操作

>>> t4 = t1 -t2
>>> t4.seconds
30

* 操作

>>> t5 = t1 * 2
>>> t5
datetime.timedelta(0, 120)
>>> t6 = t1 * 0
>>> t6
datetime.timedelta(0)

///操作

>>> t7 = t1 / 3
>>> t7
datetime.timedelta(0, 20)
>>> t7 = t1 / 7
>>> t7
datetime.timedelta(0, 8, 571428)
>>> t7 = t1 // 7
>>> t7
datetime.timedelta(0, 8, 571428)

注意这里的被除数不能是0,否则会抛出ZeroDivisionError

比较操作

>>> t1 > t2
True
>>> t1 < t2
False
>>> t1 == t2
False
>>> t1 == 60
False
>>> t1 != 60
True
>>> t1 > 60
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't compare datetime.timedelta to int

可以看到,两个timedelta对象可以直接进行比较操作,而一个timedelta对象与一个非timedelta对象进行==!=操作时总是返回False,而进行><操作则会抛出TypeError

其他操作

>>> +t1
datetime.timedelta(0, 60)
>>> -t1
datetime.timedelta(-1, 86340)

// 返回的格式为[D day[s], ][H]H:MM:SS[.UUUUUU]
>>> str(t1)
'0:01:00'
>>> t1
datetime.timedelta(0, 60)
>>> str(-t1)
'-1 day, 23:59:00'

>>> repr(t1)
'datetime.timedelta(0, 60)'

其他

在2.7版本后,新增了一个方法timedelta.total_seconds()用于计算秒数,它等价于(td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6

>>> t1
datetime.timedelta(0, 60)
>>> t1.total_seconds()
60.0

date

一个date对象代表使用年、月、日表示的时间,我们可以只用年、月、日三个值直接构造一个date对象,且这三个参数缺一不可,它们的取值范围如下

  • MINYEAR <= year <= MAXYEAR
  • 1 <= month <= 12
  • 1 <= day <= 给定年月的天数

如果我们传递的参数超出这个范围,将会抛出ValueError异常。除了手动传入年、月、日来构造date对象外,系统还提供了静态方法,我们可以使用这些静态方法来方便的得到一个date对象

  • date.today()
    返回当前的本地时间,等价于date.fromtimestamp(time.time())

  • date.fromtimestamp(timestamp)

当得到date对象后,就可以直接访问它的年、月、日属性了

>>> today = date.today()
>>> today
datetime.date(2016, 9, 1)
>>> today.year
2016
>>> today.month
9
>>> today.day
1

运算与比较操作

>>> delta = timedelta(days=7)
>>> future = today + delta
>>> future
datetime.date(2016, 9, 8)
>>> future - delta
datetime.date(2016, 9, 1)
>>> future - today
datetime.timedelta(7)
>>> future > today
True

date可以作为字典的key,并且,所有的date对象都会被认为是True的,也就是 if date 这个判断永远是成立的。

实例方法

  • date.replace(year, month, day)
    >>> d = date(2002, 12, 31)
    >>> d
    datetime.date(2002, 12, 31)
    >>> d.replace(day=26)
    datetime.date(2002, 12, 26)
  • date.weekday()
    返回一周的礼拜几,用int值表示,从0开始
  • date.isoweekday()
    返回一周的礼拜几,用int值表示,从1开始
  • date.isoformat()
    >>> d.isoformat()
    '2002-12-31'

datetime

datetime从字面意思上看是datetime的结合,而实际上也是包含了这两个对象的全部信息,我们可以手动构造datetime对象,也可以使用系统提供的静态方法,当我们手动构造的时候,必须要传入yearmonthday三个参数,他们的取值范围与上面讲到的date对象一致。

我们在处理时间问题时,始终无法回避的是时区问题,在python中,使用datetime.tzinfo来表示时区,但这是一个抽象基类,python也并没帮我们实现任意的时区,因此,我们首先来看下在不涉及时区的时候datetime都有哪些用法,然后我们会创建一个本地时区,也就是东八区,来说明datetime如何与tzinfo结合使用。

datetime对象的创建

我们可以通过指定年月日的形式来手动创建一个datetime实例

>>> cur = datetime(year=2016, month=9, day=2)
>>> cur
datetime.datetime(2016, 9, 2, 0, 0)

也可以直接通过静态方法来获的

>>> datetime.today()
datetime.datetime(2016, 9, 2, 17, 50, 21, 810692)
>>> datetime.now()
datetime.datetime(2016, 9, 2, 17, 50, 34, 89763)

这两个方法都会返回一个本地当前时间,也就是上面的两个写法是等效的,但是要注意的是,now()方法可以传入时区信息,我们稍后再一起讨论。

除了上面的方法,我们还可以通过时间戳来获取一个对象

>>> datetime.fromtimestamp(time.time())
datetime.datetime(2016, 9, 2, 17, 54, 56, 65907)

我们这里同样暂不考虑时区的问题。
除了通过时间戳获取datetime实例外,我们还可以通过一个格式化的时间字符串来获得实例,这个方法同样是不带时区信息的

>>> datetime.strptime('2016-09-02 18:00:00', '%Y-%m-%d %H:%M:%S')
datetime.datetime(2016, 9, 2, 18, 0)

既然一个datetime对象包含了datetime的所有信息,那么能不能通过这两对象来生成一个datetime实例呢,答案是肯定的

>>> d = date(year=2016, month=9, day=2)
>>> d
datetime.date(2016, 9, 2)
>>> t = time(hour=18, minute=14)
>>> t
datetime.time(18, 14)
>>> datetime.combine(d, t)
datetime.datetime(2016, 9, 2, 18, 14)

现在datetime也有了,我们总要在它身上做点什么吧,能做什么呢?

常用方法介绍

有了datetime对象之后,我们就可以获得对应的datetime对象了

>>> today = datetime.today()
>>> today.date()
datetime.date(2016, 9, 2)
>>> today.time()
datetime.time(18, 30, 10, 304536)

那如果我希望得到下个月的今天,该怎么做呢?方法有多种,但是最方便的是直接把月份进行加一操作(不考虑跨年和日期超过月份最大值的情况),这个时候replace函数就派上用场了

>>> next_month = today.replace(month=today.month+1)
>>> next_month
datetime.datetime(2016, 10, 2, 18, 30, 10, 304536)

replace()方法允许我们对datetime的任意字段进行替换,并返回一个新的datetime对象,这个新的对象在其他字段上与原有对象保持一致。

除此之外,还有一个比较常用的方法strftime(),通过它可以格式化成我们希望的样式

>>> next_month.strftime('%Y-%m-%d %H:%M:%S')
'2016-10-02 18:30:10'

还有一种情况,是我们希望将一个datetime对象转成时间戳,很遗憾的是python并没直接提供这个方法,但是提供了一个timetuple()方法,它返回一个time.struct_time对象,通过它我们可以构造出时间戳了

>>> dd = datetime.today()
>>> tt = time.mktime(dd.timetuple())
>>> print int(tt)
1472816083

时区的定义

python中给我们提供了datetime.tzinfo这一抽象的基类,如果我们想使用时区,则必须继承这个类来实现自己的时区定义。我们先来看下都有哪些方法可能需要我们来实现

  • tzinto.utcoffset(self, dt)

这个方法返回本地时间与UTC时间的时差,我们知道,我们国家使用的是东八区,也就是比世界协调时间(UTC)/格林尼治时间(GMT)快8小时的时区,因此我们可以如下实现这个方法

def utcoffset(self, dt):
    return timedelta(hours=8)
  • tzinfo.dst(self, dt)

这个方法主要是考虑到一些采用夏令时的国家,在固定月份来调整时间,而我们国家是没有采用夏令时,所以直接返回0

def dst(self, dt):
    return timedelta(0)

而对于采纳了夏令时的国家,则需要把夏令时考虑进去

def dst(self, dt):
    # Code to set dston and dstoff to the time zone's DST
    # transition times based on the input dt.year, and expressed
    # in standard local time.  Then

    if dston <= dt.replace(tzinfo=None) < dstoff:
        return timedelta(hours=1)
    else:
        return timedelta(0)

对于这些国家,我还要修改utcoffset函数,将夏令时的偏移量考虑进去

def utcoffset(self, dt):
    return timedelta(hours=N) + self.dst(dt)
  • tzinfo.tzname(self, dt)

这个方法用来返回时区的名称,没有太多可说的

一般情况下,我们只要实现上面三个方法就可以了,例如我们可以这样定义一个UTC时区和我们所在的东八区

ZERO = timedelta(0)
HOUR = timedelta(hours=1)

class UTC(tzinfo):
    """UTC"""

    def utcoffset(self, dt):
        return ZERO

    def tzname(self, dt):
        return "UTC"

    def dst(self, dt):
        return ZERO


class GMT8(tzinfo):
    """东八区""

    def utcoffset(self, dt):
        return HOUR * 8

    def tzname(self, dt):
        return 'GMT-8'

    def dst(self, dt):
        return ZERO

除了我们自己来定义时区外,我们还可以使用pytz这个模块,通过easy_install命令直接安装即可。

datetime与时区的结合使用

我们上面已经介绍了在不考虑时区因素的时候,datetime的一些简单用法,接下来看下加入时区后,有哪些不一样的地方,以及我们需要注意的地方。

我们在上面提到了类方法now,在不考虑时区的时候,它的作用和类方法today基本是一致的,我们也可以在使用now的时候传递一个tzinfo,如下的两种方式是等效的

today = datetime.today()
today = today.replace(tzinfo=GMT8())
// 等效于
today = datetime.now(GMT8())

除了now()方法外,系统还提供了utcnow()方法,这个方法没有参数,返回的是UTC的时间,但是要注意的是,这个方法创建的datetime对象同样是不带时区信息的。

到目前为止,我们都还不知道时区到底有什么作用,假设说我们现在有这样的需求:在数据库中记录的时间全部采用UTC时间,而在展示的时候需要转换成本地时间。我们在查库后,一些ORM(例如:SQLAlchemy)会自动帮我们将时间值转化成datetime对象,但这些datetime都是不带时区信息的,如果我们想转化成本地时间则必须要创建本地的时区

today = datetime.utcnow()
today = today.replace(tzinfo=UTC())
print today //2016-09-06 06:47:42.665574+00:00

today = today.astimezone(GMT8())
print today //2016-09-06 14:47:42.665574+08:00

如上所示,系统提供了datetime.astimezone(tz)方法,使用这个方法可以在各个时区之间来回转换时间。这里有两点需要注意:

  • 如果datetime对象本身没有包含时区信息,调用这个方法会抛出ValueError,并提示astimezone() cannot be applied to a naive datetime
  • replace(tzinfo=...)方法只会替换tzinfo的值,并不会更改时分秒等时间信息

最后,还有两个通过时间戳获取datetime的类方法,datetime.fromtimestamp(timestamp[, tz])datetime.utcfromtimestamp(timestamp)utcfromtimestamp得到的是一个UTC时间,并且不带tzinfo信息,除此之外,两者并无太大区别。

到这里,datetime模块的大部分方法就介绍完了,还有一些我们不是很常用的方法,大家可以自行看看文档了解下就好。