最近项目中有个需求, 要记录新注册用户的第二天登陆状况, 因而写出了以下代码:数据库
$create_time = '用户注册时间'; //格式 Y-m-d H:i:s $time = time(); $lasttime = date('Y-m-d H:i:s', $time); $current_day = floor($time / 86400); $create_day = floor( strtotime($create_time) / 86400 ); $days = $current_day - $create_day; switch ($days) { case 1: $values['2day'] = 1; break; //第二天登录 case 6: $values['7day'] = 1; break; //七日登录 case 14: $values['15day'] = 1; break; //十五日登录 } //执行SQL修改数据库相关字段
这段代码放到线上后, 出现了奇怪的BUG, 明明是当天注册的用户, 却出现了有第二天登陆的状况. 排查代码没有发现问题, 因而暂时搁置去忙其它事情. 而后在第6天时, 居然又出现了有七日登录的数据. 因而开始和同事正式解决这个问题, 最终发现是因为函数的时区缘由致使, 具体以下:函数
time() 返回自从 Unix 纪元(格林威治时间 1970 年 1 月 1 日 00:00:00)到当前时间的秒数.测试
上面是 time() 函数在手册中的说明, 重点是格林威治时间, time() 始终返回的是格林威治时间的时间戳. 当PHP设置过期区后, date() 在格式化时间的操做中会将 (当前时区的时间 - 格林威治时间) 的偏移量自动添加进去, 按东八区的时间算也就是8小时. strtotime() 一样会自动将时区的偏移量加入处理操做中. 因此这时上面代码中 strtotime($create_time) 获得的一样是格林威治时间. $current_day 与 $create_day 如今都是按照格林威治时间计算的天数, 而BUG也就出如今这里.spa
好比当前时间为 2015-02-02 07:00:00 那么格林威治时间为 2015-02-01 23:00:00 (当前时间减去8小时)code
当前时间为 2015-02-02 09:00:00 格林威治时间为 2015-02-02 01:00:00blog
再经过 floor() 处理后, 就至关于格林威治时间的 2015-02-02 与 2015-02-01, 中间相差一天.it
因此若是用户在7点多注册, 而在9点再次登陆的状况下, $current_day - $create_day = 1.ast
测试代码以下:class
//date_default_timezone_set('UTC'); //设置为格林威治时间 date_default_timezone_set('Asia/Shanghai'); //设置为东八区上海时间 $a = floor( strtotime('2015-02-02 07:00:00') / 86400 ); $b = floor( strtotime('2015-02-02 09:00:00') / 86400 ); echo $b - $a; // 结果 1 //将格林威治时间打开, 注释掉上海时间, 结果输出为 0.
最终解决BUG后的代码以下:登录
$create_time = '用户注册时间'; //格式 Y-m-d H:i:s $time = time(); $lasttime = date('Y-m-d H:i:s', $time); //时间戳老是获取的格林威治时间, strtotime()会自动添加当前时区的偏移量, 这里因时区问题致使天数计算出现一天的偏差, 因此在处理时间戳时增长时区的偏移量 $current_day = floor( ($time + date('Z')) / 86400 ); $create_day = floor( (strtotime($create_time) + date('Z')) / 86400 ); $days = $current_day - $create_day; switch ($days) { case 1: $values['2day'] = 1; break; //第二天登录 case 6: $values['7day'] = 1; break; //七日登录 case 14: $values['15day'] = 1; break; //十五日登录 } //执行SQL修改数据库相关字段