PHP、MySQL 和时区

Posted

技术标签:

【中文标题】PHP、MySQL 和时区【英文标题】:PHP, MySQL and Time Zones 【发布时间】:2011-08-11 17:19:06 【问题描述】:

我正在尝试在我的应用程序中集成时区系统,到目前为止,我一直在努力避免制作时区感知应用程序 - 但它现在是强制性要求,所以别无选择。 TimeZones 它超出了我的想象。我已经阅读了 php.net 和其他网站上的几个主题,包括但不限于 SO。但我永远无法掌握它。

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

请我相信这将有助于其他仍在努力掌握时区的人,所以请提供尽可能详细的解释,即使你不得不认为我是一个完整的小笨蛋/菜鸟。


编辑赏金:

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

如何通过 PHP DateTime 对象将时间存储在数据库中 应该将它们存储在DATETIMETIMESTAMP 中吗?每种方法的好处或注意事项是什么? 我们是否需要担心 MySQL DATE 的时区? 如何使用NOW() 插入值。这些是否需要在插入之前或之后以某种方式进行转换? MySQL使用的时区是否需要设置?如果是这样,怎么做?它应该持续完成还是在每个 HTTP 请求时完成?它必须设置为 UTC 还是可以是其他任何值?还是服务器的时间够用? 如何从 MySQL 中检索值并将它们转换为 DateTime 对象。将其直接放入DateTime::__construct() 就足够了,还是我们需要使用DateTime::createFromFormat()? 何时转换为当地时间以及为什么。是否有任何时候我们希望在它回显给用户之前对其进行转换(例如,与另一个 DateTime 对象或静态值进行比较)? 我们是否需要担心夏令时 (DST)?为什么或为什么不? 如果有人以前插入数据(例如使用NOW())而不担心时区,应该怎么做才能确保一切保持一致? 您认为有人应该注意的其他任何事情

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

【问题讨论】:

您好,您想做的是...将时间和日期设置为访问者区域? php.net/manual/en/function.date-default-timezone-set.php @Sudantha:是的,类似的。我想在 SELECT 菜单中为用户提供时区列表,然后根据所选时区格式化时间。 【参考方案1】:

此答案已更新以容纳赏金。未经编辑的原始答案位于该行下方。

赏金所有者添加的几乎所有问题点都与时区上下文中 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 日期时间存储在 MySQL 中时,将 所有 PHP 日期时间转换为 UTC,这很简单,如下所述 从 MySQL 返回的日期时间可以安全地交给 PHP DateTime 构造函数。请务必同时传递 UTC 时区! 立即将 PHP DateTime 转换为用户的本地时区on echo。值得庆幸的是,与其他 DateTime 的 DateTime 比较和数学运算将考虑到每个 DateTime 所在的时区。 您仍然可以随心所欲地使用随 PHP 提供的 DST 数据库。让您的 PHP 和 OS 补丁保持最新!将 MySQL 保持在 UTC 的幸福状态,以消除一个潜在的 DST 烦恼。

这解决了大部分点。

最后一件事很糟糕:

