Skip to content

Instantly share code, notes, and snippets.

@SimonSimCity
Last active June 13, 2016 07:00
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SimonSimCity/9950755 to your computer and use it in GitHub Desktop.
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
<?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