在PHP Web应用程序中使用时区

7

我已经花了几个小时来寻找如何在PHP/MySQL网站应用中最好地使用时区,找到一个确定的答案很难。从目前为止我所学到的是,最好将每个人的东西都存储在UTC(如果我错了请纠正我)。

当用户注册时,我会要求他们选择时区并将其存储在他们的用户信息中。这将以下拉菜单的形式呈现:

<option value="Europe/London">(GMT) Greenwich Mean Time : London</option>

我正在开发的应用程序将允许用户与他人设置未来的安排(会议),就像日历一样。显然,在一年的过程中,不同的时区有不同的夏令时期间,您有什么想法可以满足这一点?
例如,英国用户在2013年1月24日设置了下午3:00的会议,并邀请住在加利福尼亚州的人参加此次会议,如何使美国人在他/她的时区看到该会议,英国用户在他/她的时区看到该会议? (请注意,两个用户都已注册并设置了自己的时区)。
有没有人能清楚地解释一下这个问题,或者给我一些例子?或者可以告诉我在哪里找到这个答案?
谢谢

1
可能是[向用户显示时间和时区信息(是什么,而不是如何)]的重复问题(https://dev59.com/-0XRa4cB1Zd3GeqPuLkA),或者http://stackoverflow.com/questions/1285523/date-and-time-handling-in-applications-serving-clients-in-multiple-time-zones?rq=1 - user557846
UTC(曾被称为GMT)和伦敦当地时间不是同一回事,你知道吗?伦敦遵守夏令时。 - O. Jones
我知道(我住在英国),应该更清楚地说明夏令时的问题。每次当我觉得我差不多解决了这个问题,夏令时总是似乎变得困扰人 :) - Aaron Fisher
2个回答

13

我曾在一年多之前为一家私人飞机运营商编写的PHP/MySQL应用程序中广泛处理了这种情况。这两个平台处理时区有不同的策略,但我将解释我是如何做到的。我将MySQL服务器设置为UTC,并在用户在注册过程中为用户配置文件指定的时区中运行每个PHP脚本。

MySQL和PHP(PHP 5.2及以上版本)都具有本机日期时间数据类型。MySQL的datetime是一个原始数据类型,而PHP 5.2及以上版本提供了内置的DateTime类。MySQL datetime数据类型不包括时区的元数据,但是PHP DateTime对象始终包括时区。如果PHP datetime构造函数没有在第二个参数中指定可选的时区,则PHP datetime构造函数使用php环境变量。

MySQL和PHP都在配置文件中设置默认时区。MySQL使用配置文件中设置的日期时间作为每个db连接的默认时区,除非用户在使用命令SET time_zone = [timezone];开始连接后指定其他时区。PHP还使用服务器配置文件中设置的时区设置一个时区环境变量,可以在脚本开始后使用PHP函数date_default_timezone_set()覆盖此环境变量。

PHP DateTime类具有一个名为timezone的属性,该属性是PHP DateTimeZone对象。DateTimeZone对象使用精确时区的字符串指定。 时区列表非常全面,涵盖世界各地数百个单独的时区。 PHP时区将自动考虑夏令时。

当用户在Web应用程序中生成日期时间时,在用户配置文件的时区中构造一个PHP datetime对象。然后使用setTimezone方法将DateTime对象修改为UTC时区。现在您可以将用户的datetime存储为UTC值,并使用DateTimeformat方法将数据表示为MySQL接受的格式字符串。

因此,用户生成日期时间,您在用户指定的时区中创建了一个PHP datetime对象:

// set using an include file for user profile
$user_timezone = new DateTimeZone('America/New_York'); 

// 1st arg in format accepted by PHP strtotime
$date_object1 = new DateTime('8/9/2012 5:19 PM', $user_timezone); 
$date_object1->setTimezone(new DateTimeZone('UTC'));
$formated_string = $date_object1->format('Y-m-d H:i:s');
$query_string = "INSERT INTO `t_table1` (`datetime1`) VALUES('$formated_string')";
从数据库中检索值时,请使用UTC时间构造,然后将其转换为用户所在的时区。
$query_string = "SELECT `datetime1` FROM `t_table1`";
$date_object1 = new DateTime($datetime_string_from_mysql, new DateTimeZone('UTC'));
$date_object1->setTimezone($user_timezone);
$string_for_display_in_application = $date_object1->format('m/d/Y g:i a');
使用此方法,您的日期时间值始终在数据库中以UTC格式存储,用户始终以其个人资料时区的形式查看这些值。如果需要,PHP会为每个时区进行夏令时校正。
一个需要注意的问题:这个解释并不包括MySQL时间戳数据类型。我建议使用MySQL datetime数据类型来存储日期时间值,而不是时间戳数据类型。关于时间戳数据类型,请参阅手册这里编辑:您可以使用DateTimeZone类的静态方法listIdentifiers生成包含每个PHP时区字符串的数组。

