Python:如何获取时间对象所表示的毫秒数

对于datetime类型的对象,Python内置提供了time.mktime(p_tuple)和datetime类的timestamp()来返回自January 1, 1970, 00:00:00 GMT以来的秒数,但并没有提供直接获取毫秒数的方法,那么如何获取datetime对象从格林威治纪元时间起始的毫秒数呢?time.mktime(p_tuple)和datetime类的timestamp()有什么区别呢?

网络上很容易可以找到这样的写法:

1
2
3
4
5
6
7
def get_ts_in_ms(t):
"""
获取时间t表示的从格林威治时间1970-01-01 00:00:00开始的毫秒数
:param t: 为datetime类型
:return:
"""
return int(time.mktime(t.timetuple())) * 1000 + int(t.microsecond / 1000)

但,这样的写法有时并不是万能的或者说有时并非我们真正想要的。


为了方便验证,我们定义了如下方法,用来打印datetime对象的一些信息:

1
2
3
4
5
6
7
8
9
10
def print_time(t):
print(t)
print("t.tzname: {}".format(t.tzname()))
print("t.year, t.month, t.day, t.hour: {} {} {} {}".format(t.year, t.month, t.day, t.hour))
print("t.timetuple() : {}".format(t.timetuple()))
print("t.utctimetuple(): {}".format(t.utctimetuple()))
print("time.mktime(t.timetuple()) : {}".format(time.mktime(t.timetuple())))
print("time.mktime(t.utctimetuple()): {}".format(time.mktime(t.utctimetuple())))
print("t.timestamp() : {}".format(t.timestamp()))
print("--------------------------\n")
1
2
3
4
5
6
7
8
9
10
11
12
13
# 定义了两个时区:北京时区和UTC时区
tzCST = timezone(timedelta(hours=8))
tzUTC = timezone(timedelta(hours=0))


# local未定义时区信息,cst为CST时间,utc为UTC时间
local = datetime(2020, 12, 17)
cst = datetime(2020, 12, 17, tzinfo=tzCST)
utc = datetime(2020, 12, 17, tzinfo=tzUTC)

print_time(local)
print_time(cst)
print_time(utc)

case1:系统时区为CST时区下,执行以上程序

print_time(local)输出:

1
2
3
4
5
6
7
8
2020-12-17 00:00:00
t.tzname: None
t.year, t.month, t.day, t.hour: 2020 12 17 0
t.timetuple() : time.struct_time(tm_year=2020, tm_mon=12, tm_mday=17, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=3, tm_yday=352, tm_isdst=-1)
t.utctimetuple(): time.struct_time(tm_year=2020, tm_mon=12, tm_mday=17, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=3, tm_yday=352, tm_isdst=0)
time.mktime(t.timetuple()) : 1608134400.0 (北京时间2020-12-17 00:00:00)
time.mktime(t.utctimetuple()): 1608134400.0 (北京时间2020-12-17 00:00:00)
t.timestamp() : 1608134400.0 (北京时间2020-12-17 00:00:00)

print_time(cst)输出:

1
2
3
4
5
6
7
8
2020-12-17 00:00:00+08:00
t.tzname: UTC+08:00
t.year, t.month, t.day, t.hour: 2020 12 17 0
t.timetuple() : time.struct_time(tm_year=2020, tm_mon=12, tm_mday=17, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=3, tm_yday=352, tm_isdst=-1)
t.utctimetuple(): time.struct_time(tm_year=2020, tm_mon=12, tm_mday=16, tm_hour=16, tm_min=0, tm_sec=0, tm_wday=2, tm_yday=351, tm_isdst=0)
time.mktime(t.timetuple()) : 1608134400.0 (北京时间2020-12-17 00:00:00)
time.mktime(t.utctimetuple()): 1608105600.0 (北京时间2020-12-16 16:00:00)
t.timestamp() : 1608134400.0 (北京时间2020-12-17 00:00:00)

print_time(utc)输出:

