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 系统匹配。