zoukankan      html  css  js  c++  java
  • 关于PHP函数time() date() 和 strtotime() 的时区问题

    最近项目中有个需求, 要记录新注册用户的次日登录情况, 于是写出了如下代码:

    $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修改数据库相关字段
  • 相关阅读:
    基于springBoot使用templates技术
    springBoot将配置文件读取进对象
    Django学习笔记二十二——Django的简易项目之账户注册的实现
    Django学习笔记二十一——Django的auth模块
    Django学习笔记二十——Django请求流程梳理总结
    Django学习笔记一十八——Django中间件的使用
    Django学习笔记一十五——cookies和session
    Django学习笔记一十九——补充内容:视图中的CBV和FBV (2020-05-15 15:05)
    Django学习笔记一十八——Django的数据库可视化操作
    Django学习笔记一十七——Django的FORM表单
  • 原文地址:https://www.cnblogs.com/caly/p/4277760.html
Copyright © 2011-2022 走看看