我正在使用 PyMongo 来简单地迭代 Mongo 集合,但我在处理大型 Mongodb 日期对象方面遇到了困难。
例如,如果我的集合中有一些数据,如下所示:
"bad_data" : [
{
"id" : "id01",
"label" : "bad_data",
"value" : "exist",
"type" : "String",
"lastModified" : ISODate("2018-06-01T10:04:35.000Z"),
"expires" : Date(9223372036854775000)
}
]
我会做类似的事情:
from pymongo import MongoClient, database, cursor, collection
client = MongoClient('localhost')
db = client['db1']
db.authenticate('user', 'pass', source='admin')
collection = db['collection']
for i in collection:
# do something with i
并得到错误
InvalidBSON: year 292278994 is out of range
有什么办法可以处理这个可笑的
Date()
对象而不让bson摔倒吗?我意识到在 Mongodb 中进行这样的日期是疯狂的,但我对此无能为力,因为这不是我的数据。
PyMongo FAQ 中实际上有一个关于这个主题的部分:
PyMongo 将 BSON 日期时间值解码为 Python
的实例。datetime.datetime
的实例仅限于datetime.datetime
(通常为 1)和datetime.MINYEAR
(通常为 9999)之间的年份。一些 MongoDB 驱动程序(例如 PHP 驱动程序)可以存储 BSON 日期时间,其年份值远远超出datetime.MAXYEAR
支持的范围。datetime.datetime
因此,这里的基本约束是驱动程序为从 BSON 进行映射而实现的
datetime.datetime
类型,尽管这可能很“荒谬”,但对于其他语言创建这样的日期值来说是有效的。
如常见问题解答中所述,您的一般解决方法是:
处理有问题的 BSON 日期。虽然存储有效,但它可能不是最初存储它的任何人/任何东西的“真实”意图。
在代码中添加“日期范围”条件以过滤“超出范围”的日期:
result = db['collection'].find({
'expires': { '$gte': datetime.min, '$lte': datetime.max }
})
for i in result:
# do something with i
如果您不需要进一步处理数据,请在投影中忽略有问题的日期字段:
result = db['collection'].find({ }, projection={ 'expires': False })
for i in result:
# do something with i
当然
'expires'
,顾名思义,该值的最初意图是一个遥远的未来日期,它永远不会发生,该数据的原始作者(很可能当前的代码仍在编写它)不是了解“Python”日期限制。因此,在所有文档中以及任何代码仍在编写该数字的地方“降低”该数字可能是相当安全的。
我对 pymongo 查询也有同样的问题。 当我尝试查询我的数据时,我收到此错误:
year 53427 is out of range
因为我的一些值是 Unix 时间,一些是 IOS 时间。
我的解决方案:
{$addFields: {
Unix_time_index: {$cond:
{ if: { $gt: [ "$mytime", ISODate("2023-12-12T00:00:00.000Z") ] },
then: 1,
else: 0 }},
}},
{$addFields:{
mytime: {$cond:
{ if: { $eq: [ "$Unix_time_index", 1 ] },
then: {$toLong: "$mytime"},
else: "$mytime" }},
}},
{$addFields:{
mytime: {$cond:
{ if: { $eq: [ "$Unix_time_index", 1 ] },
then: { $divide: [ "$mytime",1000 ] },
else: "$mytime" }},
}},
{$addFields:{
mytime: {$cond:
{ if: { $eq: [ "$Unix_time_index", 1 ] },
then: {$toDate: "$mytime"},
else: "$mytime" }}
}},
结果:
/* 1 */
{
"_id" : ObjectId("606b5d48ab86b4002130f944"),
"mytime" : ISODate("2021-06-18T21:52:51.000Z"),
"before" : Date(1624053171000000),
"Unix_time_index" : 1.0
}
/* 2 */
{
"_id" : ObjectId("606b5d48ab86b4002130f944"),
"mytime" : ISODate("2021-06-18T21:52:47.000Z"),
"before" : Date(1624053167000000),
"Unix_time_index" : 1.0
}
/* 3 */
{
"_id" : ObjectId("606b5d48ab86b4002130f944"),
"mytime" : ISODate("2022-01-27T23:44:46.000Z"),
"before" : ISODate("2022-01-27T23:44:46.000Z"),
"Unix_time_index" : 0.0
}
这让我发疯。我在 Amazon Linux 上运行了一个类似的脚本并且不断得到
20245 年超出范围
然后我在 Windows 11 上的 Ubuntu WSL 上运行它并得到相同的错误,但有一些有用的信息:
20245 年超出范围(考虑使用 CodecOptions(datetime_conversion=DATETIME_AUTO) 或 MongoClient(datetime_conversion='DATETIME_AUTO'))。请参阅:https://pymongo.readthedocs.io/en/stable/examples/datetimes.html#handling-out-of-range-datetimes
事实上,使用选项 datetime_conversion="DATETIME_AUTO" 创建 MongoClient 解决了我的问题:
client = MongoClient(CONNECTION_STRING,datetime_conversion="DATETIME_AUTO")