Extension:Age

From MediaWiki.org
Jump to: navigation, search
MediaWiki extensions manual - list
Crystal Clear action run.png
Age

Release status: beta

Implementation Tag
Description Calculate difference between two dates
Author(s) Mark Daly (chanurtalk)
Last version 0.7 (2008-11-16)
MediaWiki 1.13.X
License GPL
Download see below
Example <age from="1775-04-19" to="1783-09-03" />
Parameters

from="YYYY-MM-DD" to="YYYY-MM-DD" left="(" right=")" format="ymd" errbox="no"

Check usage and version matrix

Contents

What can this extension do? [edit]

This extension calculates the number of years, months, and days between two dates (FROM and TO attributes). The extension does not use any PHP built-in functions so it can calculate an answer for dates before 1970. The purpose of this extension is to automatically calculate the difference between two dates. It is useful in historical or genealogical research sites since the author only needs to know the two dates and how he or she wants to show that information; the tag automatically calculates the difference.

Usage [edit]

Only the FROM and TO attributes are (usually) required. All other attributes are optional. The <age> tag is used in-line with text on the page so the calculated age becomes part of the sentence, list, etc.

Syntax [edit]

<age from="YYYY-MM-DD" to="YYYY-MM-DD" [left="C"] [right="C"] [format="ymd"] [errbox="yes"] [zeronegatives="yes"] />
<age from="YYYY-MM-DD" to="YYYY-MM-DD" [left="C"] [right="C"] [format="y"] [errbox="no"] [zeronegatives="no"] ></age>

Any text within the <age></age> tags is ignored (e.g. <age>ignored</age>).

Attributes [edit]

Of the attributes below FROM and TO are required if $wgAgeDefaultToday is FALSE; otherwise, these attributes are optional. All other attributes are optional.

from="YYYY-MM-DD"
  • ISO formatted starting date
  • Only optional if $wgAgeDefaultToday = TRUE
to="YYYY-MM-DD"
  • ISO formatted ending date
  • Only optional if $wgAgeDefaultToday = TRUE
left="C"
  • Optional text to prepend (add to left) of answer
  • Most commonly "(" to enclose answer in parenthesis
right="C"
  • Optional text to append (add to right) of answer
  • Most commonly ")" to enclose answer in parenthesis
