PHP,MySQL和时区

时间:2011-04-24 02:59:47

标签: php mysql datetime timezone

我正在尝试在我的应用程序中集成一个时区系统,我真的很努力避免现在开始制作时区感知应用程序 - 但现在它的强制性要求却别无选择。时区只是我的头脑。我在PHP.net和其他网站上阅读了几个主题,包括但不限于SO。但我永远无法掌握它。

所以我想知道是否有人可以在这里帮助我:(我想要的是我的应用程序中的偏好选项,允许用户从选择菜单中选择自己的时区,但应用程序也应该能够为每个用户自行设置/选择DST。

请相信这会帮助那些仍在努力掌握时区的人,所以请提供尽可能详细的解释,即使你必须考虑我一个完整的小型号/小号。


编辑赏金:

我正在为这个问题添加一个赏金,因为我真的需要在编写PHP / MySQL应用程序时需要一个关于时区的好的规范问题(因此我也添加了MySQL标记)。我从很多地方找到了东西,但把它们放在一起会很好。查尔斯的答案很棒,但我仍然觉得它有些缺乏。以下是我想到的一些事情:

  • 如何在PHP DateTime对象
  • 中存储数据库中的时间
  • 它们应该存储在DATETIME还是TIMESTAMP?对每个人有什么好处或警告?
  • 我们是否需要担心MySQL DATE的时区?
  • 如何使用NOW()插入值。这些是否需要在插入之前或之后以某种方式进行转换?
  • 是否需要设置MySQL使用的时区?如果是这样,怎么样?应该是持久地还是每次HTTP请求?它必须设置为UTC还是其他任何东西?或服务器的时间是否足够?
  • 如何从MySQL检索值并将其转换为DateTime对象。将它直接放入DateTime::__construct()就足够了,还是我们需要使用DateTime::createFromFormat()
  • 何时转换为当地时间及原因。是否曾经有一段时间我们想要将转换回之前将其转换回用户(例如与另一个DateTime对象或静态值进行比较)?
  • 我们是否有时间担心夏令时(DST)?为什么或为什么不呢?
  • 如果他们之前插入过数据(例如使用NOW())而不担心时区以确保所有内容保持一致,应该怎么办?
  • 你认为有人应该注意的其他事情

如果可能,请尝试将其分为逻辑部分,以便将来的用户更容易找到信息。请务必在必要时提供代码示例。

3 个答案:

答案 0 :(得分:28)

此答案已更新,以容纳赏金。原始的,未经编辑的答案在线下。

在时区的背景下,赏金所有者添加的几乎所有问题都与MySQL和PHP日期时间应该如何交互有关。

MySQL still has pathetic timezone support,这意味着情报必须是PHP端。

  • 将MySQL 连接时区设置为UTC,如上面的链接所示。这将导致MySQL处理的所有日期时间(包括NOW())得到妥善处理。
  • Always use DATETIME, never use TIMESTAMP除非您明确要求TIMESTAMP中的特殊行为。这比过去痛苦少
    • ok 如果您拥有,则将Unix纪元时间存储为整数,例如用于传统目的。时代是UTC。
    • MySQL的首选日期时间格式是使用PHP日期格式字符串Y-m-d H:i:s
    • 创建的
  • 所有 PHP日期时间转换为UTC时将其存储在MySQL中,这很简单,如下所述
  • 从MySQL返回的日期时间可以安全地传递给PHP DateTime构造函数。一定要传入UTC时区!
  • 在echo 上将PHP DateTime转换为用户的本地时区。值得庆幸的是DateTime比较和对其他DateTimes的数学将考虑每个DateTimes所在的时区。
  • 您仍然可以使用PHP提供的DST数据库。让您的PHP和OS补丁保持最新!让MySQL保持在令人愉快的UTC状态,以消除一个潜在的DST烦恼。

这解决了大多数点。

最后一件事是doozy:

  
      
  • 如果他们之前插入过数据(例如使用NOW())而不担心时区以确保所有内容保持一致,应该怎么办?
  •   

这是一个真正的烦恼。其中一个答案指出MySQL的CONVERT_TZ,虽然我个人已经通过在选择和更新期间在服务器本地和UTC时区之间跳跃来做到这一点,因为我是那样的硬核。


  

应用程序还应该能够为每个用户自行设置/选择DST。

你不需要和不应该在现代时代这样做。

PHP的现代版本具有DateTimeZone类,其中包括list named timezones的功能。命名时区允许用户选择其实际位置,并让系统自动根据该位置确定其DST规则。

您可以将DateTimeZone与DateTime结合使用,以实现一些简单但功能强大的功能。您只需以UTC by default存储和使用所有时间戳,并将其转换为显示的用户时区。

// UTC default
    date_default_timezone_set('UTC');
// Note the lack of time zone specified with this timestamp.
    $nowish = new DateTime('2011-04-23 21:44:00');
    echo $nowish->format('Y-m-d H:i:s'); // 2011-04-23 21:44:00
// Let's pretend we're on the US west coast.  
// This will be PDT right now, UTC-7
    $la = new DateTimeZone('America/Los_Angeles');
// Update the DateTime's timezone...
    $nowish->setTimeZone($la);
// and show the result
    echo $nowish->format('Y-m-d H:i:s'); // 2011-04-23 14:44:00

通过使用此技术,系统将自动为用户选择正确的DST设置,而无需询问用户他们当前是否在DST中。

您可以使用类似的方法渲染选择菜单。您可以不断为单个DateTime对象重新分配时区。例如,此代码将列出区域及其当前时间:

$dt = new DateTime('now', new DateTimeZone('UTC')); 
foreach(DateTimeZone::listIdentifiers() as $tz) {
    $dt->setTimeZone(new DateTimeZone($tz));
    echo $tz, ': ', $dt->format('Y-m-d H:i:s'), "\n";
}

通过使用一些客户端魔术,您可以大大简化选择过程。 Javascript有spotty but functional日期类,标准方法为get the UTC offset in minutes。您可以使用它来帮助缩小可能时区列表,盲目假设用户的时钟是正确的。

让我们将此方法与自己进行比较。除了向用户推断他们不会真正关心的选择之外,您还需要实际执行日期数学每次操作日期时间。这不仅仅是次优的,它是蝙蝠 - 鸟粪疯狂。强迫用户表示他们何时需要DST支持是一件麻烦和困惑。

此外,如果您想为此使用现代PHP DateTime和DateTimeZone框架,则需要use deprecated Etc/GMT... timezone strings而不是命名时区。这些区域名称可能会从未来的PHP版本中删除,因此这样做是不明智的。我从经验中说出这一切。

tl; dr :使用现代工具集,免除日期数学的恐怖。向用户显示命名时区列表。 以UTC格式存储您的日期,这不会受到DST的任何影响。将日期时间转换为用户选择的指定时区显示,而不是更早。


根据要求,这是一个循环显示可用时区,以分钟为单位显示GMT偏移量。我在这里选择了几分钟来证明一个不幸的事实:不是所有的补偿都在整个小时内!有些人实际上在DST期间提前半小时而不是整整一小时。以分钟为单位的结果偏移与Javascript的Date.getTimezoneOffset匹配。

$utc = new DateTimeZone('UTC');
$dt = new DateTime('now', $utc); 
foreach(DateTimeZone::listIdentifiers() as $tz) {
    $local = new DateTimeZone($tz);
    $dt->setTimeZone($local);
    $offset = $local->getOffset($dt); // Yeah, really.
    echo $tz, ': ', 
         $dt->format('Y-m-d H:i:s'),
         ', offset = ',
         ($offset / 60),
         " minutes\n";
}

答案 1 :(得分:4)

  •   

    如何从PHP DateTime对象

    中存储数据库中的时间

    SQL-92 standard指定应使用合适的数据类型关键字(例如TIMESTAMP表示日期/时间值)在SQL中传递时态文字,后跟值的字符串表示(包含可选的)时区偏移,如果非默认)。

    遗憾的是,MySQL不符合SQL标准的这一部分。正如Date and Time Literals所述:

      

    标准SQL允许使用type关键字和字符串指定时态文字。

    [ deletia ]
         

    MySQL识别这些构造以及相应的ODBC语法:

    [ deletia ]
         

    但是,MySQL忽略了type关键字,并且前面的每个结构都生成字符串值'str',类型为VARCHAR

    文档继续描述MySQL支持的文字格式,特别是没有明确的时区偏移。有一个feature request来解决这个问题,现在已经超过七年了,而且很快就会推出。

    相反,必须在交换服务器和客户端之间的日期/时间值之前设置会话的time_zone变量。因此,使用PDO

    1. 连接到MySQL:

      $dbh = new PDO("mysql:dbname=$dbname", $username, $password);
      $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, FALSE);
      
    2. 将会话time_zone设置为DateTime对象的会话:

      $qry = $dbh->prepare('SET SESSION time_zone = ?');
      $qry->execute([$datetime->format('P')]);
      
    3. DateTime对象生成一个合适的文字并正常传递给MySQL(即作为预备语句的参数)。

      如文档中所述,可以使用许多可能的文字格式。但是,我建议使用'YYYY-MM-DD hh:mm:ss.ffffff'格式的字符串(请注意,在5.6之前的MySQL版本中将忽略小数秒),因为它最接近SQL标准;事实上,可以在文字前面加上TIMESTAMP关键字,以确保一个人的SQL是可移植的:

      $qry = $dbh->prepare('
        UPDATE my_table
        SET    the_time = TIMESTAMP ?
        WHERE  ...
      ');
      $qry->execute([$datetime->format('Y-m-d H:i:s.u')]);
      
  •   

    它们应该存储在DATETIME还是TIMESTAMP?对每个人有什么好处或警告?

    PHP DateTime对象应始终存储在TIMESTAMP类型列中。

    最根本的区别是TIMESTAMP存储时区信息(通过将值存储在UTC中并按照上面time_zone变量的要求转换为/来自),而DATETIME不存在。因此TIMESTAMP对于表示特定时刻(类似于PHP DateTime对象)非常有用,而DATETIME对于表示在日历/时钟上看到的时间非常有用(如照片)。

    正如The DATE, DATETIME, and TIMESTAMP Types所述:

      

    DATETIME类型用于包含日期和时间部分的值。 MySQL以DATETIME格式检索并显示'YYYY-MM-DD HH:MM:SS'个值。支持的范围是'1000-01-01 00:00:00''9999-12-31 23:59:59'

         

    TIMESTAMP数据类型用于包含日期和时间部分的值。 TIMESTAMP的UTC范围为'1970-01-01 00:00:01''2038-01-19 03:14:07' UTC。

         

    MySQL将TIMESTAMP值从当前时区转换为UTC进行存储,然后从UTC返回到当前时区进行检索。 (对于DATETIME等其他类型,不会出现这种情况。)默认情况下,每个连接的当前时区是服务器的时间。可以基于每个连接设置时区。只要时区设置保持不变,您就会获得存储的相同值。如果存储TIMESTAMP值,然后更改时区并检索值,则检索的值与您存储的值不同。发生这种情况是因为在两个方向上都没有使用相同的时区进行转换。当前时区可用作time_zone系统变量的值。有关详细信息,请参阅Section 10.6, “MySQL Server Time Zone Support”

         

    TIMESTAMP数据类型提供自动初始化和更新到当前日期和时间。有关详细信息,请参阅Section 11.3.5, “Automatic Initialization and Updating for TIMESTAMP

    注意最后一段,它经常会抓住MySQL的新手。

    值得补充的是,正如Data Type Storage Requirements中所述,DATETIME值需要8个字节用于存储,而TIMESTAMP值只需要4个字节(基础数据存储格式可以是在Date and Time Data Type Representation)中找到。

  •   

    我们是否需要担心MySQL DATE的时区?

    时间对时区敏感才有意义。根据定义,一个日期单独无论时区如何都是普遍相同的,因此不需要担心时区"使用MySQL的DATE数据类型时。

    这样做的必然结果是,如果一个人拥有对时区敏感的值,那么还必须存储时间,例如:在TIMESTAMP列中:使用DATE列会导致不可逆转的重要信息丢失。

  •   

    如何使用NOW()插入值。这些是否需要在插入之前或之后以某种方式进行转换?

    正如NOW()所述:

      

    'YYYY-MM-DD HH:MM:SS'YYYYMMDDHHMMSS.uuuuuu格式返回当前日期和时间,具体取决于函数是在字符串还是数字上下文中使用。该值以当前时区表示。

    由于" 该值以当前时区表示"和那个" 当前时区"将用于评估日期/时间值,使用MySQL的NOW()函数(或其任何别名)时,不必担心时区。因此,要插入记录:

     
    INSERT INTO my_table (the_time) VALUES (NOW());
    

    请注意,如上所述,MySQL TIMESTAMP列的自动初始化使得在记录插入/更新期间大多数尝试使用NOW()

  •   

    是否需要设置MySQL使用的时区?如果是这样,怎么样?应该是持久地还是每次HTTP请求?它必须设置为UTC还是其他任何东西?或者服务器的时间是否充足?

    这已在上面解决。如果需要,可以全局设置MySQL的time_zone变量,从而避免在每次连接时设置它。有关详细信息,请参阅MySQL Server Time Zone Support

  •   

    如何从MySQL检索值并将其转换为DateTime对象。将它直接放入DateTime::__construct()就足够了,还是我们需要使用DateTime::createFromFormat()

    Compound Formats所述,PHP在DateTime::__construct()中使用的解析器识别的日期/时间格式之一是MySQL的输出格式。

    但是,由于MySQL输出格式不包含时区,因此必须确保通过其可选的第二个参数为DateTime构造函数提供该信息:

    $qry = $dbh->prepare('SET SESSION time_zone = ?');
    $qry->execute([$timezone->getName()]);
    $qry = $dbh->query('SELECT the_time FROM my_table');
    $datetime = new DateTime($qry->fetchColumn(), $timezone);
    

    或者,可以让MySQL将时间转换为UNIX时间戳并从中构造DateTime对象:

    $qry = $dbh->query('SELECT UNIX_TIMESTAMP(the_time) FROM my_table');
    $datetime = new DateTime($qry->fetchColumn());
    
  •   

    何时转换为当地时间以及原因。是否曾经有一段时间我们希望在之前将其转换回,它会回显给用户(例如,与另一个DateTime对象或静态值进行比较)?

    我不确定你的意思是什么时候"当地时间" (本地对谁?RDBMS?网络服务器?webclient?),但DateTime对象之间的比较将根据需要处理时区转换(PHP在内部以UTC格式存储值,仅转换为输出)。

  •   

    我们是否有时间担心夏令时(DST)?为什么或为什么不呢?

    一般来说,如果您遵循上面给出的方法,DST唯一需要关注的是确保以期望的​​时区向用户呈现价值。

  •   

    如果以前插入数据(例如使用NOW())而不担心时区以确保所有内容保持一致,应该怎么做?

    如上所述,使用NOW()不应该导致问题。

    如果在会话的TIMESTAMP变量设置为不正确的值时,已将文字值插入time_zone列,则需要相应地更新这些值。 MySQL的CONVERT_TZ()功能可能会有所帮助:

    UPDATE my_table SET the_time = CONVERT_TZ(the_time, '+00:00', '+10:00');
    

答案 2 :(得分:0)

  

如何从PHP DateTime对象存储数据库中的时间   它们应该存储在DATETIME还是TIMESTAMP中?有什么好处   或每个警告?

*更新,澄清我的第一段* 您还可以将时间戳存储为INT。优点是您知道在哪个时区存储了您的值,因为时间戳是自Unix Epoch(1970年1月1日00:00:00 GMT)以来的秒数测量的当前时间。 看到php doc:http://php.net/manual/en/function.time.php 使用64位操作系统,您不必担心2038年的问题: http://en.wikipedia.org/wiki/Year_2038_problem

使用时间戳更容易比较日期时间,在objet和数组中使用更有趣。例如,您可以轻松地将它们用作阵列的键。

  

我们是否需要担心MySQL DATE的时区?

在MySQL中,CURRENT_TIMESTAMP(),CURRENT_TIME(),CURRENT_DATE()和FROM_UNIXTIME()函数返回连接当前时区的值,该值可用作time_zone系统变量的值。此外,UNIX_TIMESTAMP()假定其参数是当前时区中的日期时间值。 http://dev.mysql.com/doc/refman/5.0/en/date-and-time-functions.html

  

如何使用NOW()插入值。这些是否需要转换   不知何故在插入之前或之后?

如果使用时间戳,则可以依赖PHP函数,它只是一个整数。

如果您使用日期时间,则curdate函数允许您拥有当前日期。 http://dev.mysql.com/doc/refman/5.0/en/date-and-time-functions.html#function_curdate

  

是否需要设置MySQL使用的时区?如果是这样,怎么样?应该   它是持久地还是在每个HTTP请求上完成的?是否必须如此   设置为UTC还是其他任何东西?或者是服务器的时间   足够?

请参阅http://dev.mysql.com/doc/refman/5.0/en/time-zone-support.html

  

如何从MySQL检索值并将其转换为DateTime   宾语。将它直接放入DateTime :: __ construct()就足够了   还是我们需要使用DateTime :: createFromFormat()?

同样,如果您使用时间戳,则更容易。你知道时间戳时区,你知道吗 什么时候转换为当地时间。使用时间戳可以轻松管理DST,请参阅时间戳周围的函数: http://php.net/manual/en/function.mktime.php

  

有没有时间我们想要在它之前转换它   回复给用户(例如,与另一个DateTime对象进行比较或   静态值??

我再想一想,时间戳可让您使用日期来比较它们,提取您需要的任何内容并打印您想要的内容。

  

有没有时间我们需要担心夏令时   (DST)?为什么或者为什么不?如果他们以前有人应该做什么   插入数据(例如使用NOW())而不用担心时区   确保一切都保持一致?

是的,如果您必须在申请中创建约会或会议,您应该担心。我开发了两个应用程序,一个用于临床预约,一个用于研讨会预约,支持超过70000个帐户和大量记录。我坚持使用时间戳,它非常适合索引,操作,比较。打印部件仅在视图上显示。

在数据库中使用datetime是有好处的。如果你必须在sql direct中分析表中的数据,那么它更容易阅读,它更“人性化”。

我不确定这篇文章会有一个固定的答案,因为这取决于你的需求。时间戳很容易操作(一种实用的方法)。存储它的方式取决于您的偏好,因为您可以存储日期并仍然将其转换为时间戳。但根据我的理解,时区是时间戳定义的一部分。