PHP,MySQL和时区

Zub*_*ir1 22 php mysql timezone datetime

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

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

请相信这会帮助那些仍在努力掌握时区的人,所以请提供尽可能详细的解释,即使你不得不考虑我一个完整的dumbo/noob.


编辑赏金:

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

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

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

Cha*_*les 28

这个答案已经更新,以适应赏金.未经编辑的原始答案在线下.

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

MySQL仍然具有可悲的时区支持,这意味着智能必须是PHP端.

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

这解决了大多数问题.

最后一件事是doozy:

  • 如果他们之前插入数据(例如使用NOW())而不担心时区以确保一切保持一致,那么应该怎么做?

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


应用程序还应该能够为每个用户相应地设置/选择DST.

在现代,你不需要也不应该这样做.

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

您可以将DateTimeZone与DateTime结合使用,以实现一些简单但功能强大的功能.您可以默认以UTC格式存储和使用所有时间戳,并将它们转换为显示的用户时区.

// 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
Run Code Online (Sandbox Code Playgroud)

通过使用此技术,系统将自动为用户选择正确的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";
}
Run Code Online (Sandbox Code Playgroud)

通过使用一些客户端魔术,您可以大大简化选择过程.Javascript有一个参差不齐但功能正常的 Date类,有一个标准方法可以在几分钟内获得UTC偏移量.您可以使用它来帮助缩小可能的时区列表,盲目假设用户的时钟是正确的.

让我们自己比较这种方法.每次操作日期时,您需要实际执行日期数学,此外还要向用户推断他们并不真正关心的选择.这不仅仅是次优的,它是蝙蝠 - 鸟粪疯狂.强制用户表示他们何时需要DST支持,这就是要求麻烦和困惑.

此外,如果您想为此使用现代PHP DateTime和DateTimeZone框架,则需要使用已弃用的Etc/GMT...时区字符串而不是命名时区.这些区域名称可能会从未来的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";
}
Run Code Online (Sandbox Code Playgroud)


egg*_*yal 5

  • 如何将时间从 PHPDateTime对象存储在数据库中

    SQL-92标准指定时间文字应SQL使用合适的数据类型的关键字(例如传递TIMESTAMP随后的值的字符串表示(含有任选的时区,如果非默认偏移)为日期/时间值)。

    遗憾的是,MySQL 不符合 SQL 标准的这一部分。如日期和时间文字所述

    标准 SQL 允许使用类型关键字和字符串指定时间文字。

    [删除]

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

    [删除]

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

    该文档继续描述 MySQL 支持的文字格式,特别是不存在显式时区偏移。有一个功能请求来解决这个问题,现在已经有七年多了,而且看起来不太可能很快推出。

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

    1. 连接到 MySQL:

      [ deletia ]
    2. time_zone将会话设置为DateTime对象的会话:

      [ deletia ]
    3. DateTime对象中生成合适的文字并正常传递给 MySQL(即作为准备好的语句的参数)。

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

      $dbh = new PDO("mysql:dbname=$dbname", $username, $password);
      $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, FALSE);
      
      Run Code Online (Sandbox Code Playgroud)
  • 它们应该存储在DATETIME还是TIMESTAMP?每种方法有什么好处或注意事项?

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

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

    作为下记录DATEDATETIMETIMESTAMP类型

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

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

    MySQL 将TIMESTAMP值从当前时区转换为 UTC 进行存储,然后从 UTC 转换回当前时区以进行检索。(对于其他类型,例如 ,不会发生这种情况DATETIME。)默认情况下,每个连接的当前时区是服务器的时间。可以在每个连接的基础上设置时区。只要时区设置保持不变,您就会恢复存储的相同值。如果您存储一个TIMESTAMP值,然后更改时区并检索该值,则检索到的值与您存储的值不同。发生这种情况是因为没有使用相同的时区进行双向转换。当前时区可用作time_zone系统变量的值。有关更多信息,请参阅第 10.6 节,“MySQL 服务器时区支持”

    TIMESTAMP数据类型提供了自动初始化和更新到当前的日期和时间。有关更多信息,请参阅第 11.3.5 节,“自动初始化和更新TIMESTAMP

    请注意最后一段,它经常会吸引到 MySQL 的新手。

    还值得补充的是,如数据类型存储要求中所述DATETIME值需要 8 个字节的存储空间,而TIMESTAMP值仅需要 4 个字节(底层数据存储格式可以在日期和时间数据类型表示中找到)。

  • 我们是否需要担心 MySQL 的时区问题DATE

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

    其推论是,如果一个值对时区敏感,则还必须将其时间存储在例如TIMESTAMP列中:使用DATE列会导致重要信息的不可逆转的丢失。

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

    由于根据记载NOW()

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

    由于“该值以当前时区表示”并且相同的“当前时区”将用于评估日期/时间值,因此在使用 MySQL 的NOW()函数(或其任何别名)时不必担心时区. 因此,要插入一条记录:

    $qry = $dbh->prepare('SET SESSION time_zone = ?');
    $qry->execute([$datetime->format('P')]);
    
    Run Code Online (Sandbox Code Playgroud)

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

  • 是否需要设置 MySQL 使用的时区?如果是这样,如何?它应该持续执行还是在每个 HTTP 请求时执行?它必须设置为UTC还是可以是其他任何东西?还是服务器时间够?

    这在上面已经解决了。time_zone如果需要,可以全局设置 MySQL 的变量,从而避免在每次连接时都设置它。有关更多信息,请参阅MySQL 服务器时区支持

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

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

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

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

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

    INSERT INTO my_table (the_time) VALUES (NOW());
    
    Run Code Online (Sandbox Code Playgroud)
  • 何时转换为当地时间以及原因。它被回显给用户之前,我们是否曾经想要转换它(例如,与另一个 DateTime 对象或静态值进行比较)?

    我不确定您所说的“本地时间”是什么意思(对谁来说是本地时间?RDBMS?网络服务器?网络客户端?),但是DateTime对象之间的比较将根据需要处理时区转换(PHP 在内部以 UTC格式存储值,并且只转换输出)。

  • 有没有时候我们需要担心夏令时 (DST)?为什么或者为什么不?

    一般来说,如果您遵循上面给出的方法,那么 DST 的唯一问题是确保在他们期望的时区向用户呈现值。

  • 如果某人之前插入了数据(例如使用NOW())而不必担心时区以确保一切保持一致,他们应该怎么做?

    如上所述,使用NOW()永远不会引起问题。

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

    $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);
    
    Run Code Online (Sandbox Code Playgroud)