WeBid:夏令时时区计算不正确

3
我目前正在使用一个开源应用程序:WeBid(可以在此处获得:here)。
问题如下:
- 用户在数据库中存储了首选时区。 - 站点在数据库中存储了默认时区。 - 所有数据库存储的日期都是以“GMT-0”为标准。
应用程序不能正确计算夏令时,因为它使用以下代码:
(includes/functions_global.php)
$this->ctime = time() + (($this->SETTINGS['timecorrection'] + gmdate('I')) * 3600);
$this->tdiff = ($this->SETTINGS['timecorrection'] + gmdate('I')) * 3600;

gtpotyf解释:

gmdate('I') -> 如果夏令时(DST)处于活动状态,则返回1,如果未处于活动状态,则返回0。然而,由于gmdate始终使用GMT(+0),而该时区没有DST,因此它将始终返回0。

使用date('I')代替gmdate('I')会更好,但仍然不正确,因为它使用服务器的时区而不是用户的时区。

WeBid的最新版本没有进行最终修正,请帮助我解决这个问题。

来源


GMT(+0)和GMT不是同一回事。GMT(+0)可能是英国的一个时区,受夏令时+1的影响。为了确保在解码日期以转换为时区时不会出现夏令时的复杂情况,数据库应将时间/日期存储为UTC。 - oxygen
@Tiberiu-IonuțStan:不,"GMT"不是英国的时区。在我看来,任何将"GMT"解释为"英国时区"的都是错误的。 - Jon Skeet
@Jon Skeet 我是在说 GMT+00 和 GMT+01 会被解释为时区,而不是参考时间 GMT。GMT 应该写成 GMT 或 UTC,而不是 GMT-00。我觉得那里有些问题。此外,该应用程序(WeBit)应该保存时区,而不是一些“时间校正”。例如,它应该保存 Europe/London。请参阅 bcmcfc 的答案,这是唯一解决问题的方法。 - oxygen
@Tiberiu-IonuțStan:我认为任何将“GMT+00”解释为等同于“Europe/London”的东西都是基本上有问题的。任何仅指定为相对于UTC(或GMT)的区域都应被视为不遵守DST。我同意“Europe/London”是英国时区应该存储的方式,但我不同意“GMT(+0)可能是英国时区”。除非你的意思是某些东西错误地将英国时区存储为GMT(+0)... - Jon Skeet
@JonSkeet 当我发表第一条评论bcmcfc时,这个问题还没有得到解答。bcmcfc确切地意味着“数据库应该将时间/日期存储为UTC”。此外,这应该是一个被接受的答案,我真的看不出还有什么可以补充的(WeBid)明显存在非常严重的故障。 - oxygen
2个回答

