目录

  • 前言
  • 使用 python3 解决
  • 使用 dateutil.parser
  • 使用 datetime
  • 解决
  • 后记
  • 参考链接


前言

首先这个问题在 python3 下非常好解决,但是因为一些原因因此项目前期就改成用 python2 来编写了,虽然看起来是一个很小的问题,却花费了我将近一天的时间来解决。

其实这个问题有一个很简单的思路,就是先将特殊格式的时间数据转化成 datetime 对象,然后再调用 time.mktime(datetime.timetuple()) 就能转化成时间戳了,但是这里有一个深坑,在后面会详细说明。

使用 python3 解决

展示一下我要解析的日期格式:

2020-09-21T23:40:45.115479-0700

接下来展示一下代码:

>>> from datetime import datetime
>>> dt = datetime.strptime('2020-09-21T23:40:45.115479-0700','%Y-%m-%dT%H:%M:%S.%f%z')
>>> print(type(dt),dt)
<class 'datetime.datetime'> 2020-09-21 23:40:45.115479-07:00
>>> print(dt.timestamp())
1600756845.115479

如果是在 python3 环境下在 python3.3 以上的版本 datetime.datetime 库就支持 %z 格式和 timestamp() 方法处理,因此可以非常简单的处理带有时区信息的UTC时间数据格式。

但是以上说的这些在 python2 都不支持。

使用 dateutil.parser

这个库真的是日期格式解析的福音,可以支持非常多的日期格式,并且不需要自己根据格式设置什么,函数内部全都帮你处理了。

虽然没有深入研究这个库对于各种日期格式的解析准确度,但是在我使用的日期格式解析是很准确的。

下面就来说明一些这个库的使用方法

由于不是系统默认库因此需要安装库:

pip install python-dateutil

例子:

>>> from dateutil.parser import parse
>>> dt = parse('2020-09-21T23:40:45.115479-0700')
>>> print(type(dt),dt)
(<type 'datetime.datetime'>, datetime.datetime(2020, 9, 21, 23, 40, 45, 115479, tzinfo=tzoffset(None, -25200)))

通过输出可以看出 parse 函数解析后返回的是一个 datetime.datetime 类型的对象,并且打印该对象内容输出的结果和我们想要的基本一致

使用 datetime

其实datetime不仅是个类也是模块名,因此想要一步到位的话就使用from datetime import datetime导入。

如果仅导入import datetime,则必须引用全名datetime.datetime

如果是 python3 的话现在只要执行dt.timestamp()就能根据时区正确输出对应的时间戳了,但可怜的是 python2 没有这个函数。

而在 python2 下如何将 datetime.datetime 类型的数据转化成时间戳呢,方法如下:

>>> import time
>>> int(time.mktime(dt.timetuple()) * 1000.0 + dt.microsecond / 1000.0)
1600760445115

这里经过处理最后输出的是以ms为单位的时间戳,对比 python3 例子的结果可以看出数值是不一样的。因为这个方法有个问题,不知道为什么生成的结果会比真实时间戳多出1小时。

如果仅仅是上面说的那个问题其实还挺好解决,最简单粗暴的方法就是在生成的结果后面减去1小时的时间戳即可。

而这个方法有个更严重的问题那就是不会处理时区相关的内容,查了一下官网对timetuple()方法的说明,这个方法只会将 datetime 的年月日时分秒等信息进行处理,而不会处理时区相关的,因此如果本地系统的时区和要处理数据的时区不一致的话最后输出的结果也绝对是错误的。

解决

最后我查看了一下 python3 官网中对 datetime.timestamp() 方法的说明,里面说到一句

or by calculating the timestamp directly:
timestamp = (dt - datetime(1970, 1, 1)) / timedelta(seconds=1)

这里的意思是用当前时间的 datetime 对象减去 1970/01/01 的即时间戳为0的 datetime 对象,最后再计算差值的时间即可。

有了这个思路后我就用下面的代码实现了这个功能:

dt = parse('2020-09-21T23:40:45.115479-0700')
ep = parse('1970-01-01T00:00:00.000000+0000')
td = dt-ep
print((td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**3)

# 1600756845115

最后终于获得了我想要的结果~

后记

其实还有个几个思路来解决这个问题,一个就是通过修改 datetime 对象的时区应该也能实现,在廖雪峰老师的 python3 datetime 教程中就是通过结合 datetime 的 timezone/tzinfo/astimezone 这些方法来实现的,不过同样是在 python2 下没有 timezone 这个方法,而网上找到替代 timezone 的方案比较麻烦,需要用到 pytz 这个库。

而另外一个就是计算要解析数据的时区与0时区之间的差值,然后利用 timedelta 方法对时间进行加减,这样使用 datetime.timetuple() 方法就可以按照0时区来计算时间了,在 stackoverflow 上看到了一个解决方法.

from datetime import datetime,timedelta
def dt_parse(t):
    ret = datetime.strptime(t[0:16],'%Y-%m-%dT%H:%M')
    if t[18]=='+':
        ret-=timedelta(hours=int(t[19:22]),minutes=int(t[23:]))
    elif t[18]=='-':
        ret+=timedelta(hours=int(t[19:22]),minutes=int(t[23:]))
    return ret

这里他的代码计算的是相对于时区为0的地区的差值,因此如果本地不是0时区则用计算出的时间戳还是有问题的。因此依旧需要使用上面提到的减去0时区的时间戳为0的数据才能获得真正的时间戳。

在这里还要额外说一句,虽然 dateutil.parser 这个库用起来很方便,但是经过测试这个库的性能非常耗时,因此如果对性能有一定要求的话还是推荐使用 stackoverflow 上的解决方法来解析特定的时间字符串。

参考链接