1
2
3
4
5
6
7
8
2020-12-17 00:00:00+00:00
t.tzname: UTC
t.year, t.month, t.day, t.hour: 2020 12 17 0
t.timetuple() : time.struct_time(tm_year=2020, tm_mon=12, tm_mday=17, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=3, tm_yday=352, tm_isdst=-1)
t.utctimetuple(): time.struct_time(tm_year=2020, tm_mon=12, tm_mday=17, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=3, tm_yday=352, tm_isdst=0)
time.mktime(t.timetuple()) : 1608134400.0(北京时间2020-12-17 00:00:00)
time.mktime(t.utctimetuple()): 1608134400.0(北京时间2020-12-17 00:00:00)
t.timestamp() : 1608163200.0(北京时间2020-12-17 08:00:00)

case2:系统时区为UTC时区下,执行以上程序

print_time(local)输出:

1
2
3
4
5
6
7
8
2020-12-17 00:00:00
t.tzname: None
t.year, t.month, t.day, t.hour: 2020 12 17 0
t.timetuple() : time.struct_time(tm_year=2020, tm_mon=12, tm_mday=17, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=3, tm_yday=352, tm_isdst=-1)
t.utctimetuple(): time.struct_time(tm_year=2020, tm_mon=12, tm_mday=17, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=3, tm_yday=352, tm_isdst=0)
time.mktime(t.timetuple()) : 1608163200.0(北京时间2020-12-17 08:00:00)
time.mktime(t.utctimetuple()): 1608163200.0(北京时间2020-12-17 08:00:00)
t.timestamp() : 1608163200.0(北京时间2020-12-17 08:00:00)

print_time(cst)输出:

1
2
3
4
5
6
7
8
2020-12-17 00:00:00+08:00
t.tzname: UTC+08:00
t.year, t.month, t.day, t.hour: 2020 12 17 0
t.timetuple() : time.struct_time(tm_year=2020, tm_mon=12, tm_mday=17, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=3, tm_yday=352, tm_isdst=-1)
t.utctimetuple(): time.struct_time(tm_year=2020, tm_mon=12, tm_mday=16, tm_hour=16, tm_min=0, tm_sec=0, tm_wday=2, tm_yday=351, tm_isdst=0)
time.mktime(t.timetuple()) : 1608163200.0(北京时间2020-12-17 08:00:00)
time.mktime(t.utctimetuple()): 1608134400.0(北京时间2020-12-17 00:00:00)
t.timestamp() : 1608134400.0(北京时间2020-12-17 00:00:00)

print_time(utc)输出:

1
2
3
4
5
6
7
8
2020-12-17 00:00:00+00:00
t.tzname: UTC
t.year, t.month, t.day, t.hour: 2020 12 17 0
t.timetuple() : time.struct_time(tm_year=2020, tm_mon=12, tm_mday=17, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=3, tm_yday=352, tm_isdst=-1)
t.utctimetuple(): time.struct_time(tm_year=2020, tm_mon=12, tm_mday=17, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=3, tm_yday=352, tm_isdst=0)
time.mktime(t.timetuple()) : 1608163200.0(北京时间2020-12-17 08:00:00)
time.mktime(t.utctimetuple()): 1608163200.0(北京时间2020-12-17 08:00:00)
t.timestamp() : 1608163200.0(北京时间2020-12-17 08:00:00)

APIs剖析

timetuple()和utctimetuple()

以下为timetuple()的源码,它使用了datetime实例的year、month、day、hour、minute、second来直接构建元组结构。

1
2
3
4
5
6
7
8
9
10
11
12
def timetuple(self):
"Return local time tuple compatible with time.localtime()."
dst = self.dst()
if dst is None:
dst = -1
elif dst:
dst = 1
else:
dst = 0
return _build_struct_time(self.year, self.month, self.day,
self.hour, self.minute, self.second,
dst)

以下为utctimetuple()源码,它使用了utcoffset()方法根据当前datetime实例是否定义了时区来计算UTC偏移量;如果datetime实例有时区信息,那么utctimetuple()将减/加(东区,西区)该偏移量后得到的year、month、day、hour、minute、second进行构建元组结构;否则直接使用datetime实例的year、month、day、hour、minute、second构建元组结构,从语义上讲,该方法是转换的语义,在转换UTC元组时,它使用了时间对象的时区信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def utctimetuple(self):
"Return UTC time tuple compatible with time.gmtime()."
offset = self.utcoffset()
if offset:
self -= offset
y, m, d = self.year, self.month, self.day
hh, mm, ss = self.hour, self.minute, self.second
return _build_struct_time(y, m, d, hh, mm, ss, 0)