是的,在这种情况下日期会从8月10日变为8月9日。我建议进行一些测试。在英国创建一个PHP日期时间,var_dump该值,更改对象的时区,然后再次var_dump。日期时间是日期和时间的组合,它们是PHP对象的不可分割的属性。 - steampowered
1
我建议将日期和时间都包含的值保存在 MySQL 的 DATETIME 数据类型中。 MySQL也有DATE数据类型和TIME数据类型,当只需要这些部分时可以使用它们。手册在此处解释。能给我一个点赞吗? - steampowered
我刚试过这些代码,在插入数据库时运行良好。但是当我使用你的第二个查询从数据库中读取时,似乎不起作用。这是我尝试的代码: $startdatetimeobject=new DateTime($row['StartDateAndTime'], new DateTimeZone('UTC'); $startdatetimeobject->setTimezone($user_timezone); $start = $startdatetimeobject->format('m/d/Y g:i a'); - Aaron Fisher
你需要更具体地说明“它不起作用”。你期望发生什么事情却没有发生? - steampowered
我在服务器端使用了您的技术,即php/mysql,并使用了这个js库来检测用户的时区http://pellepim.bitbucket.org/jstz/。虽然不是完美的,但我可以自动检测用户的时区,然后将其保存到会话中并刷新页面以正确显示日期。这样,我就不必询问用户,他也不需要在移动到另一个国家并在计算机上更改时区时进行更改。 - Alexandru Trandafir Catalin
显示剩余6条评论

4
在MySQL中,您需要做的是:
  1. 将每个用户选择的时区存储在可以在代表该用户进行数据库查询时检索到的某个地方。您可以将其存储为字符串。
  2. 在代表特定用户执行工作之前(例如,存储或检索约会时间和日期),执行SET time_zone =(存储的时区设置)

这将导致时区适当地转换为每个人的本地时间。

编辑:

这起作用是因为

  1. MySQL尝试使用UTC(通用时间,以前称为格林威治标准时间)在表中存储DATETIME和TIMESTAMP数据项。
  2. 只有在它知道每个应用程序给出的每个数据项的正确本地时区时,它才能正确执行此操作。
  3. 在不关心不同时区的应用程序中,它以MySQL服务器范围的方式执行此操作。请参见https://dev.mysql.com/doc/refman/5.5/en/time-zone-support.html。运行跨国和多时区应用程序的大多数人将其服务器时区设置为UTC,而不是本地时间,因为这样可以更轻松地保持事情井井有条。
  4. 除非您还知道日期和时区,否则尝试将时间从UTC转换为本地时间没有太多意义,因为当地时间在一年的各个时候开关。尝试为以色列,亚利桑那州和纽约三者正确设置这三个值!以色列在逾越节和新年之际在夏令时和标准时间之间切换;亚利桑那州不切换,而纽约则随美国联邦立法机构的心情而变化。
  5. 有一个会话范围的time_zone设置(SET time_zone = something)。如果您不设置它,则使用上述第3项中的时区表示。如果您设置它,服务器将使用此作为时区来将其内部表示转换为发送回查询中的表示。
  6. 您可以通过发出SELECT Name FROM mysql.time_zone_name来获取MySQL服务器中可用时区名称的列表。这可以填充下拉菜单,用户可以从中选择自己的时区。如果此查询未返回任何项目,请查看https://dev.mysql.com/doc/refman/5.5/en/time-zone-support.html底部。

因此,这意味着您可以将会话time_zone设置为特定用户的时区,然后以该用户的时区返回所有时间。此外,您INSERTUPDATE的任何DATETIMETIMESTAMP项目将在放置在表中时从该用户的时区转换为MySQL的内部表示。

注意:在具有持久化MySQL连接的Web应用程序中,为新用户请求工作将继承连接的time_zone设置。请不要忘记为新用户重置它。

如果您正在运行返回多个用户本地时间数据的查询,并且这些用户恰好位于不同的时区,则无法利用此MySQL每个会话功能集。您可以通过为不同的用户运行不同的查询并在它们之间更改time_zone设置来解决此问题。

或者,您可以使用MySQL函数

CONVERT_TZ(datetime,'UTC','user_time_zone')

或者在每个项目上类似的操作。

另外,Java和DotNET有自己高质量的时区操作系统。因此,您可以选择在MySQL服务器中运行time_zone设置为UTC,并在应用程序中执行所有时区转换。


我理解网站上显示的任何时间都是以用户时区时间显示的,不需要任何魔法吧? - Aaron Fisher
1
如果您执行一个查询,从两个具有不同时区的用户获取时间 - 您会怎么做? - user557846
1
我更喜欢根据用户配置文件的时区参数在PHP内部处理时区。PHP更加细致,因为每个DateTime对象都可以有自己的时区。MySQL强制您通过设置和重置环境连接变量来为每个MySQL值使用相同的时区。此外,在大多数大型Web应用程序中,MySQL计算是昂贵且稀缺的,但PHP处理是丰富且廉价的。我在回答这个问题时提供了另一种方法。 - steampowered

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接