Zub*_*ir1 22 php mysql timezone datetime
我正在尝试在我的应用程序中集成一个时区系统,我真的很努力避免现在使用时区感知应用程序 - 但它现在是强制性要求所以别无选择.时区只是我的头脑.我在PHP.net以及其他网站上阅读了几个主题,包括但不限于SO.但我永远无法掌握它.
所以我想知道是否有人可以帮助我在这里:(我想要做的是我的应用程序中的偏好选项,允许用户从选择菜单中选择自己的时区,但应用程序也应该能够设置/为每个用户选择相应的DST.
请相信这会帮助那些仍在努力掌握时区的人,所以请提供尽可能详细的解释,即使你不得不考虑我一个完整的dumbo/noob.
编辑赏金:
我正在为这个问题添加一个赏金,因为我真的需要一个关于编写PHP/MySQL应用程序的时区的好规范问题(因此我也添加了MySQL标记).我从很多地方找到了东西,但把它们放在一起会很好.查尔斯的答案很棒,但我仍然觉得它有些缺乏.以下是我想到的一些事情:
DateTime
对象存储数据库中的时间DATETIME
或TIMESTAMP
?每个有什么好处或警告?DATE
?NOW()
.这些是否需要在插入之前或之后以某种方式进行转换?DateTime
对象.将它直接放入DateTime::__construct()
还是我们需要使用DateTime::createFromFormat()
?NOW()
)而不担心时区以确保一切保持一致,那么应该怎么做?如果可能,尝试将其分成逻辑部分,以便将来的用户更容易找到信息.请务必在必要时提供代码示例.
Cha*_*les 28
这个答案已经更新,以适应赏金.未经编辑的原始答案在线下.
在时区的背景下,赏金所有者添加的几乎所有问题都与MySQL和PHP日期时间应该如何交互有关.
MySQL仍然具有可悲的时区支持,这意味着智能必须是PHP端.
NOW()
)都得到妥善处理.DATETIME
,永远不要使用,TIMESTAMP
除非你非常明确地要求特殊行为TIMESTAMP
.这比过去痛苦少.Y-m-d H:i:s
这解决了大多数问题.
最后一件事是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)
如何将时间从 PHP
DateTime
对象存储在数据库中
的SQL-92标准指定时间文字应SQL使用合适的数据类型的关键字(例如传递TIMESTAMP
随后的值的字符串表示(含有任选的时区,如果非默认偏移)为日期/时间值)。
遗憾的是,MySQL 不符合 SQL 标准的这一部分。如日期和时间文字所述:
标准 SQL 允许使用类型关键字和字符串指定时间文字。
[删除]MySQL 识别这些结构以及相应的 ODBC 语法:
[删除]但是,MySQL 会忽略 type 关键字,并且前面的每个构造都会生成字符串 value ,类型为.
'str'
VARCHAR
该文档继续描述 MySQL 支持的文字格式,特别是不存在显式时区偏移。有一个功能请求来解决这个问题,现在已经有七年多了,而且看起来不太可能很快推出。
相反,必须time_zone
在服务器和客户端之间交换日期/时间值之前设置会话的变量。因此,使用PDO:
连接到 MySQL:
[ deletia ]
time_zone
将会话设置为DateTime
对象的会话:
[ deletia ]
从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
很有用,而对于表示在日历/时钟上看到的时间(如在照片中)很有用。
作为下记录的DATE
,DATETIME
和TIMESTAMP
类型:
该
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)