Son*_*nny 6 php icalendar timezone
我正在为我的活动日历应用程序添加一项功能,以便为事件提供iCalendar(ics)文件下载.我想生成VTIMEZONEComponent,但我所拥有的只是PHP的Timezone值date_default_timezone_get().以下VTIMEZONE是Outlook生成的东部时间组件(美国和加拿大)的示例:
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的"America/New_York"时区一样,但是我如何自动生成呢?
PHP的DateTimezone类与Olson时区数据库一起使用,并且有一些(有限的)方法来访问偏移,转换和短名称.
根据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系统匹配.
VTIMEZONE解决此问题的另一种方法是采用一个固定的基准时区(例如上面的“美国/纽约”/“东部时间(美国和加拿大)”区域),而不是尝试为目标时区生成配置。并VEVENT使用 PHP 的类将 的值转换为它DateTime。
$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"
收件人的日历客户端将自动将时间转换为目标时区!
不是直接答案,但它解决了我的问题。