我想知道存储PHP"支持的时区"的最佳做法是什么。我存储每个用户的时区,这样我就可以将UTC转换为他们的本地时间。你会把它作为字符串存储在varchar类型的字段中吗?在这种情况下,支持的最长时区字符串是什么?有更好的存储方法吗?简单地存储时区偏移量不是一种选择,因为它不像PHP的datetime对象那样自动考虑DST。
这几乎是这个问题的完全重复:在数据库中存储时区的正确方法?
但我将谈谈你提到的其他几点:
-
PHP时区是IANA时区。
-
只需将名称存储在
varchar
中即可。这里有一些关于时区名称字段长度的讨论。varchar(32)
会起作用,但为了安全起见,我会为未来的更改留出一些额外的空间。255可能有些过头了,但也许varchar(50)
是合理的。 -
很多人建议将UTC存储在数据库中。这是有时的一个很好的做法,但我不喜欢它被推荐为应该一直做的事情。有充分的理由使用UTC,也有充分的原因使用当地时间。
-
如果您使用UTC,您将需要时区名称,并且必须在输入和输出时转换为当地时间和从当地时间转换为本地时间。
-
如果使用本地时间,除了本地时间外,还应始终存储时区偏移。这是因为在夏令时转换过程中,当地时间可能不明确。有些数据库的类型就是这样的,例如Oracle或Postgres中的
TIMESTAMP WITH TIMEZONE
或SQL Sever中的DATETIMEOFFSET
。不幸的是,MySql没有这方面的类型,所以您需要两列。 -
如果您的数据只记录了一次并且从未更改(例如记录的事件时间),那么这两种选项中的任何一种都是可行的。UTC的优势在于能够为数学和转换做好准备。当地时间的优势在于保留观察者的视角。请参阅关于.Net的DateTime与DateTimeOffset,但这些概念仍然适用于此处。
-
如果您要编辑,那么无论哪种方式,您都需要时区名称,因此您不妨存储它。您可以决定是按记录的时间戳存储它,还是按每个用户存储一次。您应该考虑一下,如果用户更改时区,您希望发生什么。它应该适用于所有地方吗?还是仅限于新录制的条目?
我可以想到一些实际的原因来而不是存储为UTC:
-
如果您正在运行一个将输出数百或数千行的报告,并且所需的输出是记录的本地时间,那么必须在紧密循环中多次从UTC转换可能会减慢报告的执行时间。
-
有时有一项法律要求,即时间必须准确地存储在所显示的时间。非技术审计师可能不允许任何类型的转换,并认为UTC时间是非法的。如果他们查看本地时间加偏移值,他们将可能不关心偏移部分,并对本地时间值感到满意。我知道这看起来很傻,但有些行业和司法管辖区确实存在这些要求。
-
有时,您不想实际指代某个时刻,而是具体指代该时间的本地化表示。假设美国各地的餐馆都在早上6点开门。太平洋时间的早上6点与东部时间不同。将这些存储为UTC可能会导致无效的假设,尤其是在夏令时更改时。
在大多数其他情况下,我建议存储为UTC。但您必须决定什么最适合您的特定需求。
将所有时间存储为UTC,然后创建另一个具有时区偏移量的字段。
这实际上不是PHP问题,而是使用时区的数据库最佳实践。始终以UTC格式保存数据。为TZ偏移量设置另一个字段,例如-10或作为时区,例如"欧洲/阿姆斯特丹"。对我来说,我在python中使用pytz来处理这些东西。
您将始终能够恢复夏令时。
我总是存储日期&乘以整数字段中的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
,但在发送到客户端时在存储的时区中正确表示。