一尘不染

PHP,MySQL和时区

mysql

我正在尝试将时区系统集成到我的应用程序中,直到现在为止,我一直在努力避免制作时区感知的应用程序-
但是它是一项强制性要求,因此别无选择。TimeZones只是在我头上。我已经在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()),而不必担心时区以确保一切保持一致,该怎么办?
  • 您想到别人应该注意的其他事情

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


阅读 368

收藏
2020-05-17

共1个答案

一尘不染

此答案已更新,以适应赏金。原始的未编辑答案在该行下方。

赏金所有者添加的几乎所有问题都与在时区范围内MySQL和PHP日期时间应如何交互有关。

MySQL仍然具有 可怜的 时区支持,这意味着智能必须在PHP端提供。

  • 如上面的链接所述,将您的MySQL 连接时区 设置为UTC。这将导致MySQL处理的所有日期时间(包括NOW())被合理处理。
  • 始终使用DATETIMETIMESTAMP除非明确要求特殊行为,否则不要使用TIMESTAMP。这比以前减轻了痛苦。

    • 这是 确定 的Unix纪元时间存储为一个整数,如果你 到,如用于传统的目的。时代是UTC。
    • MySQL的首选日期时间格式是使用PHP日期格式字符串创建的 Y-m-d H:i:s
    • 所有 PHP日期时间存储在MySQL中时,将 所有 PHP日期时间转换为UTC,这很简单,如下所述
    • 从MySQL返回的日期时间可以安全地传递给PHP DateTime构造函数。请务必同时传递UTC时区!
    • 尽快 在echo上将 PHP DateTime转换为用户的本地时区。值得庆幸的是,DateTime与其他DateTime的比较和数学运算将考虑每个时区的时区。
    • 您仍然可以随PHP提供的DST数据库一时兴起。保持您的PHP和OS修补程序为最新!使MySQL保持UTC的幸福状态,以消除一种潜在的DST烦恼。

这解决了 大多数 问题。

最后一件事是笨拙的:

  • 如果某人先前已插入数据(例如使用NOW()),而不必担心时区以确保一切保持一致,该怎么办?

这真是个烦人。另一个答案指出了MySQL的答案CONVERT_TZ,尽管我个人通过在选择和更新过程中在服务器本地时区和UTC时区之间切换来做到这一点,因为我很顽固。


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

在现代,您不需要也不 应这样 做。

现代版本的PHP具有DateTimeZone类,该类具有列出命名的timezones的功能。命名时区允许用户选择其实际位置,并使系统根据该位置
自动 确定其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

通过使用此技术,系统将 自动 为用户选择正确的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有一个参差不齐但实用的
Date类,它具有一种标准方法,可以在分钟内获取UTC偏移量。在用户时钟正确的盲目假设下,您可以使用它来帮助缩小可能的时区列表。

让我们将这种方法与自己进行比较。除了推翻他们并不真正在乎的用户之外,您还需要在 每次 操作日期 时间时
实际执行一次日期数学运算。这不仅是次优的,而且是蝙蝠鸟粪的疯狂。强迫用户在需要DST支持时注明需要麻烦和困惑。

此外,如果您想为此使用现代的PHP
DateTime和DateTimeZone框架,则需要使用不建议使用的Etc/GMT...时区字符串而不是命名时区。这些区域名称可能会从将来的PHP版本中删除,因此这样做是不明智的。我说的都是经验。

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


根据要求,这是一个可用时区的循环,以分钟为单位显示其GMT偏移量。我在这里选择分钟来演示一个不幸的事实:并非所有的补偿都在整个小时内!实际上,有些人在夏令时期间提前了半个小时而不是整整一个小时。以分钟为单位的最终偏移量
与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";
}
2020-05-17