我想知道存储 PHP 的“支持的时区”的最佳做法是什么。我正在存储每个用户的时区,以便可以将 UTC 转换为/从他们的本地时间转换。您会将其作为字符串存储在 varchar 类型字段中吗?在这种情况下,可能支持的最长时区字符串是多少?存储这些有更好的做法吗?简单地存储时区偏移量不是一种选择,因为它不像 PHP 的 datetime 对象那样自动考虑 DST。
3 回答
这几乎是这个问题的完全重复: Proper way to store a timezone in a database?
但我会谈谈你提到的其他几点:
PHP 时区是IANA 时区。
只需将名称存储在
varchar
. 这里有一些关于时区名称的字段长度的讨论。varchar(32)
会工作,但为了安全起见,我会为未来的变化留出一些额外的空间。255 可能是矫枉过正,但也许varchar(50)
是合理的。很多人建议将 UTC 存储在数据库中。这有时是一个很好的做法,但我不喜欢它被推荐为你应该经常做的事情。使用 UTC 有充分的理由,使用本地时间也有充分的理由。
如果您使用 UTC,您将需要时区名称,并且必须在输入和输出时与本地时间相互转换。
如果您使用本地时间,除了本地时间之外,您应该始终存储时区偏移量。这是因为 DST 转换期间本地时间可能不明确。有些数据库有专门的类型,例如
TIMESTAMP WITH TIMEZONE
Oracle 或 Postgres 或DATETIMEOFFSET
SQL Sever。不幸的是,MySql 没有这个类型,所以你需要两列。如果您的数据只记录一次并且从未更改(例如记录的事件时间),那么这些选项中的任何一个都是可行的。UTC 具有为数学和转换做好准备的优势。当地时间具有保留观察者视角的优势。请参阅关于 .Net 的DateTime vs DateTimeOffset,但这些概念仍然适用于此处。
如果您要编辑这些时间,那么无论哪种方式,您都需要时区名称,因此您不妨存储它。您可以决定是按记录的时间戳存储它,还是每个用户只存储一次。您应该考虑如果用户更改他们的时区,您希望发生什么。它应该适用于所有地方吗?还是只针对新记录的条目?
我可以想到不存储为 UTC 的一些实际原因:
如果您正在运行的报告将输出数百或数千行,并且所需的输出是记录的本地时间,那么必须在紧密循环中重复多次从 UTC 转换可能会减慢报告的执行时间.
有时有一项法律要求,即按显示的时间精确存储时间。非技术审计员可能不允许任何类型的转换,并认为 UTC 时间是非法的。如果他们查看本地时间加偏移量值,他们可能不会关心偏移量部分,并且会对本地时间值感到满意。我知道这看起来很愚蠢,但有些行业和司法管辖区确实存在这些要求。
有时您不想实际指代单个时间瞬间,而是专门指该时间的本地化表示。假设您在美国各地的餐厅都在早上 6:00 营业。好吧,太平洋时间早上 6:00 与东部时间不同。将这些存储为 UTC 可能会导致无效的假设,尤其是在 DST 更改时。
在大多数其他情况下,我建议存储为 UTC。但是您必须决定哪种方法最适合您的特定要求。
将所有时间存储为 UTC,然后创建另一个具有时区偏移量的字段。
这实际上不是 PHP 问题,而是使用时区的数据库最佳实践。始终以 UTC 格式保存数据。为 TZ 偏移量设置另一个字段,例如 -10 或作为时区,例如“欧洲/阿姆斯特丹”。对我来说,我在 python 中使用 pytz 来处理这些东西。
您将始终能够恢复 DST。
我总是将日期和时间作为 unix 时间戳存储在整数字段中。这样我就确切地知道我要从我的数据库中得到什么。我建议使用您已经知道的这个列表中的字符串表示形式将时区存储在文本字段中。
例如这样的表称为stackoverflow,在数据库中也称为stackoverflow:-
+--+----------+-------------+
|id|date_time |time_zone |
+--+----------+-------------+
|1 |1373212914|Europe/London|
|2 |1373212914|Europe/Rome |
+--+----------+-------------+
然后你会像这样为你的 DateTime 对象加水:-
$dsn = 'mysql:dbname=stackoverflow;host=127.0.0.1';
$user = 'stackoverflow';
$password = 'stackoverflow';
try {
$dbh = new PDO($dsn, $user, $password);
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
echo 'Connection failed: ' . $e->getMessage();
}
$sql = 'select date_time, time_zone from stackoverflow';
$statement = $dbh->prepare($sql);
$statement->execute();
$results = $statement->fetchAll();
foreach($results as $result){
$datetimes[] = (new \DateTime())->setTimestamp((int)$result['date_time'])->setTimezone(new \DateTimeZone($result['time_zone']));
}
var_dump($datetimes);
这给出了这个输出: -
array (size=2)
0 =>
object(DateTime)[3]
public 'date' => string '2013-07-07 17:01:54' (length=19)
public 'timezone_type' => int 3
public 'timezone' => string 'Europe/London' (length=13)
1 =>
object(DateTime)[4]
public 'date' => string '2013-07-07 18:01:54' (length=19)
public 'timezone_type' => int 3
public 'timezone' => string 'Europe/Rome' (length=11)
检查表将显示两个条目具有相同的时间戳,相当于2013-07-07 16:01:54 UTC
,但在发送到客户端时在存储的时区中正确表示。