此答案已更新以容纳赏金。未经编辑的原始答案位于该行下方。
赏金所有者添加的几乎所有问题点都与时区上下文中 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";
}