Extension:AgeParse

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

Release status: beta

Implementation Parser extension
Description Calculate difference between two dates
Author(s) Mark Daly (chanurTalk)
Last version 0.1 (2009-03-13)
MediaWiki 1.13.X
License GPL
Download see below
Example age from=1606-07-15 to=1669-10-04
Parameters

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

Check usage (experimental)

Contents

[edit] What can this extension do?

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 or after 2038. 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. A global setting also allows FROM or TO (only one) to be optional, automatically using the current date (from the web site's server) for the missing date.

[edit] Usage

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.

[edit] Syntax

{{#age from=yyyy=mm=dd | to=yyyy=mm=dd | format=ymd | left=c | right=c | errbox=yes | zeronegatives=yes}}

[edit] Attributes

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

[edit] Examples

{{#age from=1368-01-01 | to=1644-01-01 |format=y}}
{{#age from=1606-07-15 |to=1669-10-04 |left=( |right=)}}

[edit] Download instructions

Copy and paste the code found below and place them in files called $IP/extensions/AgeParse/AgeParse.php and $IP/extensions/AgeParse/AgeParse.body.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/AgeParse/AgeParse.i18n.php.

SVN: https://wecowi.svn.sourceforge.net/svnroot/wecowi/trunk/extensions/AgeParse/

[edit] Installation

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

require_once( "$IP/extensions/AgeParse/AgeParse.php" );
$wgAgeErrorBox      = true;  //true: show box if arguments/calculation cause error
$wgAgeDefaultToday  = true;  //true: FROM or TO default to current date if not provided
$wgAgeZeroNegatives = true;  //true: show '0 days' if calculated value is negative

[edit] Configuration parameters

The following settings can be added to LocalSettings.php:

$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
$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
$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

[edit] Code

[edit] AgeParse.php

/** AgeParse MediaWiki Extension
 * Type: parser
 *
 * 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}} function. All other
 * attributes are optional, incuding:
 *  from   = ISO-8601 (yyyy-mm-dd) starting date
 *  to     = ISO-8601 (uuuu-mm-dd) ending date
 *  format = controls how much of answer is displayed; choices: ymd | ym | y
 *  left   = character or text to add to front of answer
 *  right  = character or text to add to end of answer
 *  errbox = "yes" shows errors as class="errorbox"; "no" shows errors as regular text
 *  zeronegatives = "yes" shows "0 days" if TO occurs before FROM
 * 
 * == INSTALL ==
 *  1. Copy AGEPARSE.PHP and AGE.I18N.PHP into extensions/Age folder on wiki
 *  2. Add the following lines into the wiki LocalSettings.php
 *               require_once( "$IP/extensions/AgeParse/AgeParse.php" );
 *               $wgAgeErrorBox      = true;  //true: show box if arguments/calculation cause error
 *               $wgAgeDefaultToday  = true;  //true: FROM or TO default to current date if not provided
 *               $wgAgeZeroNegatives = true;  //true: show '0 days' if calculated value is negative
 *  3. Change globals to meet your needs
 *  4. Add {{#age: from=yyyy-mm-dd |to=yyy-mm-dd}} to page(s)
 *  
 * ==LANGUAGE ==
 * AgeParse.i18n.php available to implement translations
 * 
 * == SYNTAX ==
 * {{#age: from=yyyy-mm-dd | to=yyyy-mm-dd | left=C | right=C | format=ymd | errbox=yes | zeronegatives=yes }}
 *
 * == COMPATIBILITY ==
 * This extension cannot be 
 *  
 * == INFO ==
 * @author Mark Daly <[email protected]>
 * @version 0.1
 * @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);
}
 
DEFINE('AGEPARSE_VERSION','0.1 2009-03-13');
 
/**
 * Identify the extension, version, author, etc
**/
$wgExtensionCredits['parserhook'][] = array(
        'name'          =>      'AgeParse',
        'version'       =>      AGEPARSE_VERSION,
        'author'        =>      'Mark Daly',
        'url'           =>      'http://www.mediawiki.org/wiki/Extension:AgeParse',
        'description'   =>      'Calculate difference days, months, and years between two dates'
);
 
 
/**
 * Set up extension and messages
**/
$wgExtensionFunctions[] = 'wfAgeParse_Setup';
 
$wgHooks['LanguageGetMagic'][] = 'wfAgeParse_Magic';
 
$wgExtensionMessagesFiles['age'] = dirname(__FILE__) . '/AgeParse.i18n.php';
 
function wfAgeParse_Magic( &$magicWords, $langCode = "en" ) {
        $magicWords['age'] = array( 0, 'age' );
        return true;
}
 
function wfAgeParse_Setup() {
        wfLoadExtensionMessages( 'age' );
        global $wgParser;
        $wgParser->setFunctionHook( 'age', 'wfAgeParse_Render' );
}
 
 
/**
 * Define functions that do the actual calculations.
/**/
require( dirname(__FILE__) . '/AgeParse.body.php' );
 
/**
 * Parse out any arguments/attributes of the {{age}} call and calculate the age differece.
**/
function wfAgeParse_Render( &$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
        $argv = array();
 
        $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
 
        // set local values per 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
        } // otherwise $sToday is blank, which is an error
 
        if (isset($wgAgeErrorBox)) {
                $errbox = ($wgAgeErrorBox);     // if global is set, use global value
        }
 
        if (isset($wgAgeZeroNegatives)) {
                $zeronegatives = ($wgAgeZeroNegatives); // if global is set, use global value
        }
 
        // get attributes from {{#age ...}} request using name=value pairs
        foreach (func_get_args() as $arg) if (!is_object($arg)) {
                if (preg_match('/^(.+?)\\s*=\\s*(.+)$/',$arg,$match)) $argv[$match[1]]=$match[2];
        }
        if (!isset($argv[wfMsg('from')]))   $from   = $sToday;          else $from   = trim($argv[wfMsg('from')]);
        if (!isset($argv[wfMsg('to')]))     $to     = $sToday;          else $to     = trim($argv[wfMsg('to')]);
        if (!isset($argv[wfMsg('left')]))   $left   = '';               else $left   = $argv[wfMsg('left')];            // do not trim; use as entered
        if (!isset($argv[wfMsg('right')]))  $right  = '';               else $right  = $argv[wfMsg('right')];           // do not trim; use as entered
        if (!isset($argv[wfMsg('format')])) $format = wfMsg('ymd');     else $format = strtolower(trim($argv[wfMsg('format')]));
 
        if (isset($argv[wfMsg('zeronegatives')])) 
                $zeronegatives = (strtolower(trim($argv[wfMsg('zeronegatives')])) == wfMsg('yes'));     // 'yes' == true; anything else == false;
 
        if (isset($argv[wfMsg('errbox')]))
                $errbox = (strtolower(trim($argv[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
 
        $retval = calculateAge($from,$to,$format,$left,$right,$errbox,$zeronegatives);
        return $retval;
}

[edit] AgeParse.body.php

/**
 * Calculate the actual age based on information provided by the {{age}} request.
 * Each attribute is already parsed so this function is only responsible for the 
 * actual calculation.
**/
function calculateAge($from,$to,$format,$left,$right,$errbox,$zeronegatives) {
        $retval = "";
        $bError = true; // error occurred in attributes; false = no error; true = error
 
        // 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, so 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="'.wfMsg('errorbox').'">'.$retval.'</div>';       // 'errorbox' is found in style sheet (CSS) so it is fixed (not translated)
 
        return $retval;
}
 
 
/**
 * 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);
}
 
 
/**
 * 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 ==
 *   It 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. This 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 XIII 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 value.
**/
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;
}

[edit] AgeParse.i18n.php

/**
 * Internationalisation file for extension Age.
 *
 * @addtogroup Extensions
*/
$messages = array();
 
/** English (default)
 * @author Mark Daly
*/
$messages['en'] = array (
         'from'                  => 'from'
        ,'to'                    => 'to'
        ,'left'                  => 'left'
        ,'right'                 => 'right'
        ,'format'                => 'format'
        ,'errbox'                => 'errbox'
        ,'zeronegatives' => 'zeronegatives'
        ,'yes'                   => 'yes'
        ,'no'                    => 'no'
        ,'ymd'                   => 'ymd'
        ,'ym'                    => 'ym'
        ,'y'                     => 'y'
        ,'required-from' => 'FROM attribute is required (e.g. from="2009-01-12")'
        ,'required-to'   => 'TO attribute is  required (e.g. from="2009-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'    => ', '
);
 
/** Message documentation (explain purpose of each message)
 * @author Mark Daly
*/
$messages['qqq'] = array (
         'from'                  => 'attribute to specify starting date'
        ,'to'                    => 'attribute to specify ending date'
        ,'left'                  => 'attribute to specify text added to front of answer'
        ,'right'                 => 'attribute to specify text added after answer'
        ,'format'                => 'attribute to specify what parts of the answer you want to show'
        ,'errbox'                => 'attribute to specify (yes/no) if error should show in a distinctive box'
        ,'zeronegatives' => 'attribute to specify (yes/no) if negative days for answer should just show as "0 days"'
        ,'yes'                   => 'answer used by errbox and zeronegatives'
        ,'no'                    => 'answer used by errbox and zeronegatives'
        ,'ymd'                   => 'format to show days, months, and years when that part is greater than zero'
        ,'ym'                    => 'format to show months and years when that part is greater than zero'
        ,'y'                     => 'format to only show years'
        ,'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'
);
 
/** Spanish (default)
 * @author Google Translator (this is just for testing)
*/
$messages['es'] = array (
         'from'                  => 'desde'
        ,'to'                    => 'para'
        ,'left'                  => 'izquierda'
        ,'right'                 => 'acertado'
        ,'format'                => 'formato'
        ,'errbox'                => 'caja'
        ,'zeronegatives' => 'negativos'
        ,'yes'                   => 'si'
        ,'no'                    => 'no'
        ,'ymd'                   => 'ymd'
        ,'ym'                    => 'ym'
        ,'y'                     => 'y'
        ,'required-from' => 'FROM atributo es necesario (e.g. from="2009-01-12")'
        ,'required-to'   => 'TO atributo es necesario (e.g. from="2009-01-12")'
        ,'invalid-from'  => 'FROM fecha no es válida: $1-$2-$3 (YYYY-MM-DD)'
        ,'invalid-to'    => 'TO fecha no es válida: $1-$2-$3 (YYYY-MM-DD)'
        ,'from-first'    => 'FROM ($1-$2-$3) fecha debe ocurrir antes de TO ($4-$5-$6) fecha'
        ,'same-day'      => 'FROM ($1) y TO ($2) fecha son los mismos'
        ,'syntax'        => '&lt;age from="yyyy-mm-dd" to="yyyy-mm-dd" [left="c"] [right="c"]&gt;&lt;/age&gt;'
        ,'catchall'      => 'Algo está mal con la edad etiqueta; comprobar su sintaxis, coinciden con las comillas, que van y vuelven a los atributos son necesarios, la fecha debe tener el formato ISO-8601 (por ejemplo, AAAA-MM-DD), y de debe ser inferior a A.'
        ,'yy-single'     => 'años'
        ,'yy-plural'     => 'años'
        ,'mm-single'     => 'mes'
        ,'mm-plural'     => 'mes'
        ,'dd-single'     => 'día'
        ,'dd-plural'     => 'día'
        ,'answer-sep'    => ', '
);


[edit] See also

Personal tools
Namespaces

Variants
Actions
Navigation
Support
Download
Development
Communication
Print/export
Toolbox