每小时记录不同时区的PHP和MySQL

时间:2018-08-23 13:51:41

标签: php mysql timezone

我希望通过我的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

这很好用,并向我提供了这样的每小时的日志。

enter image description here

现在我被困在时区上,我的数据库时区设置为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);

        }

    }

3 个答案:

答案 0 :(得分:1)

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

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

如果要将日志时间作为本地时间传递,请对其进行转换。在数据库中始终应存储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)表示的时间以及带有猜测的时区短码的本地时间。任何实现都是有效的,只需明确结果在哪个时区以及用户如何正确查询即可。

答案 1 :(得分:0)

如果您在世界通用时间记录所有时间戳,则您的头痛会减轻。跟踪谁在哪个夏令时时间很快变老。

答案 2 :(得分:0)

最终思路(我的问题解答):

好吧,首先感谢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');

经过模糊的了解后,我现在可以看到针对我的设置的解决方案,因为我将有多个来自不同时区的用户注册我的服务,因此在他们的帐户资料下可以选择一个选项来设置正确的时区。 / p>

然后,这使我能够抓住他们的时区,然后更新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

到那里时,如果我弄错了,请告诉我。