Module:Age: Difference between revisions

    (update from sandbox: implement Template:Death date and age)
    Line 1: Line 1:
    -- Implement various "age of" and other date-related templates.
    -- Implement various "age of" and other date-related templates.
    local mtext = {
    -- Message and other text that should be localized.
    ['mt-bad-param1'] =            'Invalid parameter $1',
    ['mt-bad-param2'] =            'Parameter $1=$2 is invalid',
    ['mt-bad-show'] =              'Parameter show=$1 is not supported here',
    ['mt-cannot-add'] =            'Cannot add "$1"',
    ['mt-conflicting-show'] =      'Parameter show=$1 conflicts with round=$2',
    ['mt-date-wrong-order'] =      'The second date must be later in time than the first date',
    ['mt-dd-wrong-order'] =        'Death date (first date) must be later in time than the birth date (second date)',
    ['mt-invalid-bd-age'] =        'Invalid birth date for calculating age',
    ['mt-invalid-dates-age'] =      'Invalid dates for calculating age',
    ['mt-invalid-end'] =            'Invalid end date in second parameter',
    ['mt-invalid-start'] =          'Invalid start date in first parameter',
    ['mt-need-jdn'] =              'Need valid Julian date number',
    ['mt-need-valid-bd'] =          'Need valid birth date: year, month, day',
    ['mt-need-valid-bd2'] =        'Need valid birth date (second date): year, month, day',
    ['mt-need-valid-date'] =        'Need valid date',
    ['mt-need-valid-dd'] =          'Need valid death date (first date): year, month, day',
    ['mt-need-valid-ymd'] =        'Need valid year, month, day',
    ['mt-need-valid-ymd-current'] = 'Need valid year|month|day or "currentdate"',
    ['mt-need-valid-ymd2'] =        'Second date should be year, month, day',
    ['mt-template-bad-name'] =      'The specified template name is not valid',
    ['mt-template-x'] =            'The template invoking this must have "|template=x" where x is the wanted operation',
    ['txt-age'] =                  '(age ',
    ['txt-aged'] =                  ' (aged ',
    ['txt-and'] =                  ' and ',
    ['txt-comma-and'] =            ', and ',
    ['txt-error'] =                'Error: ',
    ['txt-or'] =                    ' or ',
    }
    local translate, from_en, to_en, isZero
    if translate then
    -- Functions to translate from en to local language and reverse go here.
    -- See example at [[:bn:Module:বয়স]].
    else
    from_en = function (text)
    return text
    end
    isZero = function (text)
    return tonumber(text) == 0
    end
    end


    local _Date, _currentDate
    local _Date, _currentDate
    Line 7: Line 51:
    local sandbox = frame:getTitle():find('sandbox', 1, true) and '/sandbox' or ''
    local sandbox = frame:getTitle():find('sandbox', 1, true) and '/sandbox' or ''
    local datemod = require('Module:Date' .. sandbox)
    local datemod = require('Module:Date' .. sandbox)
    _Date = datemod._Date
    local realDate = datemod._Date
    _currentDate = datemod._current
    _currentDate = datemod._current
    if to_en then
    _Date = function (...)
    local args = {}
    for i, v in ipairs({...}) do
    args[i] = to_en(v)
    end
    return realDate(unpack(args))
    end
    else
    _Date = realDate
    end
    end
    end
    return _Date, _currentDate
    return _Date, _currentDate
    Line 57: Line 112:
    local function message(msg, id)
    local function message(msg, id)
    -- Return formatted message text for an error or warning.
    -- Return formatted message text for an error or warning.
    local function getText(msg)
    return mtext[msg] or error('Bug: message "' .. tostring(msg) .. '" not defined')
    end
    local text
    if type(msg) == 'table' then
    text = getText(msg[1])
    local rep = {}
    for i, v in ipairs(msg) do
    if i > 1 then
    rep['$' .. (i - 1)] = v
    end
    end
    text = text:gsub('$%d+', rep)
    else
    text = getText(msg)
    end
    local categories = {
    local categories = {
    error = '[[Category:Age error]]',
    error = '[[Category:Age error]]',
    Line 66: Line 137:
    b = '</i>]</sup>'
    b = '</i>]</sup>'
    else
    else
    a = '<strong class="error">Error: '
    a = '<strong class="error">' .. getText('txt-error')
    b = '</strong>'
    b = '</strong>'
    end
    end
    Line 75: Line 146:
    return
    return
    a ..
    a ..
    mw.text.nowiki(msg) ..
    mw.text.nowiki(text) ..
    b ..
    b ..
    (category or '')
    (category or '')
    Line 252: Line 323:
    return 'error'
    return 'error'
    end
    end
    return message('Need valid date')
    return message('mt-need-valid-date')
    end
    end
    local add = stripToNil(args.add)
    local add = stripToNil(args.add)
    Line 259: Line 330:
    date = date + item
    date = date + item
    if not date then
    if not date then
    return message('Cannot add "' .. item .. '"')
    return message({ 'mt-cannot-add', item })
    end
    end
    end
    end
    Line 272: Line 343:
    result = date[show]
    result = date[show]
    if result == nil then
    if result == nil then
    result = date:text(show)
    result = from_en(date:text(show))
    elseif type(result) == 'boolean' then
    elseif type(result) == 'boolean' then
    result = result and '1' or '0'
    result = result and '1' or '0'
    else
    else
    result = tostring(result)
    result = from_en(tostring(result))
    end
    end
    end
    end
    Line 284: Line 355:
    local function rangeJoin(range)
    local function rangeJoin(range)
    -- Return text to be used between a range of ages.
    -- Return text to be used between a range of ages.
    return range == 'dash' and '–' or '&nbsp;or '
    return range == 'dash' and '–' or mtext['txt-or']
    end
    end


    Line 335: Line 406:
    elseif options.join == 'sep_serialcomma' and text.n > 2 then
    elseif options.join == 'sep_serialcomma' and text.n > 2 then
    first = ', '
    first = ', '
    last = ', and '
    last = mtext['txt-comma-and']
    else
    else
    first = ', '
    first = ', '
    last = ' and '
    last = mtext['txt-and']
    end
    end
    for i, v in ipairs(text) do
    for i, v in ipairs(text) do
    Line 478: Line 549:
    (textOptions.suffix or '')
    (textOptions.suffix or '')
    end
    end
    return message('Parameter show=' .. show.id .. ' is not supported here')
    return message({ 'mt-bad-show', show.id })
    end
    end


    Line 533: Line 604:
    end
    end
    if getopt.omitZero and i % 3 ~= 1 then  -- omit zero months and days as unknown values but keep year 0 which is 1 BCE
    if getopt.omitZero and i % 3 ~= 1 then  -- omit zero months and days as unknown values but keep year 0 which is 1 BCE
    if tonumber(fields[i]) == 0 then
    if isZero(fields[i]) then
    fields[i] = nil
    fields[i] = nil
    getopt.partial = true
    getopt.partial = true
    Line 578: Line 649:
    end
    end
    if not dates[1] then
    if not dates[1] then
    return message(getopt.missing1 or 'Need valid year, month, day')
    return message(getopt.missing1 or 'mt-need-valid-ymd')
    end
    end
    if getopt.single then
    if getopt.single then
    Line 584: Line 655:
    end
    end
    if not dates[2] then
    if not dates[2] then
    return message(getopt.missing2 or 'Second date should be year, month, day')
    return message(getopt.missing2 or 'mt-need-valid-ymd2')
    end
    end
    return dates[1], dates[2]
    return dates[1], dates[2]
    Line 595: Line 666:
    local name = frame.args.template
    local name = frame.args.template
    if not name then
    if not name then
    return message('The template invoking this must have "|template=x" where x is the wanted operation')
    return message('mt-template-x')
    end
    end
    local args = frame:getParent().args
    local args = frame:getParent().args
    Line 692: Line 763:
    local spec = specs[name]
    local spec = specs[name]
    if not spec then
    if not spec then
    return message('The specified template name is not valid')
    return message('mt-template-bad-name')
    end
    end
    if name == 'age_days' then
    if name == 'age_days' then
    Line 756: Line 827:
    }
    }
    if (spec.negative or frame.args.negative) == 'error' and parms.diff.isnegative then
    if (spec.negative or frame.args.negative) == 'error' and parms.diff.isnegative then
    return message('The second date should not be before the first date')
    return message('mt-date-wrong-order')
    end
    end
    return dateDifference(parms)
    return from_en(dateDifference(parms))
    end
    end


    Line 765: Line 836:
    local args = frame:getParent().args
    local args = frame:getParent().args
    local options = {
    local options = {
    missing1 = 'Need valid birth date: year, month, day',
    missing1 = 'mt-need-valid-bd',
    noMissing = true,
    noMissing = true,
    single = true,
    single = true,
    Line 776: Line 847:
    local diff = Date('currentdate') - date
    local diff = Date('currentdate') - date
    if diff.isnegative or diff.years > 150 then
    if diff.isnegative or diff.years > 150 then
    return message('Invalid birth date for calculating age')
    return message('mt-invalid-bd-age')
    end
    end
    local disp, show = 'disp_raw', 'y'
    local disp, show = 'disp_raw', 'y'
    Line 788: Line 859:
    end
    end
    local df = stripToNil(args.df)  -- day first (dmy); default is month first (mdy)
    local df = stripToNil(args.df)  -- day first (dmy); default is month first (mdy)
    local result = df and
    local result = '(<span class="bday">%-Y-%m-%d</span>) </span>' ..
    '%-d %B %-Y' or
    (df and '%-d %B %-Y' or '%B %-d, %-Y')
    '%B %-d, %-Y'
    result = from_en('<span style="display:none"> ' ..
    result = '(<span class="bday">%-Y-%m-%d</span>) </span>' .. result
    result = '<span style="display:none"> ' ..
    date:text(result) ..
    date:text(result) ..
    '<span class="noprint ForceAgeToShow"> ' ..
    '<span class="noprint ForceAgeToShow"> ' ..
    '(age&nbsp;' ..
    mtext['txt-age'] ..
    dateDifference({
    dateDifference({
    diff = diff,
    diff = diff,
    Line 803: Line 872:
    sep = 'sep_space',
    sep = 'sep_space',
    }) ..
    }) ..
    ')</span>'
    ')</span>')
    local warnings = tonumber(frame.args.warnings)
    local warnings = tonumber(frame.args.warnings)
    if warnings and warnings > 0 then
    if warnings and warnings > 0 then
    Line 832: Line 901:
    end
    end
    if invalid then
    if invalid then
    result = result .. message('invalid parameter ' .. invalid, 'warning')
    result = result .. message({ 'mt-bad-param1', invalid }, 'warning')
    end
    end
    end
    end
    Line 842: Line 911:
    local args = frame:getParent().args
    local args = frame:getParent().args
    local options = {
    local options = {
    missing1 = 'Need valid death date (first date): year, month, day',
    missing1 = 'mt-need-valid-dd',
    missing2 = 'Need valid birth date (second date): year, month, day',
    missing2 = 'mt-need-valid-bd2',
    noMissing = true,
    noMissing = true,
    partial = true,
    partial = true,
    Line 853: Line 922:
    local diff = date1 - date2
    local diff = date1 - date2
    if diff.isnegative then
    if diff.isnegative then
    return message('Death date (first date) must occur after birth date (second date)')
    return message('mt-dd-wrong-order')
    end
    end
    local years
    local years
    Line 863: Line 932:
    end
    end
    if years > 150 then
    if years > 150 then
    return message('Invalid dates for calculating age')
    return message('mt-invalid-dates-age')
    end
    end
    local df = stripToNil(args.df)  -- day first (dmy); default is month first (mdy)
    local df = stripToNil(args.df)  -- day first (dmy); default is month first (mdy)
    Line 881: Line 950:
    '<span style="display:none">(%-Y-00-00)</span>'
    '<span style="display:none">(%-Y-00-00)</span>'
    end
    end
    result = date1:text(result) ..
    result = from_en(date1:text(result) ..
    ' (aged&nbsp;' ..
    mtext['txt-aged'] ..
    dateDifference({
    dateDifference({
    diff = diff,
    diff = diff,
    Line 891: Line 960:
    sep = 'sep_space',
    sep = 'sep_space',
    }) ..
    }) ..
    ')'
    ')')
    local warnings = tonumber(frame.args.warnings)
    local warnings = tonumber(frame.args.warnings)
    if warnings and warnings > 0 then
    if warnings and warnings > 0 then
    Line 914: Line 983:
    end
    end
    if invalid then
    if invalid then
    result = result .. message('invalid parameter ' .. invalid, 'warning')
    result = result .. message({ 'mt-bad-param1', invalid }, 'warning')
    end
    end
    end
    end
    Line 940: Line 1,009:
    local date = Date('juliandate', args[1], args[2])
    local date = Date('juliandate', args[1], args[2])
    if date then
    if date then
    return date:text()
    return from_en(date:text())
    end
    end
    return message('Need valid Julian date number')
    return message('mt-need-jdn')
    end
    end


    Line 955: Line 1,024:
    return tostring(date.jd)
    return tostring(date.jd)
    end
    end
    return message('Need valid year/month/day or "currentdate"')
    return message('mt-need-valid-ymd-current')
    end
    end


    Line 974: Line 1,043:
    local date1 = Date(fix, 'partial', stripToNil(args[1]) or 'currentdatetime')
    local date1 = Date(fix, 'partial', stripToNil(args[1]) or 'currentdatetime')
    if not date1 then
    if not date1 then
    return message('Invalid start date in first parameter')
    return message('mt-invalid-start')
    end
    end
    local date2 = Date(fix, 'partial', stripToNil(args[2]) or 'currentdatetime')
    local date2 = Date(fix, 'partial', stripToNil(args[2]) or 'currentdatetime')
    if not date2 then
    if not date2 then
    return message('Invalid end date in second parameter')
    return message('mt-invalid-end')
    end
    end
    parms.diff = date2 - date1
    parms.diff = date2 - date1
    Line 986: Line 1,055:
    parm = translate[parm]
    parm = translate[parm]
    if parm == nil then  -- test for nil because false is a valid setting
    if parm == nil then  -- test for nil because false is a valid setting
    return message('Parameter ' .. argname .. '=' .. args[argname] .. ' is invalid')
    return message({ 'mt-bad-param2', argname, args[argname] })
    end
    end
    parms[argname] = parm
    parms[argname] = parm
    Line 997: Line 1,066:
    if show then
    if show then
    if show.id ~= round then
    if show.id ~= round then
    return message('Parameter show=' .. args.show .. ' conflicts with round=' .. args.round)
    return message({ 'mt-conflicting-show', args.show, args.round })
    end
    end
    else
    else
    Line 1,005: Line 1,074:
    parms.round = true
    parms.round = true
    end
    end
    return dateDifference(parms)
    return from_en(dateDifference(parms))
    end
    end