如果有人之前插入了数据(例如使用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 Date 类,具有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";

【讨论】:

@Charles:感谢您提供非常详细的帖子 :) 我想我理解您所说的大部分内容,并且我应该避免使用偏移量和 dst 来显示时区感知日期/时间给用户?而是只使用命名的时区标识符?如果我做对了,我想我现在有点明白了:) 但我对你发布的上述 foreach 循环非常好的一些事情感到困惑,除了我的时区,它显示的时间比它提前 1 小时目前在这里,但我知道您的代码没有问题,因为它使用我代码中的所有日期/时间来做到这一点。 @Charles:抱歉,空间不足,无法写更多内容 :) 你知道是什么原因导致了额外的 1 小时吗?我已经尝试在脚本开头使用 set timezone 函数并定义了我的本地时区,但这没有任何好处。另一件事,您能否修改上述 foreach 代码,以便将偏移量打印到同一行,例如:时区 - 时间 - 格林威治标准时间 -5.0 ? @Zubair,你确定你的服务器时钟是正确的,并且它在正确的时区吗?这可能会导致您看到的奇怪的一小时休息行为。如果操作系统是在一两年前安装的并且没有保持最新状态,那么它可能有一个过时的时区数据库。在过去几年中,美国许多地区的 DST 开始和结束日期发生了变化。我还用一个循环更新了我的帖子,演示了如何从给定的日期时间和时区获取 GMT 的偏移量。 @Charles: 谢谢 :) 大声笑不知道它这么简单(我必须更多地进入 DateTime 类,看起来很酷,但我还没有那么喜欢 OOP,仍然虽然努力)。 @Charles:我检查了我的计算机时间,它完全同步,我什至使用 Internet 时间功能将我的时间与 time.windows.com 同步。不久前我安装了我的操作系统,大约一个月,它的所有更新也都是最新的(使用 Win7)。我必须在 PHP.ini 中做任何事情吗?因为我没有做过任何与日期或时间相关的事情:oi 复制/粘贴了你的新代码,它仍然显示相同的 1 小时差异,使用偏移后它显示我 GMT +6 但它应该是 +5 :(我还尝试将您的代码上传到我的主机,它似乎显示了正确的时间:O【参考方案2】:

如何通过 PHP DateTime 对象将时间存储在数据库中

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

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

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

[ deletia ]

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

[ deletia ]

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

文档继续描述了 MySQL 支持的文字格式,特别是没有明确的时区偏移量。有一个 feature request 来解决这个问题,它现在已经七年多了,而且看起来不太可能很快推出。

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

    连接到 MySQL:

    $dbh = new PDO("mysql:dbname=$dbname", $username, $password);
    $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, FALSE);
    

    将会话 time_zone 设置为 DateTime 对象的会话:

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

    DateTime 对象产生一个合适的文字并像往常一样传递给MySQL(即作为准备好的语句的参数)。

    如文档中所述,可以使用多种可能的文字格式。但是,我建议使用'YYYY-MM-DD hh:mm:ss.ffffff' 格式的字符串(请注意,在 MySQL 5.6 之前的版本中,小数秒将被忽略),因为它最接近 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 以'YYYY-MM-DD HH:MM:SS' 格式检索并显示DATETIME 值。支持的范围是 '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 系统变量的值。如需更多信息,请参阅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?网络服务器?网络客户端?),但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');

【讨论】:

【参考方案3】:

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

* 更新,澄清我的第一段* 您还可以将时间戳存储为 INT。优点是您知道自时间戳以来将值存储在哪个时区是当前时间,以自 Unix 纪元(格林威治标准时间 1970 年 1 月 1 日 00:00:00)以来的秒数为单位。 见 php 文档:http://php.net/manual/en/function.time.php 使用 64 位操作系统,您不必担心 2038 年问题: http://en.wikipedia.org/wiki/Year_2038_problem

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

我们是否需要担心 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 对象或 静态值)?

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

我们是否有过需要担心夏令时的时候 (夏令时)?为什么或者为什么不?如果有人以前有过该怎么办 插入数据(例如使用 NOW())而不用担心时区 以确保一切都保持一致?

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

在您的数据库中使用日期时间有很多好处。如果你必须在 sql 中直接分析表中的数据,它更容易阅读,更“人类可读”。

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

【讨论】:

我觉得你对TIMESTAMP 有点困惑。我的意思是 MySQL TIMESTAMP 数据类型,而不是存储在 INT (或 BIGINT 更大数字)中的 Unix 时间戳。见dev.mysql.com/doc/refman/5.5/en/datetime.html 你说得对,我说的是存储为 INT 的 unix 时间戳。据我了解,但我可能是错的,如果我使用 DATE、DATETIME 和 TIMESTAMP MySQL 类型,那么我必须同时处理 MySQL/MySQL 连接和 php 中的时区。如果我将它存储为 INT,那么无论我在哪里使用我的表,我都知道我的日期存储在哪个时区,我只需要关心 php 代码。我可能错了,但这就是我的理解。根据我的经验,将时间戳存储为 INT 是最简单的方法。您的问题对于澄清所有这些非常有用。 我阅读了上一个答案,最好说:***.com/questions/5768380/… 如果您所做的只是简单的查找,将其存储为 Unix 时间戳整数通常应该足够好,但您仍然需要担心时区,因为它是UTC,用户可能不是。这将由 PHP 自动完成,但仍然需要完成。但是,如果您想以任何方式对其进行操作,就会出现问题。例如,添加一天。听起来很简单,对吧?就做date + 60 * 60 * 24?啊,但是你必须担心闰年和闰秒以及所有那些爵士乐。我曾经在INTs 中存储时间,但我已经悔改了我的邪恶方式。 有趣。对于用户时区,我将其放在单独的列中。对于日期操作,您有一个非常好的观点。我想我只需将其转换为 php datetime 对象,执行我的操作,然后将其转换回时间戳。

以上是关于PHP、MySQL 和时区的主要内容,如果未能解决你的问题,请参考以下文章

[转]PHP时区/MySql时区/Linux时区

在 PHP 和 MySQL 中设置时区

在 php 中从 EDT 转换为 GMT+05.30 时区以在 mysql 数据库中输入时间戳

Medoo和时区

从 phpMyAdmin 更改 mysql 时区

mysql设置时间