Extension:TimeZoneInfo/1.0
From MediaWiki.org
<?php /* * Timezone info extension. * * This extension displays several types of useful (?) information relating * to timezones. It is expected to be of some use in Wikis which serve a * distributed community of users, where people want to know what time it * is for other people. * * Install in the usual way: put this file (TimeZoneInfo.php) in your * extensions directory, and add this to LocalSettings.php: * * require_once( "extensions/TimeZoneInfo.php" ); * * This extension adds three tags: * * <tzchart class=wikitable> * zonename * zonename * ... * </tzchart> * * displays a chart of time conversions for the listed zones. * * <tztrans class=wikitable> * zonename * zonename * ... * </tztrans> * * displays a table of daylight <--> standard time transitions for the * listed time zones. * * <tzlist filter="foo" /> * * displays a table of all timezones (or zones matching the filter). * * For more info on each tag, see the hook function comments below. */ $wgExtensionFunctions[] = 'wfTimeZoneInfo'; $wgExtensionCredits['parserhook'][] = array( 'name'=>'TimeZoneInfo', 'author'=>'Johan the Ghost', 'url'=>'http://www.mediawiki.org/wiki/Extension:TimeZoneInfo', 'description'=>'display a variety of timezone information', ); /* * Install TimeZoneInfo extension. */ function wfTimeZoneInfo() { new TimeZoneInfo(); } class TimeZoneInfo { /* * Set up the colours used by the timezone chart. These are the colours * used to represent local nighttime, twilight (morning/evening), * and daytime. */ var $colours = array( "#c0c0c0", "#d0d0ff", "#c0ffff" ); /* * Set up the time bands used by the timezone chart. Each hour of * local time is set to 0 to make it show as night, 1 for twilight, * 2 for daytime. */ var $bands = array( 0, 0, 0, 0, 0, 0, 0, 0, // 0-7: night 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, // 8-18: twi, day, twi 0, 0, 0, 0, 0 // 19-23: night ); //////////////////////////////////////////////////////////////////////// // Constructor //////////////////////////////////////////////////////////////////////// /* * Set up the TimeZoneInfo extension. */ function TimeZoneInfo() { global $wgParser; // Install parser hooks for the new tags we're creating. $wgParser->setHook( 'tzchart', array($this, 'hookTimeZoneChart') ); $wgParser->setHook( 'tztrans', array($this, 'hookTimeZoneTrans') ); $wgParser->setHook( 'tzlist', array($this, 'hookTimeZoneList') ); } //////////////////////////////////////////////////////////////////////// // The <tzchart> Tag //////////////////////////////////////////////////////////////////////// /* * Parser hook for the "<tzchart> ... </tzchart>" tag. This tag * creates a conversion chart showing the relationships between a * number of specified timezones. Timezones with fractional hour * offsets (eg. India) are handled quite well. * * The tag takes the following parameters: * align=xxx passed to the generated table * start=n start the chart at the given UTC hour; * default 0 (midnight UTC). * * The tag text consists of the timezone list, one name per line. * Each name may be followed by a user-friendly label. * * Example: * <tzchart align=right start=2> * America/Los_Angeles * Europe/Paris * Asia/Calcutta * </tzchart> */ function hookTimeZoneChart($tagText, $argv, &$parser) { try { $utc = new DateTimeZone('UTC'); } catch (Exception $e) { return "Timezone UTC unknown???"; } // Get the timezone definitions based on the tag text. $zones = $this->getZones($tagText); // See if we have mixed zones; ie. some zones with whole-hour // offsets, and some zones with part-hour offsets. $mixed = false; foreach ($zones as $zone) if ($zone['split'] != $zones[0]['split']) $mixed = true; // See what the start and end times are. if (isset($argv['start'])) $start = $argv['start']; else $start = 0; $end = $start + 24; $tparms = ''; if (@$argv['align']) $tparms .= "align=" . $argv['align']; $wikiText = "{| class=wikitable $tparms\n"; // Do the table header. $wikiText .= "|-\n"; foreach ($zones as $zone) { $wikiText .= "!" . $zone['abbr'] . "\n"; } // Now do the chart. $time = new DateTime('now', $utc); for ($h = $start; $h < $end; ++$h) for ($m = 0; $m < 60; $m += $mixed ? 30 : 60) $wikiText .= $this->doChartRow($zones, $mixed, $h, $m, $h == $start, $time); // For a mixed table, do the final half-cells. Ordering doesn't // matter; these will fill in the odd gaps. if ($mixed) { $wikiText .= "|- style=\"height:7px\"\n"; foreach ($zones as $zone) if (!$zone['split']) $wikiText .= "|\n"; } $wikiText .= "|}\n"; // Done. $localParser = new Parser(); $output = $localParser->parse($wikiText, $parser->mTitle, $parser->mOptions, false); return $output->getText(); } function doChartRow($zones, $mixed, $h, $m, $first, &$time) { /* * It would be great not to have to do non-mixed tables differently. * However, this fails because an empty row is ignored; ie. in this: * |- * |- * | foo... * the first row (|-) is ignored. So instead, for non-mixed tables, * we drop the rowspan. */ $wikiText = ''; $span = $mixed ? "rowspan=2" : ""; $style = "style=\"padding-top: 0; padding-bottom:0;\""; $wikiText .= "|- style=\"height:7px\"\n"; foreach ($zones as $zone) { if ($mixed) { if ($first && $m == 0 && $zone['split']) { // Do an initial half-cell for a split zone. Ordering // matters. $wikiText .= "|\n"; continue; } else if (($zone['split'] == 0) != ($m == 0)) continue; } $time->setTime($h % 24, $m, 0); $time->modify("+" . $zone['off'] . " seconds"); $band = $this->bands[$time->format('G')]; $col = $this->colours[$band]; $wikiText .= "| $span bgcolor=\"$col\" $style | " . $time->format('H:i') . "\n"; } return $wikiText; } //////////////////////////////////////////////////////////////////////// // The <tztrans> Tag //////////////////////////////////////////////////////////////////////// /* * Parser hook for the "<tztrans> ... </tztrans>" tag. This tag formats * a list of timezone standard<->daylight time transitions as a Wiki table. * * The tag takes the following parameters: * align=xxx passed to the generated table * * The tag text consists of the timezone list, one name per line. * Each name may be followed by a user-friendly label. * * Example: * <tztrans align=right> * America/Los_Angeles LA * Europe/Paris * </tztrans> */ function hookTimeZoneTrans($tagText, $argv, &$parser) { // Get the timezone definitions based on the tag text. $zones = $this->getZones($tagText); $now = time(); $tparms = ''; if (@$argv['align']) $tparms .= "align=" . $argv['align']; $wikiText = "{| class=wikitable $tparms\n"; $wikiText .= "|\n"; foreach ($zones as $zone) $wikiText .= $this->formatTransitions($zone, $now); $wikiText .= "|}\n"; $output = $parser->parse($wikiText, $parser->mTitle, $parser->mOptions, true, false ); return $output->getText(); } function formatTransitions(&$zone, $now) { $name = $zone['name']; $abbr = $zone['abbr']; $wikiText = ''; $transitions = $this->getTransitions($zone, $now, $now + 3600 * 24 * 366, 2); $wikiText .= ";$name ($abbr)\n"; if (count($transitions) == 0) $wikiText .= ":No change\n"; else { foreach ($transitions as $tinfo) $wikiText .= ":" . $tinfo['date'] . ": " . $tinfo['text'] . "\n"; } return $wikiText; } //////////////////////////////////////////////////////////////////////// // The <tzlist> Tag //////////////////////////////////////////////////////////////////////// /* * Parser hook for the "<tzlist />" tag. This tag produces a table * of known timezones. * * The tag takes the following parameters: * filter=xxx if supplied, only zones whose name contains * the given string are shown. * * The tag text is ignored. * * Example: * <tzlist filter="Atlantic/" /> */ function hookTimeZoneList($tagText, $argv, &$parser) { $wikiText = ''; $now = time(); $wikiText .= "{| class=wikitable\n"; $wikiText .= "! Zone\n"; $wikiText .= "! Current offset\n"; $wikiText .= "! Next change\n"; $wikiText .= "! Second change\n"; $zoneIds = DateTimeZone::listIdentifiers(); foreach ($zoneIds as $zone) { if (@$argv['filter'] && strpos($zone, $argv['filter']) === false) continue; $zinfo = $this->getZone($zone); if (!$zinfo) continue; $transitions = $this->getTransitions($zinfo, $now, $now + 3600 * 24 * 366, 2); $tinfo1 = @$transitions[0]; $tinfo2 = @$transitions[1]; $off = $this->usertime($zinfo['off'], "ahead of", "same as", "behind") . " UTC"; $wikiText .= "|-\n"; $wikiText .= "| " . $zone . "\n"; $wikiText .= "| " . $off . ($zinfo['dst'] ? " (dst)" : "") . "\n"; if ($tinfo1) { $wikiText .= "| " . $tinfo1['date'] . ": " . $tinfo1['text'] . "\n"; if ($tinfo2) $wikiText .= "| " . $tinfo2['date'] . ": " . $tinfo2['text'] . "\n"; } } $wikiText .= "|}\n"; $output = $parser->parse($wikiText, $parser->mTitle, $parser->mOptions, true, false ); return $output->getText(); } //////////////////////////////////////////////////////////////////////// // Zone Utilities //////////////////////////////////////////////////////////////////////// /* * Given a list of timezones, get the zone info for them. * * $text timezone list, one name per line. Each name may * be followed by a user-friendly label. * * Returns an array of records, each containing the following fields: * 'rec' the DateTimeZone object for this zone. * 'dst' true iff the zone is currently in DST. * 'off' the current UTC offset in seconds. * 'split' true iff the offset is not a whole number of hours. * 'name' the user-friendly name of this zone. * 'label' a label for the zone and DST/standard status. * 'abbr' abbreviation for the zone. */ function getZones($text) { $zones = array(); // Scan the text for the list of timezones. $zonenames = explode("\n", $text); foreach ($zonenames as $zn) { // Break the entry into zonename and user-friendly name. $zn = trim($zn); $parts = explode(" ", $zn); if (!@$parts[0]) continue; // Get the zone info. $zinfo = $this->getZone($parts[0], @$parts[1]); if ($zinfo) $zones[] = $zinfo; } return $zones; } /* * Given a timezone name, get the zone info, and enquire for some * additional info about it. * * $zn the timezone name, as in "America/Los_Angeles". * $name a user-supplied name for the zone; if omitted, * $zn is used. * * Returns null if the zone doesn't exist; otherwise, a record * containing the following fields: * 'rec' the DateTimeZone object for this zone. * 'dst' true iff the zone is currently in DST. * 'off' the current UTC offset in seconds. * 'split' true iff the offset is not a whole number of hours. * 'name' the user-friendly name of this zone. * 'label' a label for the zone and DST/standard status. * 'abbr' abbreviation for the zone. */ function getZone($zn, $name=null) { try { $zone = new DateTimeZone($zn); } catch (Exception $e) { return null; } if (!$name) $name = $zn; $now = new DateTime('now', $zone); $offset = $zone->getOffset($now); $dst = $now->format('I'); $abbr = $now->format('T'); $label = $name . "<br>" . ($dst ? "DST" : "Std"); return array('rec' => $zone, 'dst' => $dst ? true : false, 'off' => $offset, 'split' => $offset / 60 % 60 != 0, 'name' => $name, 'label' => $label, 'abbr' => $abbr); } //////////////////////////////////////////////////////////////////////// // Transition Utilities //////////////////////////////////////////////////////////////////////// /* * Get the standard/daylight time transitions for a given tmiezone. * * $zone the zone info record, as returned by getZone(). * $start if not null, must be a timestamp; return * transitions starting at that time. * $end if not null, must be a timestamp; return * transitions up to that time. * $max if not null, the maximum nunber of transitions * we want. * * Returns a array of records, each containing the following fields: * 'date' the date of the transition * 'text' text string descbribing the transition * 'type' the type of time we're moving to: daylight or * standard * 'offset' the UTC offset after the transition */ function getTransitions(&$zone, $start, $end, $max) { $timezone = $zone['rec']; $transitions = $timezone->getTransitions(); $trans = array(); $prevOff = null; foreach ($transitions as $tran) { if ($start && $tran['ts'] < $start || $end && $tran['ts'] > $end) { $prevOff = $tran['offset']; continue; } // Now get the transition data. $trans[] = $this->getTransition($zone, $tran, $prevOff); // See if we're done. if ($max > 0 && count($trans) >= $max) break; $prevOff = $tran['offset']; } return $trans; } /* * Format the next standard/daylight time transition for a given timezone. * * $zone the zone info record, as returned by getZone(). * $tran the transition to format. * $prevOff the previous UTC offset in seconds. * * Returns a record containing the following fields: * 'date' the date of the transition * 'text' text string descbribing the transition * 'type' the type of time we're moving to: daylight or * standard * 'offset' the UTC offset after the transition */ function getTransition(&$zone, &$tran, $prevOff) { // When is the transition? $when = new DateTime("@" . ($tran['ts'] + $prevOff), $zone['rec']); $date = $when->format("j M"); // How do the clocks move? $diff = null; if ($prevOff !== null) { $ds = $tran['offset'] - $prevOff; $diff = $this->usertime($ds, "forward", "", "back"); } // What is the new zone? $type = $tran['isdst'] ? "daylight" : "standard"; // Now format it. if ($diff) $text = "$diff"; else $text = "to $type time"; // Return the textual description, and the new offset. return array('date' => $date, 'text' => $text, 'type' => $type, 'offset' => $tran['offset']); } //////////////////////////////////////////////////////////////////////// // Basic Utilities //////////////////////////////////////////////////////////////////////// /* * Convert a time interval in seconds to a user-friendly string; * such as "17 hours ago", "1 day 17 hours ago", "3 days ago". * * $secs the interval, in seconds; may be negative. * $pos the suffix used if the value is positive. * $pos the string returned if the value is zero. * $neg the suffix used if the value is negative. */ function usertime($secs, $pos, $zero, $neg) { if ($secs == 0) return $zero; $sign = $secs > 0 ? $pos : $neg; $secs = abs($secs); $s = (int) ($secs % 60); $rs = round($secs % 60); $secs /= 60; $m = (int) ($secs % 60); $rm = round($secs % 60); $secs /= 60; $h = (int) ($secs % 24); $rh = round($secs % 24); $secs /= 24; $d = (int) $secs; $rd = round($secs); $res = array(); if ($d >= 2) $res[] = $rd . " day" . ($d > 1 ? "s" : ""); else { if ($d != 0) $res[] = $d . " day" . ($d > 1 ? "s" : ""); if ($h >= 2) $res[] = $rh . " hour" . ($h > 1 ? "s" : ""); else { if ($h != 0) $res[] = $h . " hour" . ($h > 1 ? "s" : ""); if ($m >= 2) $res[] = $rm . " min" . ($m > 1 ? "s" : ""); else { if ($m != 0) $res[] = $m . " min" . ($m > 1 ? "s" : ""); if ($s > 0) $res[] = $rs . " sec" . ($s > 1 ? "s" : ""); } } } $res[] = $sign; return implode(" ", $res); } }
