Extension:TimeZoneInfo/1.0

<?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: * *   *   zonename *  zonename *  ... *    * * displays a chart of time conversions for the listed zones. * *   *   zonename *  zonename *  ... *    * * displays a table of daylight <--> standard time transitions for the * listed time zones. * *   * * 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 Tag ////////////////////////////////////////////////////////////////////////

/*    * Parser hook for the " ... " 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: *         *     America/Los_Angeles *    Europe/Paris *    Asia/Calcutta *         */    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 Tag ////////////////////////////////////////////////////////////////////////

/*    * Parser hook for the " ... " 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: *         *     America/Los_Angeles LA     *     Europe/Paris *         */    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 Tag ////////////////////////////////////////////////////////////////////////

/*    * Parser hook for the " " 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: *     */   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. " " . ($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); }

}

?>