4
为了正确处理时区,您需要采取一些措施。
将您的服务器设置为UTC,这样PHP的timedate函数会返回一个UTC时间戳(以及其他相关的函数,如strftime)。
根据您的数据库,您还可以将其时区设置为UTC。 MySQL在此问题上的文档在此处
如果日期时间尚未存储为UTC,则需要将其迁移到UTC。(我不确定您所说的GMT-0是什么意思。)
让用户选择一个格式为“Europe/London”的时区,该格式由PHP的DateTimeZone类支持。
经过一些研究,我编写了以下时区数组,以便在下拉框中向用户呈现时区,因为我不希望出现重复(例如,阿姆斯特丹和布鲁塞尔位于同一时区)。但我并不认为这个列表是完美的。根据您的用户群体,您可能需要更详细地调查某些时区。
$timezones = array(
        'Pacific/Midway' => '(UTC-11:00) Midway Island, Samoa',
        'Pacific/Honolulu' => '(UTC-10:00) Hawaii-Aleutian',
        'Pacific/Marquesas' => '(UTC-09:30) Marquesas Islands',
        'Pacific/Gambier' => '(UTC-09:00) Gambier Islands',
        'America/Anchorage' => '(UTC-09:00) Alaska',
        'America/Ensenada' => '(UTC-08:00) Tijuana, Baja California',
        'Etc/GMT+8' => '(UTC-08:00) Pitcairn Islands',
        'America/Los_Angeles' => '(UTC-08:00) Pacific Time (US & Canada)',
        'America/Denver' => '(UTC-07:00) Mountain Time (US & Canada)',
        'America/Chihuahua' => '(UTC-07:00) Chihuahua, La Paz, Mazatlan',
        'America/Dawson_Creek' => '(UTC-07:00) Arizona',
        'America/Belize' => '(UTC-06:00) Saskatchewan, Central America',
        'America/Cancun' => '(UTC-06:00) Guadalajara, Mexico City, Monterrey',
        'Chile/EasterIsland' => '(UTC-06:00) Easter Island',
        'America/Chicago' => '(UTC-06:00) Central Time (US & Canada)',
        'America/New_York' => '(UTC-05:00) Eastern Time (US & Canada)',
        'America/Havana' => '(UTC-05:00) Cuba',
        'America/Bogota' => '(UTC-05:00) Bogota, Lima, Quito, Rio Branco',
        'America/Caracas' => '(UTC-04:30) Caracas',
        'America/Santiago' => '(UTC-04:00) Santiago',
        'America/La_Paz' => '(UTC-04:00) La Paz',
        'Atlantic/Stanley' => '(UTC-04:00) Falkland Islands',
        'America/Campo_Grande' => '(UTC-04:00) Brazil',
        'America/Goose_Bay' => '(UTC-04:00) Atlantic Time (Goose Bay)',
        'America/Glace_Bay' => '(UTC-04:00) Atlantic Time (Canada)',
        'America/St_Johns' => '(UTC-03:30) Newfoundland',
        'America/Araguaina' => '(UTC-03:00) UTC-3',
        'America/Montevideo' => '(UTC-03:00) Montevideo',
        'America/Miquelon' => '(UTC-03:00) Miquelon, St. Pierre',
        'America/Godthab' => '(UTC-03:00) Greenland',
        'America/Argentina/Buenos_Aires' => '(UTC-03:00) Buenos Aires',
        'America/Sao_Paulo' => '(UTC-03:00) Brasilia',
        'America/Noronha' => '(UTC-02:00) Mid-Atlantic',
        'Atlantic/Cape_Verde' => '(UTC-01:00) Cape Verde Is.',
        'Atlantic/Azores' => '(UTC-01:00) Azores',
        'Europe/Dublin' => '(UTC) Irish Standard Time : Dublin',
        'Europe/Lisbon' => '(UTC) Western European Time : Lisbon',
        'Europe/London' => '(GMT) Greenwich Mean Time : London, Belfast',
        'Africa/Abidjan' => '(GMT) Monrovia, Reykjavik',
        'Europe/Amsterdam' => '(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna',
        'Europe/Belgrade' => '(UTC+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague',
        'Europe/Brussels' => '(UTC+01:00) Brussels, Copenhagen, Madrid, Paris',
        'Africa/Algiers' => '(UTC+01:00) West Central Africa',
        'Africa/Windhoek' => '(UTC+01:00) Windhoek',
        'Asia/Beirut' => '(UTC+02:00) Beirut',
        'Africa/Cairo' => '(UTC+02:00) Cairo',
        'Asia/Gaza' => '(UTC+02:00) Gaza',
        'Africa/Johannesburg' => '(UTC+02:00) Johannesburg, Harare, Pretoria',
        'Asia/Jerusalem' => '(UTC+02:00) Jerusalem',
        'Europe/Athens' => '(UTC+02:00) Athens',
        'Europe/Minsk' => '(UTC+02:00) Minsk',
        'Asia/Damascus' => '(UTC+02:00) Syria',
        'Europe/Moscow' => '(UTC+03:00) Moscow, St. Petersburg, Volgograd',
        'Africa/Addis_Ababa' => '(UTC+03:00) Nairobi',
        'Asia/Tehran' => '(UTC+03:30) Tehran',
        'Asia/Dubai' => '(UTC+04:00) Abu Dhabi, Muscat',
        'Asia/Yerevan' => '(UTC+04:00) Yerevan',
        'Asia/Kabul' => '(UTC+04:30) Kabul',
        'Asia/Yekaterinburg' => '(UTC+05:00) Ekaterinburg',
        'Asia/Tashkent' => '(UTC+05:00) Tashkent',
        'Asia/Kolkata' => '(UTC+05:30) Chennai, Kolkata, Mumbai, New Delhi',
        'Asia/Katmandu' => '(UTC+05:45) Kathmandu',
        'Asia/Dhaka' => '(UTC+06:00) Astana, Dhaka',
        'Asia/Novosibirsk' => '(UTC+06:00) Novosibirsk',
        'Asia/Rangoon' => '(UTC+06:30) Yangon (Rangoon)',
        'Asia/Bangkok' => '(UTC+07:00) Bangkok, Hanoi, Jakarta',
        'Asia/Krasnoyarsk' => '(UTC+07:00) Krasnoyarsk',
        'Asia/Hong_Kong' => '(UTC+08:00) Beijing, Chongqing, Hong Kong, Urumqi',
        'Asia/Irkutsk' => '(UTC+08:00) Irkutsk, Ulaan Bataar',
        'Australia/Perth' => '(UTC+08:00) Perth',
        'Australia/Eucla' => '(UTC+08:45) Eucla',
        'Asia/Tokyo' => '(UTC+09:00) Osaka, Sapporo, Tokyo',
        'Asia/Seoul' => '(UTC+09:00) Seoul',
        'Asia/Yakutsk' => '(UTC+09:00) Yakutsk',
        'Australia/Adelaide' => '(UTC+09:30) Adelaide',
        'Australia/Darwin' => '(UTC+09:30) Darwin',
        'Australia/Sydney' => '(UTC+10:00) Sydney, Canberra, Brisbane',
        'Australia/Hobart' => '(UTC+10:00) Hobart',
        'Asia/Vladivostok' => '(UTC+10:00) Vladivostok',
        'Australia/Lord_Howe' => '(UTC+10:30) Lord Howe Island',
        'Etc/GMT-11' => '(UTC+11:00) Solomon Is., New Caledonia',
        'Asia/Magadan' => '(UTC+11:00) Magadan',
        'Pacific/Norfolk' => '(UTC+11:30) Norfolk Island',
        'Asia/Anadyr' => '(UTC+12:00) Anadyr, Kamchatka',
        'Pacific/Auckland' => '(UTC+12:00) Auckland, Wellington',
        'Etc/GMT-12' => '(UTC+12:00) Fiji, Kamchatka, Marshall Is.',
        'Pacific/Chatham' => '(UTC+12:45) Chatham Islands',
        'Pacific/Tongatapu' => '(UTC+13:00) Nuku Alofa',
        'Pacific/Kiritimati' => '(UTC+14:00) Kiritimati'
    );

