【问题标题】:Hourly logs in different timezones PHP & MySQL每小时记录不同时区 PHP 和 MySQL
【发布时间】:2018-08-23 13:51:41
【问题描述】:

我希望通过我的 api 从不同时区向我的用户提供简单的日志,我有点迷茫,希望能得到一些帮助。

我有下表称为日志:

CREATE TABLE `logs` (
   `id` INT(11) NOT NULL AUTO_INCREMENT,
   `userId` int(11) NOT NULL,
   `ip` VARCHAR(45) NOT NULL,
   `timestamp` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
   PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

我有一个每小时表来绑定我的查询超过 24 小时。

CREATE TABLE `log_hours` (
   `id` INT(11) NOT NULL AUTO_INCREMENT,
   `hourId` int(11) NOT NULL,
   PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO log_hours
    (hourId)
VALUES
    (1),
    (2),
    (3),
    (4),
    (5),
    (6),
    (7),
    (8),
    (9),
    (10),
    (11),
    (12),
    (13),
    (14),
    (15),
    (16),
    (17),
    (18),
    (19),
    (20),
    (21),
    (22),
    (23),
    (24);

每次我获得 api 命中时,我都会存储日志并获取以下数据。

id | userId |   ip    | timestamp
1    1        0.0.0.0   2018-08-23 14:20:34
2    1        0.0.0.0   2018-08-23 14:20:34
3    1        0.0.0.0   2018-08-23 14:20:34
4    1        0.0.0.0   2018-08-23 14:20:34
5    1        0.0.0.0   2018-08-23 14:20:34

现在,如果我像下面这样运行我的查询。

SELECT DW.hourId AS hour, ifnull(t.hits,0) hits from log_hours DW left join (            
    SELECT COUNT( * ) AS hits, HOUR( logs.timestamp ) AS `hour` 
    FROM logs WHERE DAY( logs.timestamp ) = DAY(CURDATE()) 
    AND MONTH( logs.timestamp ) = MONTH(CURDATE()) 
    AND `userId` = 1 GROUP BY HOUR( logs.timestamp )) t on DW.hourId = t.hour 
    ORDER BY hour ASC

这很好用,并且像这样给我每小时的日志。

现在我卡在时区上,我的数据库时区设置为 BST。

假设我在美国有一个用户,例如,如果他们在他们的时区点击 api,mysql 是否会自动将他们的时区转换为我的 BST 时区,以便输出数据有意义?

我应该使用 datetime NOT NULL DEFAULT CURRENT_TIMESTAMP 还是应该使用 INT 并使用 php time() 函数来插入记录?

我应该先从我的时区通过 php 返回数据,然后转换为他们的数据,在这种情况下什么是最佳做法?

编辑:

如果我在这里问一些愚蠢的问题,但我真的想完全理解这个过程,首先很抱歉。

我在什么时候检索用户的本地时区?我应该将它作为必需参数添加到 API 中吗?

这是我的 PHP 代码,我只是将所有内容添加到一个函数中,这样更容易阅读我的过程是 MVC。

public function usage_get(){

        $userId = $this->get('userId'); // i am obviously not getting the users info this way this is just for the example

        // DO I MAKE THEM SEND THEIR TIMEZONE TO THE API AS A PARAM THEN USE IT IN MYSQL
        //https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_convert-tz?
        $timezone = $this->get('timezone');

        // DO I SOMEHOW RETRIEVE THE TIME ZONE BASE ON IP AND GET IT THAT WAY?

        $query = $this->db->query("SELECT DW.hourId AS hour, ifnull(t.hits,0) hits
        from log_hours DW 
        left join (

            SELECT COUNT( * ) AS hits, HOUR( logs.timestamp ) AS `hour`
            FROM logs
            WHERE DAY( logs.timestamp ) = DAY(CURDATE())
            AND MONTH( logs.timestamp ) = MONTH(CURDATE())
            AND `userId` = ".$this->db->escape_str($userId)."
            GROUP BY HOUR( logs.timestamp )

        ) t  on DW.hourId = t.hour ORDER BY hour ASC");

        // DO I RUN A PHP FUNCTION AFTER THE MYSQL BASED ON TIMEZONE?

        if($query->num_rows() > 0){

            $this->response([
                'status'  => TRUE,
                'message' => 'Success',
                'data'    => $query->result(),
            ], REST_Controller::HTTP_OK);


        }else{ 

            $this->response([
                'status' => FALSE,
                'message' => 'Error'
            ], REST_Controller::HTTP_OK);

        }

    }

【问题讨论】:

    标签: php mysql timezone


    【解决方案1】:

    保持简单始终是最好的主意。保存独立于当地时间的所有日期。在我看来,其他一切都是不好的做法,因为当地时间只会在以后导致混乱和问题。

    假设我有一个美国用户,例如,如果他们在他们的时区点击 api,mysql 是否会自动将他们的时区转换为我的 BST 时区,这样输出数据才有意义? p>

    如果您想将日志时间作为本地时间进行转换。在数据库中,应始终存储 UTC(+/-0)。在 MySQL 中,您可以在查询期间使用 CONVERT_TZ 函数 (https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_convert-tz)。

    我应该使用 datetime NOT NULL DEFAULT CURRENT_TIMESTAMP 还是应该使用 INT 并使用 php time() 函数来插入记录?

    CURRENT_TIMESTAMP 似乎是解决此问题的正确方法,因为它是独立的 (Should MySQL have its timezone set to UTC?)。我看不出使用 PHP time() 函数的任何充分理由,因为如果服务器时间设置正确,CURRENT_TIMESTAMP 是最精确且始终正确的,但是基本上除了来自 PHP 的调用和实际的数据库插入。

    我是否应该先通过 php 从我的时区返回数据,然后转换为他们的在这种情况下的最佳做法是什么?

    在我看来,使用数据库进行转换是最好的(最快的)——至少如果您可以编写自己的 SQL 查询的话。否则,通过 PHP 进行转换是完全合法的,但是开销很小。之前有人问过如何做到这一点:Convert UTC dates to local time in PHP

    我在什么时候检索用户的本地时区?我应该将其作为必需参数添加到 API 中吗?

    这是应用程序架构的问题,没有“规则”。检索用户时区的可能性:

    1. GeoIP:使用 GeoIP 和基于位置的匹配将 IP 与区域匹配。与许多解决方案一样,这里的缺点是,如果他们使用 VPN 或代理,它将显示 VPN 或代理 IP 的区域,而不是其背后的用户物理 IP。

    2. JavaScript: Javascript 是客户端,虽然很像 GeoIP,但如果客户端位于 VPN 或代理之后,同样的问题仍然存在。您不会显示用户的时区,而是显示 VPN 或代理 (Getting the client's timezone in JavaScript) 的时区。

    3. 询问用户:时区以某种方式作为参数传递。

    对于 API,我肯定会推荐后者 - 要求时区作为参数,或者只是将时间 UTC (+-0) 传递给它们。也许使用您的 API 的用户有他们设置时区的个人资料?

    如果不考虑向他们传递一个包含 UTC (+-0) 时间以及带有猜测时区简码的本地时间的结果。任何实现都是有效的,只要明确结果是哪个时区以及用户如何正确查询即可。

    【讨论】:

    • 谢谢,@Blackbam 我已经用我的代码更新了底部的问题,以尝试更好地理解检索用户时区的正确位置
    • 嘿,你最好说清楚。等一下,我更新我的答案。
    • 嗨,Blackbam,非常感谢我昨天发出的回复;)我想我会要求用户发送他们的时区作为对我来说最简单的参数。
    • 我也更新了最后的想法:希望我在正确的轨道上
    • @user1503606 感谢您详细分享这一切。我从中制作了一个社区 wiki 答案,让未来的读者不会感到困惑。
    【解决方案2】:

    如果您以通用时间记录所有时间戳,您将不会那么头疼。跟踪谁有哪个夏令时很快变老了。

    【讨论】:

    【解决方案3】:

    最后的想法(我的问题解决方案):

    好的,首先感谢 Blackbam 提供的真正有帮助的信息,我也一直在阅读这篇有帮助的文章。

    http://www.vertabelo.com/blog/technical-articles/the-proper-way-to-handle-multiple-time-zones-in-mysql

    据我了解,UTC 是 24 小时制时钟起点的参考,然后可以将其偏移以转换为正确的时区,这曾经是 GMT 或 GMT 相同不确定,但它是起点。

    当必须定义原始时间参考时,同意使其相对于地球上的某个点。格林威治是伦敦的一个自治市镇,它是本初子午线(0o 经度)的所在地。

    然后,偏移量由 PHP 或 MySql 中的函数表示,这些函数根据字符串参数计算偏移量,例如,这些可以更改并存储在全局数据库中。

    MySQL:

    SELECT CONVERT_TZ('2004-01-01 12:00:00','GMT','MET');
    

    PHP:

    date_default_timezone_set('America/Los_Angeles');
    

    在模糊地理解这一点后,我现在可以看到我的设置的解决方案,因为我将有来自不同时区的多个用户注册我的服务,因此在他们的帐户配置文件下有一个选项来设置正确的时区对我来说很有意义。

    这使我能够获取他们的时区,然后更新 mysql 查询以在他们的时区返回正确的数据我同意 Blackbam 我更喜欢使用 mysql。

    我的流程如下。

    1. Mysql 数据库存储时间或设置为 UTC(仍在寻找 正确的方法。)

    **更新:**

    我想我已经通过以下命令登录到 mysql 并在命令行运行。

    SELECT @@global.time_zone, @@session.time_zone;

    然后返回你的时区我的。

    mysql> SELECT @@global.time_zone, @@session.time_zone;
    +--------------------+---------------------+
    | @@global.time_zone | @@session.time_zone |
    +--------------------+---------------------+
    | SYSTEM             | SYSTEM              |
    +--------------------+---------------------+
    1 row in set (0.00 sec)
    

    然后我就跑了。

    mysql> SET GLOBAL time_zone = '+00:00';
    

    现在我明白了。

    mysql> SELECT @@global.time_zone, @@session.time_zone;
    +--------------------+---------------------+
    | @@global.time_zone | @@session.time_zone |
    +--------------------+---------------------+
    | +00:00             | SYSTEM              |
    +--------------------+---------------------+
    1 row in set (0.00 sec) 
    
    1. 用户通过他们的帐户设置他们的时区,就像你一样 在博客平台(例如 Wordpress)中设置时区。

    2. 时区存储在数据库中,然后通过 用于转换的 API。

    例如这样。

    $timezone = $this->get('timezone');
    $query = $this->db->query("SELECT DW.hourId AS hour, ifnull(t.hits,0) hits
    from log_hours DW 
    left join (
    
        SELECT COUNT( * ) AS hits, HOUR( CONVERT_TZ(logs.timestamp,'GMT','".$this->db->escape_str($timezone)."') ) AS `hour`
        FROM logs
        WHERE DAY( CONVERT_TZ(logs.timestamp,'GMT','".$this->db->escape_str($timezone)."') ) = DAY(CURDATE())
        AND MONTH( CONVERT_TZ(logs.timestamp,'GMT','".$this->db->escape_str($timezone)."') ) = MONTH(CURDATE())
        AND `userId` = ".$this->db->escape_str($userId)."
        GROUP BY HOUR( CONVERT_TZ(logs.timestamp,'GMT','".$this->db->escape_str($timezone)."') )
    
    ) t  on DW.hourId = t.hour ORDER BY hour ASC");
    

    我目前正在从 CONVERT_TZ 获取 NULL,所以也只是想出解决这个问题的最佳方法,显然你需要将 zoneinfo 加载到 mysql 中。

    这解决了我的时区问题:

    ./mysql_tzinfo_to_sql /usr/share/zoneinfo | ./mysql -uroot -proot mysql
    

    这意味着我现在可以跑步了。

    SELECT CONVERT_TZ(NOW(), @@session.time_zone, 'America/Los_Angeles');
    SELECT CONVERT_TZ(NOW(), @@session.time_zone, 'Indian/Antananarivo');
    SELECT CONVERT_TZ(NOW(), @@session.time_zone, 'Asia/Dhaka');
    SELECT CONVERT_TZ(NOW(), @@session.time_zone, 'Antarctica/Troll');
    

    输出:

    CONVERT_TZ(NOW(), @@session.time_zone, 'America/Los_Angeles')
    2018-08-24 02:07:36
    
    CONVERT_TZ(NOW(), @@session.time_zone, 'Indian/Antananarivo')
    2018-08-24 12:07:36
    
    CONVERT_TZ(NOW(), @@session.time_zone, 'Asia/Dhaka')
    2018-08-24 15:07:36
    
    CONVERT_TZ(NOW(), @@session.time_zone, 'Antarctica/Troll')
    2018-08-24 11:07:36
    

    您可以从这里获取时区:http://php.net/manual/en/timezones.php

    如果我弄错了,请告诉我。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多