从PHP';s时区值

从PHP';s时区值,php,timezone,icalendar,Php,Timezone,Icalendar,我正在向我的事件日历应用程序添加一项功能,以便为事件提供iCalendar(ics)文件下载。我想生成,但我所拥有的只是来自的值。以下是Outlook为东部时间(美国和加拿大)生成的VTIMEZONE组件的示例: BEGIN:VTIMEZONE TZID:Eastern Time (US & Canada) BEGIN:STANDARD DTSTART:16011104T020000 RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11 TZOFFSETFROM:

我正在向我的事件日历应用程序添加一项功能,以便为事件提供iCalendar(ics)文件下载。我想生成,但我所拥有的只是来自的值。以下是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的“美国/纽约”时区,但我如何自动生成它呢?

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

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


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

PHP有一个国际化扩展名为intl,它是IBM底层库的一个简单包装器,它完成了所有脏活

现在,ICU实现了,能够生成具有夏令时规则的iCalVTIMEZONE组件(使用Olson tz db转换)


太好了!!但是该类在PHP中没有获得包装器。

PHP的
DateTimezone
类与奥尔森时区数据库一起工作,并且具有一些(有限的)访问偏移量、转换和短名称的权限

根据,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;
}
use\Sabre\VObject;
/**
*返回Olson时区标识符的VTIMEZONE组件
*具有覆盖给定日期范围的日光过渡。
*
*PHP日期函数中使用的@param字符串时区ID
*@param integer Unix时间戳,具有此时区中的第一个日期/时间
*@param integer Unix timestap,最后日期/时间在此时区
*
*@return混合了一个Sabre\VObject\Component对象,表示VTIMEZONE定义
*如果没有可用的时区信息,则为false
*/
函数generate_vtimezone($tzid,$from=0,$to=0)
{
如果(!$from)$from=time();
如果(!$to)$to=$from;
试一试{
$tz=新建\日期时区($tzid);
}
捕获(\异常$e){
返回false;
}
//提前/提前一年完成所有过渡
$year=86400*360;
$transitions=$tz->getTransitions($from-$year,$to+$year);
$vt=新的VOObject\Component('VTIMEZONE');
$vt->TZID=$tz->getName();
$std=null;$dst=null;
foreach($i=>$trans){
$cmp=null;
//跳过第一个条目。。。
如果($i==0){
//…但请记住下一个TZOFFSETFROM值的偏移量
$tzfrom=$trans['offset']/3600;
继续;
}
//夏令时定义
如果($trans['isdst'])){
$t_dst=$trans['ts'];
$dst=新VObject\Component(“日光”);
$cmp=$dst;
}
//标准时间定义
否则{
$t_std=$trans['ts'];
$std=新对象\组件(“标准”);
$cmp=$std;
}
如果($cmp){
$dt=新日期时间($trans['time']);
$offset=$trans['offset']/3600;
$cmp->DTSTART=$dt->format('Ymd\THis');
$cmp->TZOFFSETFROM=sprintf(“%s%02d%02d',$tzfrom>=0?“+”:“”,地板($tzfrom),($tzfrom-地板($tzfrom))*60);
$cmp->tzoffetto=sprintf(“%s%02d%02d”,$offset>=0?“+”:“”,地板($offset),($offset-floor($offset))*60);
//添加缩写时区名称(如果可用)
如果(!empty($trans['abbr'])){
$cmp->TZNAME=$trans['abbr'];
}
$tzfrom=$offset;
$vt->add($cmp);
}
//我们涵盖了整个约会范围
如果($std&&$dst&&min($t_std,$t_dst)<$from&&max($t_std,$t_dst)>$to){
打破
}
}
//添加X-MICROSOFT-CDO-TZID(如果可用)
$microsoftExchangeMap=array_flip(VObject\TimeZoneUtil::$microsoftExchangeMap);
如果(数组\键\存在($tz->getName(),$microsoftExchangeMap)){
$vt->add('X-MICROSOFT-CDO-TZID',$microsoftExchangeMap[$tz->getName());
}
返回$vt;
}
上面的代码示例使用该库创建
VTIMEZONE
定义,但可以轻松地重新编写以生成纯字符串输出

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


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

查看vtimezone类。@Zyava-感谢您的参考!这是一些糟糕的代码,但我会设法通过。它似乎对我也适用,但似乎不符合iCalendar规范(RFC2445)。也许将所有内容转换为UTC,然后嵌入时区规范将是一个不错的选择。感谢您推荐这个库-Sabre VObject正是我想要的!关于如何生成纯字符串输出有什么想法吗?返回的对象看起来很复杂。@mozgras返回的对象有一个输出字符串的
serialize()
方法:
$vtimezone=generate\u vtimezone('Europe/Berlin');打印$vtimezone->serialize()下面是一个使用Sabre/VObject 4.x版的更新示例
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;
}