当日期从您的数据库中出来时,它们需要从UTC转换为用户的时区。

$timestamp = time(); // or a timestamp from your DB

# create server and user timezone objects
$fromZone = new DateTimeZone('UTC'); // UTC
$toZone = new DateTimeZone($userTimezone); // Europe/London, or whatever it happens to be

$time = date('Y-m-d H:i:s', $timestamp);
$dt = new DateTime($time, $fromZone);
echo $dt->format('Y-m-d H:i:s'); // Still UTC

$dt->setTimezone($toZone);
echo $dt->format('Y-m-d H:i:s'); // Converted

您需要通过 WeBid 应用程序并修改适当的部分以正确转换。


因此,使用上述代码,您需要编写一些函数,这些函数执行以下操作:

function getConvertedDateTimeObject($timestamp, $userTimezone){
    # create server and user timezone objects
    $fromZone = new DateTimeZone('UTC'); // UTC
    $toZone = new DateTimeZone($userTimezone); // Europe/London, or whatever it happens to be

    $time = date('Y-m-d H:i:s', $timestamp);
    $dt = new DateTime($time, $fromZone);
    $dt->setTimezone($toZone);
    return $dt;
}

function getUserTimestamp($timestamp, $userTimezone){
    $dt = getConvertedDateTimeObject($timestamp, $userTimezone);
    return $dt->getTimestamp();
}

function getUserOffset($timestamp, $userTimezone){
    $dt = getConvertedDateTimeObject($timestamp, $userTimezone);
    return $dt->getOffset();
}

然后在你的类中:

$this->ctime = getUserTimestamp(time(), $userTimezone); // I assume you have access to the user's timezone?
$this->tdiff = getUserOffset(time(), $userTimezone);

非常感谢您的回复,但是这个更改涉及到所有日期拍卖应用程序范围内的重要更改,我更喜欢一种方法,可以应用 $system->ctime 和 $system->tdiff 的正确值,以避免在整个应用程序中进行大型测试案例和繁重的质量保证。 - Luc Laverdure
这并不难实现 - 我已经更新了我的答案。我希望您可以在设置ctime和tdiff的类中访问已登录用户的时区。 - bcmcfc

0

对于bcmcfc的答案(基本上已经说得很清楚了)的另一种解决方案:

从WeBid中删除任何时区支持(至少禁用任何自定义),并将所有内容保存为UTC时间(如果尚未这样做)。

使用Javascript修改界面以显示日期和时间戳。浏览器将根据浏览器或操作系统的设置正确转换Unix时间戳(不要忘记乘以1000,Javascript的Date期望毫秒,而不是PHP中的秒)。

例如,您可以输出<script>document.write(new Date(1345282423*1000).toString());</script>,而不是输出2012-08-18 09:33:43,其中1345282423直接从数据库中获取,或使用strtotime

您只需要更改显示日期和时间戳的函数,并禁用时区自定义(强制使用UTC / GMT)(不要忘记date_default_timezone_set(“UTC”);)。

我认为这似乎很微不足道。


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