从 PHP 时区值生成 iCalendar VTIMEZONE 组件

Posted

技术标签:

【中文标题】从 PHP 时区值生成 iCalendar VTIMEZONE 组件【英文标题】:Generating an iCalender VTIMEZONE Component from PHP's Timezone Value 【发布时间】:2011-10-04 16:02:38 【问题描述】:

我正在向我的活动日历应用程序添加一项功能,以便为活动提供 iCalendar (ics) 文件下载。我想生成VTIMEZONE Component,但我只有来自date_default_timezone_get()php's Timezone 值。下面是 Outlook 生成的东部时间(美国和加拿大)VTIMEZONE 组件示例:

BEGIN:VTIMEZONE
TZID:Eastern Time (US & Canada)
BEGIN:STANDARD
DTSTART:16011104T020000
RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11
TZOFFSETFROM:-0400
TZOFFSETTO:-0500
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:16010311T020000
RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3
TZOFFSETFROM:-0500
TZOFFSETTO:-0400
END:DAYLIGHT
END:VTIMEZONE

这类似于 PHP 的“美国/纽约”时区,但我将如何自动生成它?

【问题讨论】:

看iCalcreatorvtimezone类。 @Zyava - 感谢您的参考!那是一些悲惨的代码,但我会尽力解决。 【参考方案1】:

PHP 的 DateTimezone 类与 Olson 时区数据库一起使用,并且有一些(有限的)methods 可以访问偏移量、转换和短名称。

根据RFC 5545,RRULE 属性是可选的,因此我们应该能够使用内置实用程序生成有效的VTIMEZONE 定义。按照 RFC 的建议,以下函数正是这样做的:

use \Sabre\VObject;

/**
 * Returns a VTIMEZONE component for a Olson timezone identifier
 * with daylight transitions covering the given date range.
 *
 * @param string Timezone ID as used in PHP's Date functions
 * @param integer Unix timestamp with first date/time in this timezone
 * @param integer Unix timestap with last date/time in this timezone
 *
 * @return mixed A Sabre\VObject\Component object representing a VTIMEZONE definition
 *               or false if no timezone information is available
 */
function generate_vtimezone($tzid, $from = 0, $to = 0)

    if (!$from) $from = time();
    if (!$to)   $to = $from;

    try 
        $tz = new \DateTimeZone($tzid);
    
    catch (\Exception $e) 
        return false;
    

    // get all transitions for one year back/ahead
    $year = 86400 * 360;
    $transitions = $tz->getTransitions($from - $year, $to + $year);

    $vt = new VObject\Component('VTIMEZONE');
    $vt->TZID = $tz->getName();

    $std = null; $dst = null;
    foreach ($transitions as $i => $trans) 
        $cmp = null;

        // skip the first entry...
        if ($i == 0) 
            // ... but remember the offset for the next TZOFFSETFROM value
            $tzfrom = $trans['offset'] / 3600;
            continue;
        

        // daylight saving time definition
        if ($trans['isdst']) 
            $t_dst = $trans['ts'];
            $dst = new VObject\Component('DAYLIGHT');
            $cmp = $dst;
        
        // standard time definition
        else 
            $t_std = $trans['ts'];
            $std = new VObject\Component('STANDARD');
            $cmp = $std;
        

        if ($cmp) 
            $dt = new DateTime($trans['time']);
            $offset = $trans['offset'] / 3600;

            $cmp->DTSTART = $dt->format('Ymd\THis');
            $cmp->TZOFFSETFROM = sprintf('%s%02d%02d', $tzfrom >= 0 ? '+' : '', floor($tzfrom), ($tzfrom - floor($tzfrom)) * 60);
            $cmp->TZOFFSETTO   = sprintf('%s%02d%02d', $offset >= 0 ? '+' : '', floor($offset), ($offset - floor($offset)) * 60);

            // add abbreviated timezone name if available
            if (!empty($trans['abbr'])) 
                $cmp->TZNAME = $trans['abbr'];
            

            $tzfrom = $offset;
            $vt->add($cmp);
        

        // we covered the entire date range
        if ($std && $dst && min($t_std, $t_dst) < $from && max($t_std, $t_dst) > $to) 
            break;
        
    

    // add X-MICROSOFT-CDO-TZID if available
    $microsoftExchangeMap = array_flip(VObject\TimeZoneUtil::$microsoftExchangeMap);
    if (array_key_exists($tz->getName(), $microsoftExchangeMap)) 
        $vt->add('X-MICROSOFT-CDO-TZID', $microsoftExchangeMap[$tz->getName()]);
    

    return $vt;

上面的代码示例使用Sabre VObject 库来创建VTIMEZONE 定义,但可以很容易地重写以生成纯字符串输出。

除了时区标识符之外,它还需要两个 unix 时间戳作为参数来定义我们需要时区信息的时间范围。然后列出给定时间范围内的所有相关转换。

我使用发送到 Outlook 的 iTip 邀请成功测试了生成的输出,否则无法将普通 Olson 时区标识符与 Microsoft 系统匹配。

【讨论】:

感谢您推荐这个库 - Sabre VObject 正是我想要的! 关于如何制作纯字符串输出的任何想法?看起来返回的对象很复杂。 @mozgras 返回的对象有一个输出字符串的serialize()方法:$vtimezone = generate_vtimezone('Europe/Berlin'); print $vtimezone-&gt;serialize(); 这是一个更新的示例,适用于 Sabre/VObject 版本 4.x gist.github.com/thomascube/47ff7d530244c669825736b10877a200【参考方案2】:

另一种解决方法,而不是尝试为目标时区生成 VTIMEZONE 配置,而是采用一个固定的基准时区(例如,“America/New_York”/“东部时间(美国和加拿大)" 区域),并使用 PHP 的 DateTime 类将 VEVENT 的值转换为它。

$Date = new DateTime( $event_date ); // this will be in the server's time zone

// convert it to the 'internal' time zone
$Date->setTimezone( new DateTimeZone( 'America/New_York' ) );

// ...

echo "BEGIN:VEVENT\n";
echo "DTSTART;TZID=America/New_York:" . $Date->format( 'Ymd\THis' ) . "\n"

收件人的日历客户端会自动将时间转换为目标时区!

不是直接的答案,但它解决了我的问题。

【讨论】:

它似乎也适用于我,但似乎不符合 iCalendar 规范 (RFC 2445)。也许将所有内容都转换为 UTC,然后嵌入该时区规范将是可行的方法。【参考方案3】:

PHP 有一个名为 intl 的国际化扩展,它是 IBM 底层库 ICU - International Components for Unicode 的简单包装器,可以完成所有繁琐的工作。

现在,ICU 实现了com.ibm.icu.util.VTimeZone,能够生成具有夏令时规则的 iCal VTIMEZONE 组件(使用 Olson tz db of transitions)。

太好了!!但是.. 那个类并没有在 PHP 中获得包装器。

【讨论】:

以上是关于从 PHP 时区值生成 iCalendar VTIMEZONE 组件的主要内容,如果未能解决你的问题,请参考以下文章

php 活动日历:将活动档案的iCalendar导出链接更改为webcal://

python 为洛杉矶健身俱乐部生成一个ics(icalendar / ical)。你必须知道俱乐部的身份证,你可以通过他们的网站获得

PHP 将时间从一个时区转换为另一个时区

PHP时区列表

PHP如何从国家代码和地区返回时区

从 GMT 值计算时区 - Android