def utcoffset(self):
"""Return the timezone offset as timedelta positive east of UTC (negative west of
UTC)."""
if self._tzinfo is None:
return None
offset = self._tzinfo.utcoffset(self)
_check_utc_offset("utcoffset", offset)
return offset

所以,

  • case1中print_time(local):对于没有时区信息的local变量,t.timetuple()t.utctimetuple()返回的元组信息除了tm_isdst(夏令时)以外都是一致的;
  • case1中print_time(cst):cst变量的实例的时区偏移量为8小时,t.utctimetuple()返回的元组信息中要晚于t.timetuple()8个小时;
  • case1中print_time(utc):utc变量的实例的时区偏移量为0小时,所以t.timetuple()t.utctimetuple()返回的元组信息除了tm_isdst(夏令时)以外都是一致的;
  • 另外,print_time(local)、print_time(cst)、print_time(utc)对于t.timetuple()t.utctimetuple()的输出在case1和case2中是一致的,与操作系统设置的时区并无关系;

time.mktime(p_tuple)

以下为mktime()的说明,它的语义是对时间元组(注意,元组结构不具有时区信息)所代表的本地时间计算秒数。

1
2
3
4
5
6
7
8
9
10
def mktime(p_tuple): # real signature unknown; restored from __doc__
"""
mktime(tuple) -> floating point number

Convert a time tuple in local time to seconds since the Epoch.
Note that mktime(gmtime(0)) will not generally return zero for most
time zones; instead the returned value will either be equal to that
of the timezone or altzone attributes on the time module.
"""
return 0.0

所以,

  • print_time(local),在case1和case2不同时区下,t.timetuple()t.utctimetuple()表示的元组都是time.struct_time(tm_year=2020, tm_mon=12, tm_mday=17, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=3, tm_yday=352, …)
    • case1中,该元组被mktime()认为是CST时间2020-12-17 00:00:00,则结果都为1608134400.0;
    • case2中,该元组被mktime()认为是UTC时间2020-12-17 00:00:00,则结果都为1608163200.0;
  • 同理,可以分析print_time(cst)、print_time(utc)

timestamp()

以下为timestamp()的源码,该方法在没有时区信息时,使用_mktime()方法计算秒数,_mktime()内部使用了本地时区信息;在有时区信息时,直接减去_EPOCH值。

1
2
3
4
5
6
7
def timestamp(self):
"Return POSIX timestamp as float"
if self._tzinfo is None:
s = self._mktime()
return s + self.microsecond / 1e6
else:
return (self - _EPOCH).total_seconds()

所以,

  • local变量没有定义时区,对于print_time(local),case1北京时区下输出1608134400.0 (北京时间2020-12-17 00:00:00),case2UTC时区下输出1608163200.0(北京时间2020-12-17 08:00:00);
  • cst变量带有时区信息,对于print_time(cst),case1北京时区和case2UTC时区下都输出了同样的结果1608134400.0 (北京时间2020-12-17 00:00:00);

结论

time.mktime(t.timetuple())适用于构建本地时区时间(即,LocalTime)的场景,借助timestamp()来获取秒数更适用于多时区编程的场景:

1
2
3
4
5
6
7
def get_ts_in_ms(t):
"""
获取时间t表示的从格林威治时间1970-01-01 00:00:00开始的毫秒数
:param t: 为datetime类型
:return:
"""
return int(t.timestamp()) * 1000 + int(t.microsecond / 1000)

几种时区设置方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
tzCST = timezone(timedelta(hours=8))
tzUTC = timezone(timedelta(hours=0))


# 当前时间
local = datetime.now()
cst = datetime.now(tzCST)
utc = datetime.now(tzUTC)


# 通过datetime()构造
local = datetime(2020, 12, 17)
cst = datetime(2020, 12, 17, tzinfo=tzCST)
utc = datetime(2020, 12, 17, tzinfo=tzUTC)


# 通过datetime.strptime()解析
local = datetime.strptime("20201217", '%Y%m%d')
cst = local.replace(tzinfo=tzCST)
utc = local.replace(tzinfo=tzUTC)