小記:datetime 與 timestamp 相互轉換遇到的坑
事情是這樣的,今天遇到一個業務場景:按照比賽的時間start_at作為分頁查詢的條件獲取賽程列表,首先初始化20條資料(資料庫用的是MongoDB)
事情是這樣的,今天遇到一個業務場景:按照比賽的時間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
整個世界都清靜了。
關注公眾號「Python之禪」(id:vttalk)獲取最新文章