问题:

最近在做接口自动化框架,其中有涉及到接口返回值与MongoDB中的数据校验,所以用到了pymongo这个库。在使用过程中,踩了一个大坑:当我根据日期去筛选数据的时候,发现接口返回的数据量和mongodb中记录的数据量始终不一致。

分析:

先看三组数据(同一条数据):

1.数据库连接工具Navicat中的记录

1
2
3
4
5
6
7
8
{
"_id": ObjectId("5f646902ee75270c044f15d2"),
"Version": "1.0",
"TypeName": "运行",
"HappenTime": ISODate("2020-03-18T07:55:03.605Z"),
"AppName": "vipservermailG1",
"AppFriendlyName": "邮件服务",
}

2.数据库连接工具MongoDB Compass中的记录

1
2
3
4
5
6
7
8
9
{    
"_id":"5f646902ee75270c044f15d2",
"Version":"1.0",
"TypeName":"运行"
"HappenTime":2020-03-18 15:55:03.605,
"AppName":"vipservermailG1",
"AppFriendlyName":"邮件服务"
}

3.通过pymongo查出的数据

1
2
3
4
5
6
7
8
{
'_id': ObjectId('5f646902ee75270c044f15d2'),
'Version': '1.0',
'TypeName': '运行',
'HappenTime': datetime.datetime(2020, 3, 18, 7, 55, 3, 605000),
'AppName': 'vipservermailG1',
'AppFriendlyName': '邮件服务'
}

我发现同一条数据,在两个不同的数据库连接工具中日期”HappenTime”居然不一样,这个ISODate是什么鬼?查阅一番资料后才搞明白,原来mongo中的date类型以UTC(Coordinated Universal Time)存储,即格林尼治标准时间,而中国是在东八区,所以系统时间是加了时区的,即UTC+0800时间,两者正好相差8个小时

“2020-03-18T07:55:03.605Z”这种带T带Z的时间即为ISODate,它定义了互联网上日期/时间的偏移量表示。

同一时刻,不同时区的表示方法:

UTC时间:2020-03-18T07:55:03.605Z

CST时间(即东八区北京时间):2020-03-18T07:55:03.605+08:00

在接口查询数据库时,是以系统时间去查询的,然而从上面第三组数据可以看出,在pymongo去查询数据时,会将ISODate转化为Python的datetime.datetime对象,时间仍然是UTC时间,即没有加时区。所以才会出现两边数据不一致的情况。

解决方法

在搞懂问题根本原因后,那么解决方法也就非常明确了,在查询数据库时需要加上时区,两边时间保证一致即可。

pymongo也提供了非常简便地加时区的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import pymongo
import pytz

tzinfo = pytz.timezone('Asia/Shanghai') # 时区
client = pymongo.MongoClient(
host='1.1.1.1',
port=10,
username='admin',
password='123',
tz_aware=True, # 设置为True
tzinfo=tzinfo # 传入时区信息
)
db = client['log']
collection = db['systemruning']
data = collection.find({'AppName':'vipservermailG1'})

再次查询时,时间已变为东八区时间。

1
2
3
4
5
6
7
8
{
'_id': ObjectId('5f646902ee75270c044f15d2'),
'Version': '1.0',
'TypeName': '运行',
'HappenTime': datetime.datetime(2020, 3, 18, 15, 55, 3, 605000), # 加了8小时
'AppName': 'vipservermailG1',
'AppFriendlyName': '邮件服务'
}

ok,问题完美解决,不过既然涉及到了时区相关的问题,就顺便补一下这方面的知识,从源头上彻底了解一下时间的相关概念,以后再遇到类似的问题会游刃有余。

GMT

GMT(Greenwich Mean Time), 格林威治平时(也称格林威治时间)。

它规定太阳每天经过位于英国伦敦郊区的皇家格林威治天文台的时间为中午12点。

UTC

UTC(Coodinated Universal Time),协调世界时,又称世界统一时间、世界标准时间、国际协调时间。由于英文(CUT)和法文(TUC)的缩写不同,作为妥协,简称UTC。

UTC 是现在全球通用的时间标准,全球各地都同意将各自的时间进行同步协调。UTC 时间是经过平均太阳时(以格林威治时间GMT为准)、地轴运动修正后的新时标以及以秒为单位的国际原子时所综合精算而成。

UTC vs GMT

GMT是前世界标准时,UTC是现世界标准时。

UTC 比 GMT更精准,以原子时计时,适应现代社会的精确计时。

但在不需要精确到秒的情况下,二者可以视为等同。

每年格林尼治天文台会发调时信息,基于UTC。

时间戳

时间戳是一个数字,定义为格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数。注意,同一时刻,不同时区获得的时间戳是相同的。以前很多用来记录时间的字段,在数据库中往往不会存储为Datetime类型,而是直接存储为无符号整形,存放时间戳的值。

Python获取时间戳的代码为

1
2
import time
int(time.time())

本地时间

当前时区的本地时间

1
2
import datetime
datetime.datetime.now()

上面的输出值为

2020-03-20 18:50:03.23743

标准时间

本地时间只包括当前的时间,不包含任何时区信息。同一时刻,东八区的本地时间比零时区的本地时间快了8个小时。在不同时区之间交换时间数据,除了用纯数字的时间戳,还有一种更方便人类阅读的表示方式:标准时间的偏移量表示方法。

RFC3339详细定义了互联网上日期/时间的偏移量表示:

2020-03-20T00:00:00.00Z

这个代表了UTC时间的2017年12月08日零时

2020-03-20T00:08:00.00+08:00

这个代表了同一时刻的,东八区北京时间(CST)表示的方法

上面两个时间的时间戳是等价的。两个的区别,就是在本地时间后面增加了时区信息。Z表示零时区。+08:00表示UTC时间增加8小时。

这种表示方式容易让人疑惑的点是从标准时间换算UTC时间。以CST转换UTC为例,没有看文档的情况下,根据 +08:00 的结尾,很容易根据直觉在本地时间再加上8小时。正确的计算方法是本地时间减去多增加的8小时。+08:00减去8小时才是UTC时间,-08:00加上8小时才是UTC时间。

参考文献:

champyin彻底弄懂GMT、UTC、时区和夏令时

柳纯互联网上的日期和时间