datetime 与 timestamp 相互转换遇到的坑
事情是这样的,今天遇到一个业务场景:按照比赛的时间 start_at 作为分页查询的条件获取赛程列表,首先初始化 20 条数据(数据库用的是 MongoDB):
第1条记录: start_at: 2015-08-15 01:13:17.330299
第2条记录: start_at: 2015-08-15 01:13:18.330299
第3条记录: start_at: 2015-08-15 01:13:19.330299
第4条记录: start_at: 2015-08-15 01:13:20.330299
第5条记录: start_at: 2015-08-15 01:13:21.330299
第6条记录: start_at: 2015-08-15 01:13:22.330299
第7条记录: start_at: 2015-08-15 01:13:23.330299
第8条记录: start_at: 2015-08-15 01:13:24.330299
第9条记录: start_at: 2015-08-15 01:13:25.330299
第10条记录: start_at: 2015-08-15 01:13:26.330299
客户端请求的时候通过 last_id
来确定下次从什么位置获取,第一次请求的时候不需要此参数,系统默认从第1条开始查询,此处假设page_size
为3,每次获取3条。第1次请求完,服务端会返回一个 last_id
给客户端,那么这个 last_id
是怎么生成的呢?服务端会把第3条记录的start_at从datetime转换成timestamps,返回一个int类型的时间戳给客户端,第次一的查询条件是:
cursor = conn.matches.find({}).sort([('start_at', 1)]).limit(3)
返回前3条数据以及last_id
(last_id
是根据第三条数据数据的的start_at
转换成时间戳之后的值)
last_id = time.mktime(start_at.timetuple())
>>> 1439572399.0 # 第三条记录 2015-08-15 01:13:19.330000 转换成时间戳的值
客户端发起第2次请求获取第2页的时候,把该数值传递到服务端,服务端接收到last_id=1439572399
后,做一次转换,转换成datetime类型:
start_at = datetime.datetime.fromtimestamp(last_id)
第二次查询的条件是:
cursor = conn.matches.find({'start_at': {'$gt': start_at}}).sort([('start_at', 1)]).limit(3)
于是碰到一个奇怪的问题:第二次查询返回的第一条数据和第一次查询返回的数据是一样的,感觉查询条件 $gt
变成了 gte
,这太不科学了,开始根本找不到原因,于是把初始化数据修改了一下:
第1条记录: start_at: 2015-08-14 11:05:21
第2条记录: start_at: 2015-08-14 11:05:22
第3条记录: start_at: 2015-08-14 11:05:23
第4条记录: start_at: 2015-08-14 11:05:24
第5条记录: start_at: 2015-08-14 11:05:25
第6条记录: start_at: 2015-08-14 11:05:26
第7条记录: start_at: 2015-08-14 11:05:27
第8条记录: start_at: 2015-08-14 11:05:28
第9条记录: start_at: 2015-08-14 11:05:29
第10条记录: start_at: 2015-08-14 11:05:30
然后重复上面的查询,此时返回的结果是正常的。究其原因是什么呢?从两份数据的对比,唯一的区别就是前者的时间带有毫秒数,为啥带上毫秒就出问题了呢,于是开始一步步调试发现一处细节,timestamp转换成datetime的时候,最后的毫秒丢失了:
start_at = datetime.datetime.fromtimestamp(1439572399)
>>> 2015-08-15 01:13:19
于是问题就可以解释的通了,没有带毫秒的2015-08-15 01:13:19
肯定是小于2015-08-15 01:13:19.330299
的,因此第二次查询的时候把第一次的查询返回的最后一条数据也查出来了。问题定位到了,那么就好解决了,原来最终的bug就出现在datetime转成timestamp的埋下的。
last_id = time.mktime(start_at.timetuple())
转换的时候,会自动忽略掉毫秒级别的值,解决的办法就是把毫秒数加上:
time.mktime(start_at.timetuple()) + start_at.microsecond * 0.000001
整个世界都清静了。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论