Last active
June 13, 2016 07:00
-
-
Save SimonSimCity/9950755 to your computer and use it in GitHub Desktop.
A script to guess the timezone based on data provided in a VTIMEZONE object in an iCal file. The name is a non-olson-name. Those with olson-names are easy. Originally written for https://github.com/fruux/sabre-vobject/issues/44
This file contains hidden or 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 | |
$timezones = DateTimeZone::listIdentifiers(); | |
$VTIMEZONE = array( | |
"TZID" => "Mitteleuropäische Zeit", | |
"DAYLIGHT" => array( | |
"RULE" => array( | |
"FREQ" => "YEARLY", | |
"BYDAY" => "-1SU", | |
"BYMONTH" => 3, | |
), | |
"DTSTART" => "20140330T010000", | |
"TZOFFSETFROM" => "+0100", | |
"TZOFFSETTO" => "+0200", | |
), | |
"STANDARD" => array( | |
"RULE" => array( | |
"FREQ" => "YEARLY", | |
"BYDAY" => "-1SU", | |
"BYMONTH" => 10, | |
), | |
"DTSTART" => "19701101T020000", | |
"TZOFFSETFROM" => "+0200", | |
"TZOFFSETTO" => "+0100", | |
) | |
); | |
function parseOffsetToInteger($offset) { | |
$time = ($offset[1].$offset[2] * 60) + ($offset[3].$offset[4]); | |
// in seconds please .. | |
$time = $time * 60; | |
if ($offset[0] === "-") { | |
$time = $time *-1; | |
} | |
return $time; | |
} | |
// Source: http://stackoverflow.com/questions/924246/get-the-first-or-last-friday-in-a-month | |
function get_date($month, $year, $week, $day, $direction) { | |
if($direction > 0) | |
$startday = 1; | |
else | |
$startday = date('t', mktime(0, 0, 0, $month, 1, $year)); | |
$start = mktime(0, 0, 0, $month, $startday, $year); | |
$weekday = date('N', $start); | |
if($direction * $day >= $direction * $weekday) | |
$offset = -$direction * 7; | |
else | |
$offset = 0; | |
$offset += $direction * ($week * 7) + ($day - $weekday); | |
return mktime(0, 0, 0, $month, $startday + $offset, $year); | |
} | |
foreach($timezones as $timezone) { | |
if ($timezone !== "Europe/Berlin") | |
continue; | |
$timeZones = array(); | |
$timeZones[] = parseOffsetToInteger($VTIMEZONE["DAYLIGHT"]["TZOFFSETFROM"]); | |
$timeZones[] = parseOffsetToInteger($VTIMEZONE["DAYLIGHT"]["TZOFFSETTO"]); | |
$timeZones[] = parseOffsetToInteger($VTIMEZONE["STANDARD"]["TZOFFSETFROM"]); | |
$timeZones[] = parseOffsetToInteger($VTIMEZONE["STANDARD"]["TZOFFSETTO"]); | |
// Reduce the list of timezones to those, who're valuable to take a closer look at. | |
$now = new DateTime("now", new DateTimeZone($timezone)); | |
if (!in_array($now->getOffset(), $timeZones)) { | |
continue; | |
} else { | |
echo "Matched one of the timezones: $timezone\n"; | |
} | |
// Guess timezone based on DTSTART | |
$transitions = timezone_transitions_get(new DateTimeZone($timezone)); | |
foreach($transitions as $transition) { | |
foreach(array("DAYLIGHT" => $VTIMEZONE["DAYLIGHT"], "STANDARD" => $VTIMEZONE["STANDARD"]) as $name => $definition) { | |
try { | |
if ((new DateTime($definition["DTSTART"]))->format("c") == (new DateTime($transition["time"]))->format("c") | |
&& $transition["offset"] === parseOffsetToInteger($definition["TZOFFSETTO"])) { | |
echo "[$name] Timezone found by DTSTART: $timezone\n"; | |
} | |
} catch (Exception $e) { | |
if (strpos($e->getMessage(), "DateTime::__construct(): Failed to parse time string") !== 0) { | |
throw $e; | |
} | |
} | |
} | |
} | |
// Guess timezone based on RULES for now | |
foreach(array("DAYLIGHT" => $VTIMEZONE["DAYLIGHT"], "STANDARD" => $VTIMEZONE["STANDARD"]) as $name => $definition) { | |
$rules = $definition["RULE"]; | |
if ($rules["FREQ"] !== "YEARLY") { | |
echo "- [$name] Unknown frequency {$rules['FREQ']}"; | |
continue; | |
} | |
$dayMap = array( | |
"MO" => 1, | |
"TU" => 2, | |
"WE" => 3, | |
"TH" => 4, | |
"FR" => 5, | |
"SA" => 6, | |
"SU" => 7, | |
); | |
date_default_timezone_set('UTC'); | |
$date = date(DATE_ATOM, get_date( | |
$rules["BYMONTH"], | |
date("Y"), | |
$rules["BYDAY"][1], // Count of weeks to go back or forth | |
$dayMap[$rules["BYDAY"][2].$rules["BYDAY"][3]], // Weekday as number | |
$rules["BYDAY"][0] === "-" ? -1 : 1 | |
)); | |
$dtstart = new DateTime($definition["DTSTART"]); | |
$dateTime = new DateTime($date); | |
$dateTime->setTime( | |
$dtstart->format("H")+0, | |
$dtstart->format("i")+0, | |
$dtstart->format("s")+0 | |
); | |
// Set time back for 1sec to get behind the time-transition | |
$dateTime->sub(new DateInterval("PT1S")); | |
// Try the first offset | |
$dateTime->setTimezone(new DateTimeZone($timezone)); | |
if ($dateTime->getOffset() !== parseOffsetToInteger($definition["TZOFFSETFROM"])) { | |
echo "- [$name] Warning for TZOFFSETFROM: {$dateTime->getOffset()} - {$definition['TZOFFSETFROM']} | ({$timezone})\n"; | |
// Maybe the system set one hour too late for the switch to StandardTime? Like M$ does in the provided example ... :) | |
if (parseOffsetToInteger($definition["TZOFFSETFROM"]) < parseOffsetToInteger($definition["TZOFFSETTO"])) { | |
// Set it back to UTC, add two seconds to get past the time-transition | |
$dateTime->setTimezone(new DateTimeZone("UTC")); | |
$dateTime->sub(new DateInterval("PT1H")); | |
// Try again for the next offset | |
$dateTime->setTimezone(new DateTimeZone($timezone)); | |
if ($dateTime->getOffset() !== parseOffsetToInteger($definition["TZOFFSETTO"])) { | |
echo "- [$name] Skipped for TZOFFSETTO: {$dateTime->getOffset()} - {$definition['TZOFFSETTO']} | ({$timezone})\n"; | |
continue 2; | |
} | |
} | |
} | |
// Set it back to UTC, add two seconds to get past the time-transition | |
$dateTime->setTimezone(new DateTimeZone("UTC")); | |
$dateTime->add(new DateInterval("PT2S")); | |
// Try again for the next offset | |
$dateTime->setTimezone(new DateTimeZone($timezone)); | |
if ($dateTime->getOffset() !== parseOffsetToInteger($definition["TZOFFSETTO"])) { | |
echo "- [$name] Skipped for TZOFFSETTO: {$dateTime->getOffset()} - {$definition['TZOFFSETTO']} | ({$timezone})\n"; | |
continue 2; | |
} | |
echo "[$name] Timezone found by RULE: $timezone\n"; | |
} | |
// Guess timezone based on RULES for registered timezone-transitions | |
// TODO: Add another loop, that checks if the timezone-transitions match the defined timezone-rules | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment