Extension:TimeZoneInfo/1.0

From MediaWiki.org
Jump to: navigation, search
<?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);
    }
 
}
Personal tools
Namespaces

Variants
Actions
Navigation
Support
Download
Development
Communication
Print/export
Toolbox