format="ymd" | format="ym" | format="y"
  • Controls how much of answer to show
    • ymd = X years, Y months, Z days
    • ym = X years, Y months (days are not shown)
    • y = X years (months and days are not shown
  • Defaults to "ymd"
errbox="yes" | errbox="no"
  • Controls whether error box appears (if, of course, there is an error)
  • Overrides $wgAgeErrorBox
zeronegatives="yes" | zeronegatives="no"
  • Controls whether negative date-differences are rendered as '0 days' or whether an error is displayed instead.
  • This is useful when omitting FROM (which defaults to 'today') and setting an expiry date using TO.
  • After the expiry date the difference will show as '0 days'. Basically a days-countdown with a '0 days' floor.
  • Overrides $wgAgeZeroNegatives

Download instructions [edit]

Copy and paste the code found below and place it in a file called $IP/extensions/Age/Age.php. Note: $IP stands for the root directory of your MediaWiki installation, the same directory that holds LocalSettings.php.

Copy and paste the language code found below and place it in a file called $IP/extensions/Age/Age.i18n.php.

Installation [edit]

To install this extension, add the following to LocalSettings.php:

require_once("$IP/extensions/Age/Age.php");

Configuration parameters [edit]

The following settings can be added to LocalSettings.php:

$wgAgeErrorBox = TRUE;
  • TRUE = show any errors using the 'errorbox' CSS format (usually a red box)
  • FALSE = show errors in-line with the page content
  • Not set in LocalSettings.php defaults to FALSE
  • This site-wide setting can be overridden using the 'errbox' attribute
$wgAgeDefaultToday = FALSE;
  • TRUE = automatically use current date if either FROM or TO attributes are not specified
    • Set this value then include FROM value in tag to calculate time between that date and the current date
    • Set this value then include TO value in tag to calculate time between current date and a future date (like a count down)
  • FALSE = FROM and TO values are required but will not default to the current date
  • Not set in LocalSettings.php defaults to FALSE
$wgAgeZeroNegatives = FALSE;
  • TRUE = display '0 days' when the time difference is negative
  • FALSE = negative time difference will display an error
  • Not set in LocalSettings.php defaults to FALSE

Code [edit]

Create a file called extensions/Age/Age.php and copy the following text into it. Be sure to add <?php and ?> before and after this text, respectively.

/** AGE MediaWiki Extension
 * Type: tag <age ... />
 *
 * == DESCRIPTION ==
 * This extension calculates the number of years plus months plus days between two ISO-8601 formatted
 * dates called FROM and TO. These values are specified as attributes to the <age> tag. All other
 * attributes are optional, incuding:
 *  from          = starting date in YYYY-MM-DD format
 *  to            = ending date in YYYY-MM-DD format
 *  left          = character or text to add to front of answer
 *  right         = character or text to add to end of answer
 *  format        = "ymd" (years, months, days); "ym" = (years, months); "y" = (years only)
 *                  default: "ymd" if not included
 *  errbox        = "yes" shows errors as class="errorbox"; "no" shows errors as regular text
 *                  default: "yes" if not included
 *  zeronegatives = "yes" displays nagetive time differences as '0 days'; "no" shows regular error text
 * 
 * == INSTALL ==
 *  1. Copy AGE.PHP and AGE.I18N.PHP into extensions/Age folder in your wiki
 *  2. Add line below to wiki LocalSettings.php
 *                      require_once("../$IP/extensions/Age/Age.php");
 *  3. Optionally add site-wide (global) settings after 'require_once' line
 *                      $wgAgeErrorBox          = TRUE; // TRUE means show box around any error messages so errors stand out clearly in text
 *                                                                              // FALSE or not set means do not display errors in a box
 *                                                                              // Can be overridden using 'errbox' attribute
 *                      $wgAgeDefaultToday      = TRUE; // TRUE means use current date if FROM or TO not provided
 *                                                                              // FALSE or not set means FROM and TO must be provided in tag attributes
 *  4. Save LocalSettings.php and upload to your site
 *  5. Add "<age ... />" tag to wiki pages (see SYNTAX and EXAMPLES)
 *
 * == LIMITS ==
 * This function only works for Gregorian calendar dates.
 * This function does not convert Gregorian to Julian calendar or any other calendar.
 * This function does not calculate BCE (before common era) dates; i.e. negative years do not work.
 * This tag expects a hyphen (-) between the year, month and day values in the FROM and TO attributes.
 * Partial dates (e.g. 2008-11) are not allowed. Each date must include year, month, day separate by hyphens.
 *  
 * == SYNTAX ==
 *  <age from="YYYY-MM-DD" to="YYYY-MM-DD" [left="C"] [right="C"] [format="ymd"] [errbox="yes"] [zeronegatives="yes"] />
 *  <age from="YYYY-MM-DD" to="YYYY-MM-DD" [left="C"] [right="C"] [format="y"] [errbox="no"] [zeronegatives="no"] ></age>
 *  
 *  This tage ignores any text within the tags (e.g. <age ...>ignored</age>).
 *  
 * == EXAMPLES ==
 *  <age from="1776-07-04" to="2008-11-12" />
 *     232 years, 4 months, 8 days
 *       
 *  <age from="2000-01-01" to="2008-11-12"></age>
 *     8 years, 10 months, 11 days
 *       
 *  <age from="2000-01-01" to="2008-11-12" left="(" right=")" />
 *     (8 years, 10 months, 11 days) 
 * 
 * == INTERNATIONALIZATION ==
 * This tag needs additional languauge translations (see Age.i18n.php file).
 * Attribute names and values can be included in the translation; only the 'age' tag ahd hyphen in dates is fixed.
 *
 * == CREDITS ==
 * @author Mark Daly <research@chanur.com>
 * @version 0.7
 * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
**/
 
 
/**
 * Protect against register_globals vulnerabilities.
 * This line must be present before any global variable is referenced.
**/
if(!defined('MEDIAWIKI')){
        echo("This is an extension to the MediaWiki package and cannot be run standalone.\n" );
        die(-1);
}
 
/**
 * Identify the extension, version, author, etc
**/
$wgExtensionCredits['parserhook'][] = array(
        'name'          =>      'Age',
        'version'       =>      '0.7',
        'author'        =>      'Mark Daly',
        'url'           =>      'http://www.mediawiki.org/wiki/Extension:Age',
        'description'   =>      'Calculate difference between two dates using &lt;age from="yyyy-mm-dd" to="yyyy-mm-dd" [format="ymd"] [left="("] [right=")"] [errbox="no"] [zeronegatives="no"] /&gt; in "years, months, days" format'
);
 
 
/**
 * Set up extension and messages
**/
$wgExtensionFunctions[] = 'efAge_Setup';
 
$dir = dirname(__FILE__) . '/';
$wgExtensionMessagesFiles['age'] = $dir . 'Age.i18n.php';
 
function efAge_Setup(){
        wfLoadExtensionMessages( 'age' );
        global $wgParser;
        $wgParser->setHook( 'age', 'efAge_Render' );
}
 
 
/**
 * Process the <age> tag and attributes to return an answer, or an error message
**/
function efAge_Render( $input, $args, $parser ) {
        global $wgAgeErrorBox;          // global setting to show errors in a distinct box site-wide by default; can be overridden by errbox="no" in each use
        global $wgAgeDefaultToday;      // global setting to use current date if FROM or TO not specified site-wide by default
        global $wgAgeZeroNegatives;     // global setting to use zero negative date differences
 
        $retval        = '';    // result to show
        $sToday        = '';    // current date not set
        $errbox        = false; // do not show error box if error occurs
        $zeronegatives = false; // show 0 when date difference is negative
        $bError        = true;  // error occurred in attributes; false = no error; true = error
 
        // set global controls, some of which can be overridden
        if (isset($wgAgeDefaultToday)) {
                if ($wgAgeDefaultToday) $sToday = date('Y-m-d'); // if global is set, then get current date for FROM/TO defaults if not set in attributes
        }
 
        if (isset($wgAgeErrorBox)) {
                $errbox = ($wgAgeErrorBox);     // if global is set and TRUE then show error box unless turned off in attributes
        }
 
        if (isset($wgAgeZeroNegatives)) {
                $zeronegatives = ($wgAgeZeroNegatives); // if global is set and TRUE then zero negative date differences
        }
 
        // get attributes from <age> tag
        if (!isset($args[wfMsg('from')]))   $from   = $sToday;          else $from   = trim($args[wfMsg('from')]);
        if (!isset($args[wfMsg('to')]))     $to     = $sToday;          else $to     = trim($args[wfMsg('to')]);
        if (!isset($args[wfMsg('left')]))   $left   = '';               else $left   = $args[wfMsg('left')];            // do not trim
        if (!isset($args[wfMsg('right')]))  $right  = '';               else $right  = $args[wfMsg('right')];           // do not trim
        if (!isset($args[wfMsg('format')])) $format = wfMsg('ymd');     else $format = strtolower(trim($args[wfMsg('format')]));
 
        if (isset($args[wfMsg('zeronegatives')]))  $zeronegatives = (strtolower(trim($args[wfMsg('zeronegatives')])) == wfMsg('yes'));  // 'yes' == true; anything else == false;
        if (isset($args[wfMsg('errbox')]))         $errbox        = (strtolower(trim($args[wfMsg('errbox')]))        == wfMsg('yes'));  // 'yes' == true; anything else == false
 
        // validate input
        if ($format <> wfMsg('ymd') && $format <> wfMsg('ym') && $format <> wfMsg('y')) $format = wfMsg('ymd'); //use default if invalid
 
        // process the attributes
        if (strlen($from) == 0) {
                $retval = wfMsg('required-from');               // FROM is required, even if from $sToday
        } elseif (strlen($to) == 0) {
                $retval = wfMsg('required-to');                 // TO is required, event if from $sToday
        } elseif ($from == $to) {
                $retval = '0 ' . wfMsg('dd-plural');            // no need to calculate anything, they are the same date
        } elseif ($zeronegatives && $to < $from ) {
                $retval = '0 ' . wfMsg('dd-plural');            // TO comes before FROM hence we use '0 days'
        } else {
                $aDate = array();
                $aDate = explode('-',$from);                    // separate pages in format [<era>-]<yyyy>-<mm>-<dd>
                $first = array();
                $first['year']  = $aDate[0] *1;                 // '*1' makes sure it is a number (makes some versions of PHP happy)
                $first['month'] = $aDate[1] *1;
                $first['day']   = $aDate[2] *1;
 
                $aDate = explode('-',$to);                              // separate pages in format [<era>-]<yyyy>-<mm>-<dd>
                $last  = array();
                $last['year']   = $aDate[0] *1;                 // '*1' makes sure it is a number (makes some versions of PHP happy)
                $last['month']  = $aDate[1] *1;
                $last['day']    = $aDate[2] *1;
 
                // ok, ready to calculate answer
                $diff = date_difference($first,$last);
                if (is_array($diff)) {                  // got something, build answer
                        $bError = false;                        // turn off error indicator
                        if ($diff['years'] > 0) {       // year included in any format
                                $retval .= $diff['years'] . ' ' . ($diff['years'] == 1 ? wfMsg('yy-single') : wfMsg('yy-plural'));
                        }
 
                        if ($diff['months'] > 0 && ($format == wfMsg('ymd') || $format == wfMsg('ym'))) {
                                if (strlen($retval) > 0) $retval .= wfMsg('answer-sep');
                                $retval .= $diff['months'] . ' ' . ($diff['months'] == 1 ? wfMsg('mm-single') : wfMsg('mm-plural'));
                        }
 
                        if ($diff['days'] > 0 && ($format == wfMsg('ymd'))) {
                                if (strlen($retval) > 0) $retval .= wfMsg('answer-sep');
                                $retval .= $diff['days'] . ' ' . ($diff['days'] == 1 ? wfMsg('dd-single') : wfMsg('dd-plural'));
                        }
                } else {
                        $retval = $diff; // some error message from date_difference()
                }
        }
 
        // prepare answer
        if (strlen($retval) == 0) $retval = wfMsg('catchall');
        if (strlen($left) > 0 || strlen($right) > 0) $retval = $left . $retval . $right;
        if ($bError && $errbox) $retval = '<div class="errorbox">'.$retval.'</div>';    // 'errorbox' is found in style sheet (CSS) so it is fixed (not translated)
        return $retval;
} // function efAgeRender
 
 
/**
 * Convenience function to create date in "YYYYMMDD" format without punctuation (-).
**/
function smoothdate ( $year, $month, $day ) {
    return sprintf ('%04d', $year) . sprintf ('%02d', $month) . sprintf ('%02d', $day);
}// function smoothdate
 
 
/**
 * Date Difference performs the actual calculations. This function is based on a function 
 * posted at http://www.tek-tips.com/faqs.cfm?fid=3493 on 2003-04-24 as 'faq434-3493'.
 *
 * == INPUT ==
 *      $first is FROM or STARTING date
 *      $second is TO or ENDING date
 * 
 *      Both arguments must be an associative array as follows:
 *              array ( 'year' => year_value, 'month' => month_value, 'day' => day_value )
 * 
 * == RULES ==
 * This function does not make use of 32-bit unix timestamps, so it will 
 * work for dates outside the range 1970-01-01 through 2038-01-19. The 
 * function works by taking the earlier date finding the maximum number 
 * of times it can increment the years, months, and days (in that order) 
 * before reaching the second date. The function does take yeap years into 
 * account, but does not take into account the 10 days removed from the 
 * calendar (specifically October 5 through October 14, 1582) by Pope 
 * Gregory to fix calendar drift.
 *   
 * The first input array is the earlier date, the second the later date. It
 * will check to see that the two dates are well-formed, and that the first
 * date is earlier than the second.
 *
 * == OUTPUT ==
 * If successful, function returns an associative array as follows:
 *              array ( 'years' => years_different, 'months' => months_different, 'days' => days_different )
 *   
 * If an error occurred, function returns a string that describes the problem.
**/
function date_difference ( $first, $second ) {
    $month_lengths = array (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
        $retval = "";
    if (!checkdate($first['month'], $first['day'], $first['year'])) {
                $retval = wfMsg('invalid-from',$first['year'],$first['month'],$first['day']);
        } elseif (!checkdate($second['month'], $second['day'], $second['year'])) {
                $retval = wfMsg('invalid-to',$second['year'],$second['month'],$second['day']);
        } elseif (mktime(0,0,0,$first['month'],$first['day'],$first['year']) > mktime(0,0,0,$second['month'],$second['day'],$second['year'])) {
                $retval = wfMsg('from-first',$first['year'], $first['month'], $first['day'], $second['year'], $second['month'], $second['day']);
        } else {
        $start  = smoothdate ($first['year'],  $first['month'],  $first['day']);
        $target = smoothdate ($second['year'], $second['month'], $second['day']);
        if ($start <= $target) {
            $add_year = 0;
            while (smoothdate ($first['year']+ 1, $first['month'], $first['day']) <= $target) {
                $add_year++;
                $first['year']++;
            } //while years
 
            $add_month = 0;
            while (smoothdate ($first['year'], $first['month'] + 1, $first['day']) <= $target) {
                $add_month++;
                $first['month']++;
                if ($first['month'] > 12) {
                    $first['year']++;
                    $first['month'] = 1;
                }
            } //while months
 
            $add_day = 0;
            while (smoothdate ($first['year'], $first['month'], $first['day'] + 1) <= $target) {
                if (($first['year'] % 100 == 0) && ($first['year'] % 400 == 0)) {
                    $month_lengths[1] = 29;     //leap year adjustment
                } else {
                    if ($first['year'] % 4 == 0) {
                        $month_lengths[1] = 29; // leap year adjustment
                    }
                }
                $add_day++;
                $first['day']++;
                if ($first['day'] > $month_lengths[$first['month'] - 1]) {
                    $first['month']++;
                    $first['day'] = 1;
                    if ($first['month'] > 12) {
                        $first['month'] = 1;
                    }
                }
            } // while days
 
            $retval = array ('years' => $add_year, 'months' => $add_month, 'days' => $add_day);
        } else {
                        $retval = wfMsg('from-first',$first['year'], $first['month'], $first['day'], $second['year'], $second['month'], $second['day']);
                }
    }// validation ok
 
    return $retval;
}// function date_difference

Language [edit]

Create a file called extensions/Age/Age.i18n.php and copy the following text into it. Be sure to add <?php and ?> before and after this text, respectively. Note that this tag needs additional translations.

/**
 * Internationalisation file for extension Age.
*/
$messages = array();
 
/** English (default)
 * @author Mark Daly
*/
$messages['en'] = array (
         'required-from'        => 'FROM attribute is required (e.g. from="2008-01-12")'
        ,'required-to'          => 'TO attribute is  required (e.g. from="2008-01-12")'
        ,'invalid-from'         => 'FROM date is not valid: $1-$2-$3 (YYYY-MM-DD)'
        ,'invalid-to'           => 'TO date is not valid: $1-$2-$3 (YYYY-MM-DD)'
        ,'from-first'           => 'FROM ($1-$2-$3) date must occur before TO ($4-$5-$6) date'
        ,'same-day'             => 'FROM ($1) and TO ($2) date are the same'
        ,'syntax'               => '&lt;age from="yyyy-mm-dd" to="yyyy-mm-dd" [left="c"] [right="c"]&gt;&lt;/age&gt;'
        ,'catchall'             => 'Something is wrong with the AGE tag; check your syntax, match quotes, FROM & TO attributes are required, date format should be ISO-8601 (e.g. YYYY-MM-DD), and FROM must be less than TO.'
        ,'yy-single'            => 'year'
        ,'yy-plural'            => 'years'
        ,'mm-single'            => 'month'
        ,'mm-plural'            => 'months'
        ,'dd-single'            => 'day'
        ,'dd-plural'            => 'days'
        ,'answer-sep'           => ', '
        ,'yes'                  => 'yes'
        ,'no'                   => 'no'
        ,'from'                 => 'from'
        ,'to'                   => 'to'
        ,'left'                 => 'left'
        ,'right'                => 'right'
        ,'format'               => 'format'
        ,'errbox'               => 'errbox'
        ,'zeronegatives'        => 'zeronegatives'
        ,'y'                    => 'y'          //year format
        ,'ym'                   => 'ym'         //year+month format
        ,'ymd'                  => 'ymd'        //year+month+day format
);
 
/** Message documentation (explain purpose of each message)
 * @author Mark Daly
*/
$messages['qqq'] = array (
         'required-from' => 'error message: FROM value is required'
        ,'required-to'   => 'error message: TO value is required'
        ,'invalid-from'  => 'error message: FROM value is not a valid date'
        ,'invalid-to'    => 'error message: TO value is not a valid date'
        ,'from-first'    => 'error message: FROM date must be less than or equal to TO date'
        ,'same-day'      => 'error message: days are the same, why bother?'
        ,'syntax'        => 'error message: show how the tag can be written'
        ,'catchall'      => 'error message: show this when we know an error occurred but not what happened'
        ,'yy-single'     => 'word for 1 year'
        ,'yy-plural'     => 'word for many years'
        ,'mm-single'     => 'word for 1 day'
        ,'mm-plural'     => 'word for many months'
        ,'dd-single'     => 'word for 1 day'
        ,'dd-plural'     => 'word for many days'
        ,'answer-sep'    => 'separate year, month, and day values in answer'
        ,'yes'           => 'attribute value for "errbox": yes'
        ,'no'            => 'attribute value for "errbox": no'
        ,'from'          => 'attribute: ending date'
        ,'left'          => 'attribute: text to prepend to left or answer'
        ,'right'         => 'attribute: text to append to right of answer'
        ,'format'        => 'attribute: format to show'
        ,'errbox'        => 'attribute: controls display of error messages'
        ,'zeronegatives' => 'attribute: controls zeroing of negative time differences'
        ,'y'             => 'attribute value for "format": year only format'
        ,'ym'            => 'attribute value for "format": year and month format'
        ,'ymd'           => 'attribute value for "format": full year, month, and day format/count'
);

See also [edit]