最近項目中有個需求, 要記錄新注冊用戶的次日登錄情況, 於是寫出了如下代碼:
$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也就出現在這里.
比如當前時間為 2015-02-02 07:00:00 那么格林威治時間為 2015-02-01 23:00:00 (當前時間減去8小時)
當前時間為 2015-02-02 09:00:00 格林威治時間為 2015-02-02 01:00:00
再通過 floor() 處理后, 就相當於格林威治時間的 2015-02-02 與 2015-02-01, 中間相差一天.
所以如果用戶在7點多注冊, 而在9點再次登錄的情況下, $current_day - $create_day = 1.
測試代碼如下:
//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修改數據庫相關字段