Last active
February 15, 2023 21:15
-
-
Save thomascube/47ff7d530244c669825736b10877a200 to your computer and use it in GitHub Desktop.
VTIMEZONE component for a Olson timezone identifier with daylight transitions.Solution to this StackOverflow question: https://stackoverflow.com/questions/6682304/generating-an-icalender-vtimezone-component-from-phps-timezone-value/25971680
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
use \Sabre\VObject; | |
// use composer autoloader | |
require_once 'vendor/autoload.php'; | |
/** | |
* 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); | |
$vcalendar = new VObject\Component\VCalendar(); | |
$vt = $vcalendar->createComponent('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 = $vcalendar->createComponent('DAYLIGHT'); | |
$cmp = $dst; | |
} | |
// standard time definition | |
else { | |
$t_std = $trans['ts']; | |
$std = $vcalendar->createComponent('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 ? '+' : '-', abs(floor($tzfrom)), ($tzfrom - floor($tzfrom)) * 60); | |
$cmp->TZOFFSETTO = sprintf('%s%02d%02d', $offset >= 0 ? '+' : '-', abs(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; | |
} | |
$vtimezone = generate_vtimezone('Europe/Berlin'); | |
print $vtimezone->serialize(); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
You can use
sprintf()
to do both the padding and the sign. However, to get correct rounding towards zero for the hour part, you cannot usefloor()
because it doesn't go towards the zero:The
%+03d
uses value3
because that number counts the whole width of the field including the sign.Also note the fixed rounding for e.g.
$tzfrom = -7.25
.That would still be buggy for hypothetical timezones between -0059 and -0001 but I'm pretty sure those do not exist in reality. This happens because
sprintf()
cannot emit-00
for "negative zero". If you want to handle such values, too, I think you will have to use ternary operator (? :
) or a properif
. In that case, the best I can think of is