最近使用Python寫一段緩存用戶登錄信息的一段代碼,使用MongoDB保存token及用戶信息,同時設置過時時間爲當前時間1小時後,代碼以下:html
db['user'].insert_one(
{'token': token, 'info': user, 'expire_at': datetime.fromtimestamp(time.time() + 60 * 60 * 1)}
)
複製代碼
結果發現過了預期的時間好久後,數據依然存在。python
rs0:PRIMARY> db.user.getIndexes()
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "sign.user"
},
{
"v" : 2,
"key" : {
"expire_at" : 1
},
"name" : "expire_at_1",
"ns" : "sign.user",
"expireAfterSeconds" : 0
}
]
rs0:PRIMARY> db.user.find({"_id" : ObjectId("5e0cfc630f37131c94412622")}, {expire_at: 1}).pretty()
{
"_id" : ObjectId("5e0cfc630f37131c94412622"),
"expire_at" : ISODate("2020-01-02T10:10:22.160Z")
}
複製代碼
一開始有點納悶,仔細一看ISODate中有個"Z",大概知道怎麼回事了:時區在做怪!mongodb
我們是東8時,比標準時間快了8小時,也就是說我這裏設置的是1小時後過時,實際上存儲的時間是標準時間的9小時後...api
因而想到下面兩種方案來處理這個問題。緩存
方案1:使用pymongo保存時間時,指定時區bash
首先嚐試用幾種不一樣的方法插入幾條測試數據函數
db['user'].insert_one({'test': datetime.now()})
db['user'].insert_one({'test': datetime.utcnow()})
db['user'].insert_one({'test': datetime.fromtimestamp(time.time())})
db['user'].insert_one({'test': datetime.utcfromtimestamp(time.time())})
複製代碼
python環境下查詢結果以下測試
{'_id': ObjectId('5e0d7369db0af443d23152e5'), 'test': datetime.datetime(2020, 1, 2, 12, 36, 57, 550000)}
{'_id': ObjectId('5e0d737bdb0af443d23152e6'), 'test': datetime.datetime(2020, 1, 2, 4, 37, 15, 745000)}
{'_id': ObjectId('5e0d73e1db0af443d23152e7'), 'test': datetime.datetime(2020, 1, 2, 12, 38, 57, 925000)}
{'_id': ObjectId('5e0d73eadb0af443d23152e8'), 'test': datetime.datetime(2020, 1, 2, 4, 39, 6, 430000)}
複製代碼
mongo客戶端環境下查詢結果以下spa
{ "_id" : ObjectId("5e0d7369db0af443d23152e5"), "test" : ISODate("2020-01-02T12:36:57.550Z") }
{ "_id" : ObjectId("5e0d737bdb0af443d23152e6"), "test" : ISODate("2020-01-02T04:37:15.745Z") }
{ "_id" : ObjectId("5e0d73e1db0af443d23152e7"), "test" : ISODate("2020-01-02T12:38:57.925Z") }
{ "_id" : ObjectId("5e0d73eadb0af443d23152e8"), "test" : ISODate("2020-01-02T04:39:06.430Z") }
複製代碼
因而可知,若是直接使用pymongo及datetime保存時間字段是,若是不設置時區,就會與標準時間產生誤差。設計
經過查閱pymongo的幫助文檔發現,MongoClient這個類的構造函數中有一個參數tz_aware
,也正如其字面含義(知道、察覺時區)。
使用MongoClient鏈接時將這個參數設爲True,查詢結果以下
{'_id': ObjectId('5e0d7369db0af443d23152e5'), 'test': datetime.datetime(2020, 1, 2, 12, 36, 57, 550000, tzinfo=<bson.tz_util.FixedOffset object at 0x7fa4b0a8fac0>)}
{'_id': ObjectId('5e0d737bdb0af443d23152e6'), 'test': datetime.datetime(2020, 1, 2, 4, 37, 15, 745000, tzinfo=<bson.tz_util.FixedOffset object at 0x7fa4b0a8fac0>)}
{'_id': ObjectId('5e0d73e1db0af443d23152e7'), 'test': datetime.datetime(2020, 1, 2, 12, 38, 57, 925000, tzinfo=<bson.tz_util.FixedOffset object at 0x7fa4b0a8fac0>)}
{'_id': ObjectId('5e0d73eadb0af443d23152e8'), 'test': datetime.datetime(2020, 1, 2, 4, 39, 6, 430000, tzinfo=<bson.tz_util.FixedOffset object at 0x7fa4b0a8fac0>)}
複製代碼
這樣可以知道查詢到的時間使用的是什麼時區,使用utcnow
、utcfromtimestamp
這兩個函數保存的時間查詢後能夠根據時區獲得真實的時間,不過若是保存時就已經發生了錯誤,就沒有辦法了。
除此之外,有沒有方法來控制保存時間時採用的時區呢?除了上文提到的utcnow
、utcfromtimestamp
還有其它辦法嗎?繼續查閱pymongo文檔,其中提到了pytz
這個庫。
from datetime import datetime
import pytz
db['user'].insert_one(
{'test': pytz.timezone('Asia/Shanghai').localize(datatime.now())}
)
複製代碼
實際上,datetime也支持一個默認參數tz,能夠傳入tzinfo
類型的值來指定時區。
db['user'].insert_one(
{
'token': ticket,
'info': user,
'expire_at': datetime.fromtimestamp(time.time() + 60 * 60 * 1, tz=pytz.timezone('Asia/Shanghai'))
}
)
複製代碼
這幾種辦法保存時間,採用的都是將時間先轉化成標準時間,再進行保存。因此讀取時也須要將標準時間轉換成目標時區的時間。
from bson.codec_options import CodecOptions
collection = db.user.with_options(
codec_options=CodecOptions(tz_aware=True, tzinfo=pytz.timezone('Asia/Shanghai'))
)
result = collection.find()
複製代碼
得到帶時區的datetime後,astimezone
函數能夠進行時區轉換
import datetime
import pytz
if __name__ == '__main__':
y = datetime.datetime(2020, 1, 2, 4, 37, 15, 745000, tzinfo=pytz.timezone('UTC'))
# print(y.astimezone(pytz.timezone('Asia/Shanghai')))
print(y.astimezone(datetime.timezone(datetime.timedelta(hours=8))))
print(y.astimezone(pytz.timezone('Asia/Shanghai')).strftime('%Y-%m-%d %H:%M:%S'))
複製代碼
結果以下
2020-01-02 12:37:15.745000+08:00
2020-01-02 12:37:15
複製代碼
方案2:設置MongoDB的時區
本想應該能夠設置時間保存的時區,結果卻沒有找到相應的方法設置MongoDB中ISODate的默認時區,暫且擱置,若是有新的發現再來補充。
總結
最後思考了下,出現這個問題的緣由,一方面是本身思考不足,另外一方面跟MongoDB的設計思路有關,它的TTL索引字段只支持ISODate類型,若是沒有這個限制,在全部時間字段一概使用時間戳,就避免了這個問題。