今天接到一个需求,要求从数据库中统计出当月的一些指标,想想这需求如此简单,就说没问题,半小时搞定。可是,最后却拖了大半天时间,真是令人心塞。

本以为用python可以轻轻松松搞定,没想到线上数据库没有python接口,只有java接口,可是这就能难道程序员?上网搜了半天加上组内导师的指导,找到了jython这个能在python里面用java接口的工具,然后加上运维大哥整理的依赖包和示例,折腾了一小会就把环境给布好了。貌似,说了这么多,有点跑题了,时区问题是在最后检查数据的时候发现的,临门一脚被打偏了,差点打脸,实在有点气愤,于是写篇博客缓解缓解。

大体是这样的,首先需要根据传入的年、月参数初始化时间,得到这个月的第一天和最后一天,这有啥难的,一段代码敲下去。

year = int(sys.argv[1])
month = int(sys.argv[2])
# 获取当月第一天的星期和当月的总天数
firstDayWeekDay, monthRange = calendar.monthrange(year, month)
# 获取当月的第一天
firstDay = datetime.date(year=year, month=month, day=1)
lastDay = datetime.date(year=year, month=month, day=monthRange)

calender这个库还是很实用了,monthrange函数可以返回这个月第一天是星期几,以及这个月总共有多少天,然后根据这个信息,得到当月的第一天和最后一天。

当然,本需求还需要拿到当月最后一个小时的时间,利用datetime也可以轻松搞定。

lastDayLastHour = datetime.datetime(year, month, monthRange,23)

虽然是在做需求,但是还是学习学习这个函数原型。

# 根据年,月,日,小时,分钟,秒来初始化一个日期时间
class datetime.datetime(year, month, day, hour=0, minute=0, second=0, microsecond=0)

输出的格式是这样的:

>>> import datetime
>>> print(datetime.datetime(2018,8,1,0,0,0))
2018-08-01 00:00:00

但是,数据库中一般存放的是时间戳,转换时间戳有很多种方式,本人省得麻烦,不想导入额外的库了,就选择了calendar里的函数,具体是这样的:

# 获取时间戳
startTime = calendar.timegm(firstDay.timetuple())
endTime = calendar.timegm(lastDay.timetuple())
lastDayHourTime = calendar.timegm(lastDayLastHour.timetuple)

至此,就可以根据时间戳去数据库捞数据了。出于检查的目的,我都会打印出sql语句,以便能快速定位出问题。捞完数据后,本以为可以轻轻松松的交差了。就在最后,我隐约地觉得这时间戳有点怪怪的(一般人还真看不出来)

select * from xx where UNIX_TIMESTAMP(time)>=1530403200 and UNIX_TIMESTAMP(time)<=1533081600

随手一查

还真有问题!!!随手google了一下,原来calendar.timegm是按照UTC时区来算的,而中国时区是UTC+8,而且time.timetuple函数不会带上时区信息。本着弄清楚问题的态度,我做了如下测试:

>>> d = datetime.datetime(2018, 8, 10)
>>> calendar.timegm(d.timetuple())
1533859200  ==> 2018/8/10 8:0:0 北京时间	
>>> time.mktime(d.timetuple())
1533830400.0  ==> 2018/8/10 0:0:0 北京时间

可见,time.mktime假定传入的timetuple为本地时区,也就是UTC+8,而calendar.timegm假定传入的timetuple为UTC时区,所以要加上8个小时。以后还是尽量用time模块吧,不然遇到这种问题真的是坑啊。

最后,说一下我的解决方案吧,并没有因此引入time包,而是转换出来的时间戳减去28800!!!就是这么皮!

参考资料:

时区转换利器pytz第三方模块

datetime官方文档

stack overflow timegm vs mktime