A documentação para este módulo pode ser criada na página Módulo:Data/doc

local fun = {}

local Ferramentas = require 'Módulo:Ferramentas'
-- carregando o banco de dados listando algumas páginas existentes para evitar "ifexist".
local dataLinks
local success, resultado = pcall ( mw.loadData, 'Módulo:Data/Dados' )
if success then
    dataLinks = resultado
else
    -- proteção no caso ou sub módulos serem mal modificados
    dataLinks = { [''] = { mes = { vazio = 1000, todos = { 1773, 2014 } }, } }
end

-- limpa um parâmetro sem nome (vira os espaços no começo e no fim)
-- voltar nil se o texto estiver vazio ou não estiver enviando mensagens de texto. A atenção é importante para a função que a utiliza.
local trim = Ferramentas.trim

-- Função destinada a meter a primeira letra do mês em minúscula do mês :
-- uso de string porque nenhum mês começa com uma letra não ascii em português ou inglês.
local function lcfirst( str )
    return str:sub( 1, 1 ):lower() .. str:sub( 2 )
end


-- lista de meses, escritas exatamente com alias, em minúscula
local listaMes = {
    { num = 1,    nDia = 31, abrev = 'jan.',    nome = 'janeiro', alias = { 'jan.', 'jan.', 'jan', 'janeiro', 'january'} },
    { num = 2,    nDia = 29, abrev = 'fev.',     nome = 'fevereiro', alias = { 'fevereiro', 'fev.', 'fev', 'february'} },
    { num = 3,    nDia = 31, abrev = 'mar.',     nome = 'março', alias = { 'mar.', 'mar', 'março', 'march' } },
    { num = 4,    nDia = 30, abrev = 'abr.',     nome = 'abril', alias = { 'abr.', 'abr','abril', 'april' } },
    { num = 5,    nDia = 31, abrev = 'maio',      nome = 'maio', alias = { 'maio', 'may' } },
    { num = 6,    nDia = 30, abrev = 'jun.',     nome = 'junho', alias = { 'jun', 'june' } },
    { num = 7,    nDia = 31, abrev = 'jul.', nome = 'julho', alias = { 'jul.', 'july' } },
    { num = 8,    nDia = 31, abrev = 'ago.',     nome = 'agosto', alias = { 'ago', 'august' } },
    { num = 9,    nDia = 30, abrev = 'set.',    nome = 'setembro', alias = { 'set', 'set.', 'setembro', 'september' } },
    { num = 10, nDia = 31, abrev = 'out.',     nome = 'outubro', alias = { 'out', 'out.', 'october' } },
    { num = 11, nDia = 30, abrev = 'nov.',     nome = 'novembro', alias = { 'nov', 'nov.', 'november' } },
    { num = 12, nDia = 31, abrev = 'dez.',     nome = 'dezembro', alias = { 'dezembro',  'dez.', 'dez', 'december' } },

}

-- adicionar nomes, abreviaturas e aliases como uma lista de keyMonth listaMes
for i = 1, 12 do
    local mes = listaMes[ i ]
    listaMes[ tostring( i ) ] = mes
    listaMes[ '0' .. i ] = mes
    listaMes[ mes.nome ] = mes
    listaMes[ mes.abrev ] = mes
    for _, n in ipairs( mes.alias ) do
    listaMes[ n ] = mes
    end
end
--for _, n in ipairs( listaMes.aout.alias ) do
--    listaMes[ n ] = listaMes.aout
--end
fun.listaMes = listaMes

local lista_estacao = {
    { 'Primavera', 'spring', },
    { 'Verão', 'summer', },
    { 'Outono', 'autumn', },
    { 'Inverno', 'winter', },
}

---
-- validadar que a string passada é uma valida.
-- devolver o nome completo ou nil se não for reconhecido
-- se reconhecido, também retorna o número de mes [1-12]
function fun.validaMes( mes )
    local m = trim( mes )
    if m then
    m = mw.ustring.lower( m )
    if listaMes[ m ] then
        return listaMes[ m ].nome, listaMes[ m ].num
    end
    end
end

---
-- validar que a string passada é uma estação valida.
-- devolver o nome completo ou nil se não for reconhecido
function fun.validaEstacao( estacao )
    local s = trim( estacao )
    if s then
    s = mw.ustring.lower( s )
    for i = 1, 4 do
        for _, n in ipairs( lista_estacao[i] ) do
        if s == n then
            return lista_estacao[i][1]
        end
        end
    end
    end
end

---
-- determinationMes encontrar o número do mes e seu nome,
-- do seu nome, seu número ou uma expressão matemática.
-- Se o segundo parâmetro for true, números maiores que 12 ou não inteiros serão aceites.
function fun.determinationMes( mes, mod, laco )
    local num, nome
    if tonumber( mes ) then
    num = math.floor( tonumber( mes ) )
    if mod then      
        -- se o número de mes é calculado por uma expressão, o resultado pode ser maior que 12, ou menor que 1
        num = math.fmod( num + 239, 12 ) + 1  -- +239 car fmod(-1) = -1 et non 11
    elseif num < 1 or num > 12 then
        num = nil
    end
    elseif trim( mes ) then
    nome, num = fun.validaMes( mes )
    if nome == nil and laco == nil then
        -- tente determinar um número com o analisador #expr do Mediawiki.
        -- o parâmetro laco evita o loop.
        nome, num = fun.determinationMes( mw.getCurrentFrame():callParserFunction( '#expr', mes ), true, true )
    end
    end
    if num and not nome then
    nome = listaMes[ num ].nome
    end
    return nome, num
end


--  função interna para modelData, para determinar se podemos fazer sem ifexitif
local function existData( dataQualificativo, ano, mes )
    local data
    if mes then
       data = dataQualificativo.mes
    else
    data = dataQualificativo.ano
    end
    if type( data ) ~= 'table' then
       -- Se os dados não existem, considera-se que não há link.
       return
    end
    -- o qualificador é substituído pelo do banco de dados, que permite aliases.
    local link = ano
    if dataQualificativo.qualificativo then
       link = link .. ' ' .. dataQualificativo.qualificativo
    end
    local singular = ano
    if mes then
       link = mes .. ' de ' .. link
       singular =      mes .. ' de ' .. ano
    end
    local vazio = tonumber( data.vazio )
    if vazio and ano <= vazio then
       -- se o ano está na parte "não", testamos se ainda há um link isolado
       if type( data.singular ) == 'table' then
             for i, v in ipairs( data.singular ) do
              if singular == v or singular == tonumber( v ) then
                   return link
              end
          end
       end
          -- parte não e nenhum link => nada
          return nil
       elseif type( data.todos ) == 'table' then
             local todos1, todos2 = tonumber( data.todos[1] ), tonumber( data.todos[2] )
          if todos1 and todos2 and ano >= todos1 and ano <= todos2 then
             -- o ano está no partido 'todos' então retornamos o link
             return link
          end
       end
       -- o ano não está nem na parte nem na parte de todos, então você tem que testar se a página existe.
       local alvoLink = mw.title.new( link )
       if alvoLink and alvoLink.exists then
          return link
       end
end

---
-- Exclui o dia da semana e "o" antes de um dado
function fun.limpezaDia( dia )
    if type( dia ) == 'string' then
    local nomeDia = { '[Ss]egunda-feira', '[Tt]erça-feira', '[Qq]uarta-feira', '[Qq]uinta-feira', '[Ss]exta-feira',
        '[Ss]ábado', '[Dd]omingo', '^ *[Oo]' }
--      local premier = { '<abbr class="abbr" title="[Pp]remier" ?>1<sup>er</sup></abbr>', '1<sup>er</sup>', '1er' }
    for i, v in ipairs( nomeDia ) do
        dia = dia:gsub( v, '' )
    end
--      for i, v in ipairs( premier ) do
--          dia = dia:gsub( v, '1' )
--      end
    dia = trim( dia )
    end
    return dia
end

---
-- Separa uma cadeia de data numa tabela com campos dia, mes e ano.
-- os dados devem conter o mês.
function fun.separationDiaMesAno( data )
    data = trim( data )
    if data then
    local function erro( periode, valor )
        return false, '<span class="error">' .. periode .. ' invalida (' .. valor .. ')</span>'
    end
    local dia, mes, ano, esconderMes, esconderAno, separador
    -- variável para construir as regex
    local j = '([0-3]?%d)'          -- dia
    local m = '([01]?%d)'         -- mês numérico
    local mmm =  '([^%s%p%d]+[.]?)' -- mês em todas as  letras
    local aj = '(%-?%d+)'         -- ano ou dia
    local s = '[ ./-de]+'          -- separador simples
--    local sep = '([ ./-de]+)'      -- separador com capture, para detectá-lo duas vezes que não é necesário
    local menos = '(%-?)'          -- o sinal menos para indicar que esses dados não devem ser exibidos
    local regexb = {
        jmmm = '^'..j..s..mmm..menos..'$',
        mmmjva = '^'..mmm..s..j..', ?'..aj..'$',
    }
   
    data = fun.limpezaDia( data )
    -- excluir categoria, links, tags
    data = mw.ustring.gsub( data, '%[%[[Cc]ategor[yi]a?:.-%]%]', '' )
    data = data    :gsub( '%b<>', '' )
            :gsub( '%[%[([^%[%]|]*)|?([^%[%]]*)%]%]', function ( l, t ) return trim( t ) or l end )
    -- remoção de espaços não quebráveis
            -- nbsp
            :gsub( '\194\160', ' ' )
            :gsub( '&nbsp;', ' ' )
            :gsub( '&#160;', ' ' )
            -- narrow nbsp
            :gsub( '\226\128\175', ' ' )
            :gsub( '&#8239;', ' ' )
            -- thin space
            :gsub( '\226\128\137', ' ' )
            :gsub( '&thinsp;', ' ' )
            :gsub( '&#8201;', ' ' )
            -- simple space
            :gsub( '&#32;', ' ' )
            -- vários espaços
            :gsub( ' +', ' ' )
    -- redução a.C. para simplificar um pouco de regex:
            :gsub( '(%d+) ?[Aa]%.? ?C%.?', '-%1' )
    -- exclusão de horas em dados ISO
            :gsub( '^+?([%d-]*%d%d%-%d%d)T%d%d[%d:,.+-]*Z?$' , '%1')
   
    -- teste de um ano
    if data:match( '^'..aj..'$' ) then
        ano = data:match( '^'..aj..'$' )
    elseif data:match( '^'..aj..s..aj..menos..'$' ) then
        -- dd/mm, mm/aaaa ou aaaa/mm
        local a, separador, b, sb = data:match( '^'..aj..s..aj..menos..'$' )
        a, b = tonumber( a ), tonumber( b )
        if separador:match( '^.+%-$' ) then
        -- provavelmente mm/-aaaa, ano a.C.
        b = 0 - b
        end
        if    a > 12 and ( b < 1 or b > 31 ) or
        b > 12 and ( a < 1 or a > 31 ) then
        return erro( 'Data', data )
        elseif b < 1 or b > 31 then
        mes, ano, esconderAno = a, b, sb
        elseif a < 1 or a > 31 then
        ano, mes  = a, b
        elseif b > 12 then
        return erro( 'Mes', b )
        else
        dia, mes, esconderMes = a, b, sb
        end
    elseif data:match( '^'..aj..s..m..menos..'%2'..s..aj..menos..'$' ) then
        -- dd/mm/aaaa ou aaaa/mm/dd
        dia, separador, mes, esconderMes, ano, esconderAno =     data:match( '^'..aj..s..m..menos..'%2'..s..aj..menos..'$' )
        if separador == '-' and esconderMes == '-' and esconderAno == '' and tonumber( ano ) > 0 then
        -- data no formato dd-mm--aaaa type 17-06--44 para 17 junho 44 a.C.
        esconderMes = nil
        ano = 0 - ano
        end
    elseif data:match( '^'..j..s..mmm..menos..'%2'..s..aj..menos..'$' ) then
        -- dd mmm aaaa
        dia, separador, mes, esconderMes, separador, ano, esconderAno = data:match( '^'..j..s..mmm..menos..'%2'..s..aj..menos..'$' )
    elseif data:match( '^'..mmm..s..aj..menos..'$' ) then
        -- mmm aaaa
        mes, separador, ano, esconderAno = data:match( '^'..mmm..s..aj..menos..'$' )
        if separador:match( '^.+%-$' ) then
        ano = '-' .. ano
        end
    elseif data:match( '^'..j..s..mmm..menos..'$' ) then
        -- dd mmmm
        dia, mes, esconderMes = data:match( '^'..j..s..mmm..menos..'$' )
    elseif data:match( '^'..mmm..s..j..', ?'..aj..'$') then
        -- mmm dd, aaaa (formato anglo-saxão)
        mes, dia, ano = data:match( '^'..mmm..s..j..', ?'..aj..'$')
    elseif data:match( '^'..mmm..'$' ) then
        mes = data
    else
        return erro( 'Data', data )
    end
    local jn, an = tonumber( dia ), tonumber( ano )
    if jn and an and ( jn > 31 or jn < 0 or #dia >= 3 ) and an <= 31 then
        -- caso particular dos dados ISO 2015-06-17, -0044-06-17 e -0002-06-17
        -- inversão do dia e do ano
        local temp = ano
        ano = dia
        dia = temp
    end
   
    return fun.validationDiaMesAno{
        dia, mes, ano,
        esconderAno = trim( esconderAno ) and true or nil,
        esconderMes = ( trim( esconderAno ) or not ano ) and trim( esconderMes ) and true or nil,
        -- or nil serve apenas para evitar arrastar um valor falso em todos os testes unitários.
    }
    else
    return true, {}
    end
end


---
-- validationDiaMesAno verifica os parâmetros correspondentes a uma cadeia valida de dados.
-- os dados podem estar nos parâmetros 1 a 3, ou nos parâmetros dia, mes e ano.
-- A função retorna true seguida por uma tabela com os dados em parâmetros nomeados (sem foco no ano)
-- ou falso seguido por uma mensagem de erro.
function fun.validationDiaMesAno( frame, ... )
    local args = Ferramentas.extractArgs( frame, ... )
    local dia, mes, numMes, ano
    local bdia = args[1] or args['dia'] or ''
    local bmes = tostring( args[2] or args['mês'] or args['mes'] or '' )
    local bano = args[3] or args['ano'] or args['year'] or ''
 
    local function erro( periode, valor )
    return false, '<span class="error">' .. periode .. ' inválido (' .. valor .. ')</span>'
    end
 
    -- agora tratamos o ano
    if Ferramentas.notEmpty( bano ) then
    ano = tonumber( bano )
    if ano == nil and type( bano ) == 'string'  then
        -- teste se o ano contiver a.C.
        ano = string.match( string.upper( bano ), '^(%d+) ?[Aa]%.? ?[Cc]%.?' )
        ano = tonumber( ano )
        if ano then
        ano = 0 - ano
        else
        return erro( 'Ano', bano )
        end
    elseif ano == 0 then
        return erro( 'Ano', 0 )
    end
    else
    ano = nil
    end
 
    -- agora tratamos o mês
    if Ferramentas.notEmpty( bmes ) then
    mes, numMes = fun.determinationMes( bmes )
    if mes == nil then
        mes = fun.validaEstacao( bmes )
        if mes == nil then
        return erro( 'Mês', bmes )
        end
    else   
        -- agora tratamos o dias se está informado
        if Ferramentas.notEmpty( bdia ) then
        dia = tonumber( bdia )
        if dia == nil then
            dia = tonumber( fun.limpezaDia( bdia ) )
        end
        if dia == nil then
            return erro( 'Dia', bdia )
        end
        -- agora verifica se dia está bem
        if dia < 1 or dia > 31 then
            return erro( 'Dia', bdia )
        elseif dia > listaMes[numMes].nDia then
            return erro( 'Dia', bdia .. ' ' .. mes )
        elseif dia == 29 and numMes == 2 and ano and ( math.fmod( ano, 4 ) ~= 0 ) then
            --o ano bisexto nos séculos é de todos os dias aceite para ser compatível com dados julianos.
            return erro( 'Dia', '29 de fevereiro de ' .. ano     )
        end
        else
        -- Se não houver dia, a pessoa olha se a primeira letra do mes é minuúscula
        if bmes:match( '^%u' ) then
            -- sim, passamos a primeira letra em letras minúsculas
--              mes = lcfirst( mes )
        end
        -- se não houver ano, retornamos o mês simples
        end
    end
    else
    -- verificamos o dia se está informado
    if Ferramentas.notEmpty( bdia ) then
        if ano then
        return erro( 'Mês', 'não informado' )
        else
        bdia = fun.limpezaDia( bdia )
        dia = tonumber( bdia )
        if dia then
            if dia > 31 or dia < 1 then
            ano = dia
            dia = nil
            else
            return erro( 'Data', 'dia único : ' .. bdia )
            end
        else
            return erro( 'Dia', bdia )
        end
        end
    end
    end
 
    -- verificação da ausência de desvio
    if ano and ano < 13 and ano > 0 and not dia and ( tonumber( bmes ) or (not mes and tonumber( args[4] ) ) ) then
    return false, '<span class="error">ano improvável (' .. ano .. ')</span>'
    end
   
    local resultado = {
    dia = dia,
    mes = mes,
    numMes = numMes,
    ano = ano,
    esconderAno = args.esconderano,
    esconderMes = args.escondermes,
    }
    return true, resultado
end


---
-- emula a predefinição {{tl|Data}}.
-- Configurações:
-- 1: dia (número) ou os dados completos
-- 2: mês (na íntegra) ou estação do ano
-- 3: ano (número)
-- 4: estação do ano
-- juliano: dados no calendário juliano
-- compacto: exibe o mês como uma abreviatura
-- ad: não para desativar a exibição de "a.C." para dados negativos
-- idade: adicione a duração desde que esses dados
-- nolink: não coloque um link nos dados
-- nascimento: adicione a classe "bday"
-- dead: adicione a classe "dday"
function fun.modeloData( frame )
    local args = Ferramentas.extractArgs( frame )
    local cat, resultado = ''
 
    -- analisar parâmetros sem nome (ou parâmetros de dados dia, mês, ano)
    local test, params
    local arg1, arg2, arg3 = fun.limpezaDia( args[1] ), trim( args[2] ), trim( args[3] )
    if type( arg1 ) == 'string' and arg3 == nil and ( arg1:match( '[^ ./-][ ./-]+[^ ./-]' ) or arg2 == nil or dataLinks[arg2] or mw.ustring.match( arg2, '%a %a' ) ) then
    -- os dados estão no primeiro parâmetro
    test, params = fun.separationDiaMesAno( arg1 )
    if test then
        params.qualificativo = arg2
    end
    else
    local function esconderParam( p )
        -- separa o possível sinal de menos significando que o parâmetro não deve ser exibido.
        if type( p ) ~= 'string' then
        return p, nil
        end
        local value, mask = p:match( '^%s*(.-)(%-?)%s*$' )
        return value, ( mask == '-' or nil )
    end
    local cleanArgs = { arg1 or args.dia }
    cleanArgs[2], cleanArgs.escondermes = esconderParam( args[2] or args.mes )
    cleanArgs[3], cleanArgs.esconderano = esconderParam( args[3] or args.ano )
   
    test, params = fun.validationDiaMesAno( cleanArgs )
    if test then
        params.qualificativo = trim( args[4] )
    end
    end
 
    -- analisar os parâmetros nomeados
    if test then
    local Yesno = require 'Módulo:Yesno'
    params.qualificativo = params.qualificativo or args.qualificativo
    -- Juliano pode ter três valores: inativo, formato padrão (true), formato curto
    params.juliano = Yesno( args.juliano, 'curto', false )
    params.ac = Yesno( args.ac )
       
    local listaParam = {
        idade = 'idade',
        nascimento = 'nascimento',
        morte = 'morte',
        falecido = 'morte',
        ac = 'a.C.',
        nolinks = 'nolinks',
        compacto = 'compacto',
        compacta = 'compacto',
    }
    for n, v in pairs( listaParam ) do
        params[v] = params[v] or Yesno( args[n], true, false ) or nil
    end
   
    -- saída para testes unitários ou para depurar
    if args.debug then
        return params
    end
   
    resultado = fun._modeloData( params )

    else
    local namespaceCategorisation = { [0] = true, [4] = true, [10] = true, [14] = true, [100] = true }
    if namespaceCategorisation[ mw.title.getCurrentTitle().namespace ] and not Ferramentas.notEmpty( args.nocat ) then
        cat = '[[Categoria:!Páginas que usam a predefinição data com erros de sintaxe]]'
    end
    resultado = params .. cat
    end
 
    return resultado or ''
end

function fun._modeloData( args )
    local ano, mes, numMes, dia = args.ano, args.mes, args.numMes, args.dia
    local qualificativo = args.qualificativo
 
    if ( ano or mes or dia ) == nil then
    return
    end
 
    -- agora tratamos a idade, o nascimento e a morte
    local idade = args['idade'] and  fun.idade( ano, numMes, dia )
    local nascimento = args.nascimento
    local morte = args.morte
 
    -- tratar o calendário
    local gano, gmes, gdia = ano, numMes, dia    -- dados de acordo com o calendário gregoriano para <hora>
    local jano, jmes, jdia = ano, mes, dia      -- dados de acordo com o calendário juliano, se necessário
    local julianoData, julianoSup, julianoSep         -- pode ser usado para exibir os dados de acordo com o calendário juliano
    local gregAprMes, gregAprAno, gregFim       -- Mensagem do calendário gregoriano quando os dados estão de acordo com o calendário juliano
    if ano and dia then
    local amj = ano * 10000 + numMes * 100 + dia
    if amj < 15821014 then
        if ano > 0 then
           gano, gmes, gdia = fun.julianToGregorian( ano, numMes, dia )
        else
       -- Calendário gregoriano proléptico com ano 0.
           gano, gmes, gdia = fun.julianToGregorian( ano + 1, numMes, dia )
        end
        args.juliano = false
   
    elseif args.juliano then
        gano, gmes, gdia = fun.julianToGregorian( ano, numMes, dia )
        ano, mes, dia = gano, listaMes[gmes].nome, gdia
        if args.compacto then
           jmes = listaMes[ jmes ].abrev
        end
        if args.juliano == 'curto' then
           julianoData = jdia .. ' ' .. jmes .. ' '
           julianoSup = '<sup>[[calendário juliano|jul.]]</sup>'
           if jano == ano then
              gregAprMes = '<sup>[[calendário gregoriano|greg.]]</sup>'
           else
              julianoData = julianoData .. jano .. ' '
              gregAprAno = '<sup>[[calendário gregoriano|greg.]]</sup>'
           end
           julianoSep = ' / '
        else
           julianoData = jdia .. ' ' .. jmes .. ' ' .. jano
           julianoSep = ' ('
           gregFim = ' [[Mudança para o calendário gregoriano|dentro do calendário gregoriano]])'
        end
    end
    else
       if ano and ano < 0 then
          gano = gano + 1
       end
       args.juliano = false
 
    end
 
    -- agora geramos o resultado
 
    -- Declarações de variáveis
    local wikiLista = {}           -- recebe a mensagem de texto exibida para cada parâmetro
    local iso = {}             -- recebe o formato de data ISO de cada parâmetro
    local textoMes = mes         -- mensagem de texto que será exibida (possivelmente a abreviação)
    if args.compacto then
       if args.nolinks then
             textoMes = '<abbr class=abbr title="' .. mes .. '">' .. listaMes[ mes ].abrev .. '</abbr>'
       else
      textoMes = listaMes[ mes ].abrev
       end
    end
 
    local dataQualificativo, dataCat
    if not args.nolinks then
    dataQualificativo = dataLinks[qualificativo or '']
    if type( dataQualificativo ) ~= 'table' then
        -- se o qualificador não estiver no banco de dados, criamos uma tabela mínima,
        -- que vai impor um teste no ano, mas considera que não há link no dia ou no mes
        dataQualificativo = { qualificativo = ' ' .. qualificativo, ano = { } }
    end
    dataCat = dataLinks[dataQualificativo.cat]
    if type( dataCat ) ~= 'table' or dataCat == dataQualificativo then
        dataCat = { qualificativo = '' }
    end
    end
    local function wikiLink( link, texto )
    if link == texto then
        return '[[' .. texto .. ']]'
    else
        return '[[' .. link .. '|' .. texto .. ']]'
    end
    end
 

    -- o dia se informado
    local qualifDia = ''
    if dia then
    local textoDia = dia
    if args.nolinks then
        table.insert( wikiLista,  dia )
    else
        qualifDia = dataQualificativo.dia and dataQualificativo.qualificativo
        or dataCat.dia and dataCat.qualificativo
        or ''
        local link = dia .. ' de ' .. mes .. ' ' .. qualifDia
        -- se não houver um link no mes, ele será exibido com o dia.
        table.insert( wikiLista,  wikiLink( link, dia ) )
        table.insert( wikiLista,  wikiLink( link, dia .. ' de '.. textoMes ) )
    end
    table.insert( iso,  1, string.sub( '0' .. gdia, -2 ) )
    end
 
    -- o mês
    if mes then
    if #wikiLista == 0 and ano == nil then
        return textoMes
    end
    if args.nolinks then
        if not args.escondermes then
        table.insert( wikiLista,  textoMes )
        end
    else
        local link
        if ano then
        link = existData( dataQualificativo, ano, mes ) or existData( dataCat, ano, mes )
        if link == nil and qualificativo and qualifDia == '' then
            -- teste novo teste sem o qualificador somente se não houver efemérides para esse qualificador.
            link = existData( dataLinks[''], ano, mes )
        end
        end
        if link or args.escondermes then
        -- se houver um link, remova o link que exibe 'dia mes' para adicionar 'Mês dia'
        table.remove( wikiLista )
        if not args.escondermes then
            table.insert( wikiLista,  wikiLink( link, textoMes ) )
        end
        elseif #wikiLista > 0 then
        -- caso contrário, removemos o link exibindo 'dia' para manter apenas o link 'dia mês'
        table.remove( wikiLista, #wikiLista - 1 )
        elseif args.esconderano then
        -- se não houver dia e o ano não for exibido, insira o único.
        table.insert( wikiLista,  textoMes )
        end
    end
    if gmes then
        table.insert( iso,    1, string.sub( '0' .. gmes, -2 ) )
        table.insert( wikiLista, ' de ' )
    end
    table.insert( wikiLista, gregAprMes )
    end
 
    -- o ano
    if ano and not (args.juliano == true and args.nolinks and jano == ano ) then
    if not args.esconderano then
        local textoAno = ano
        local link
        if ano < 0 then
        local anoaC = 0 - ano
        link = link or ( anoaC .. ' a.C.' )
        if args.ac == false then
            textoAno = anoaC
        else
            textoAno = anoaC .. ' <abbr class="abbr" title="'
            .. anoaC .. ' antes da Era Comum">a.C.</abbr>'
        end
        elseif args.ac then
        textoAno = textoAno .. ' <abbr class="abbr" title="'
            .. textoAno .. ' depois da Era Comum">depois de a.C.</abbr>'
        end
        if args.nolinks then -- somente se tivermos que exibi-lo
        table.insert( wikiLista,  textoAno )
        else
        link = existData( dataQualificativo, ano ) or existData( dataCat, ano ) or link or ano      
        if mes and #wikiLista == 0 then
            -- se o mes não tiver link e não for exibido com o dia, ele será exibido com o ano.
            textoAno = textoMes .. ' de ' .. textoAno
        end
        table.insert( wikiLista,  wikiLink( link, textoAno ) )
        end
    end
    end
    if ano then
       if gano > 999 then
             table.insert( iso,    1, gano )
       elseif gano > -1 then
             table.insert( iso,    1, string.sub( '000' .. gano , -4 ) )
       elseif gano > -999 then
           -- Calendário gregoriano proléptico com ano 0.
          table.insert( iso,    1, 'U-' .. string.sub( '000' .. ( 0 - gano ), -4 ) )
       else
          table.insert( iso,    1, 'U' .. gano )       
       end
    end
    table.insert( wikiLista, gregAprAno )

    -- a idade
    if type( idade ) == 'number' and idade >= 0 and ( not nascimento or idade < 120 ) then
    if idade == 0 then
        idade = '(menos de um ano)'
    elseif idade == 1 then
        idade = '(1 ano)'
    else
        idade = '(' .. idade .. ' anos)'
    end
    else
    idade = false
    end
 
 
    -- compilação dos resultados
    local wikiTexto = table.concat( wikiLista, ' ' )
    local isoTexto = table.concat( iso, '-' )
 
    -- Nós adicionamos um pouco de semântica.
    local wikiHtml = mw.html.create( '' )
 
    if julianoData then
    wikiHtml:tag( 'span')
        :addClass( 'nowrap' )
        :attr( 'date-sort-value', isoTexto )
        :wikitext( julianoData    )
        :node( julianoSup )
        :done()
        :wikitext( julianoSep )
    end
 
    local dataHtml = wikiHtml:tag( 'time' )
        :wikitext( wikiTexto )
    if wikiTexto:match( ' ' ) then
    dataHtml:addClass( 'nowrap' )
    end
    if isoTexto ~= wikiTexto then
    dataHtml:attr( 'datetime', isoTexto )
        :attr( 'date-sort-value', isoTexto )
    end
    if not args.nolinks then
    dataHtml:addClass( 'date-link' )
    end
    if nascimento then
    dataHtml:addClass( 'bday' )
    elseif morte then
    dataHtml:addClass( 'dday' )
    end
 
    wikiHtml:wikitext( gregFim )
 
    if idade then
    wikiHtml:wikitext( ' ' )
        :tag( 'span' )
            :addClass( 'noprint')
            :wikitext( idade )
            :done()
    end
 
    return tostring( wikiHtml )
end


---
-- função para infoboxes, especialmente para exibir nascimento e dados mortos
-- os links presentes nos dados fornecidos são automaticamente excluídos para gerenciar os casos ou
-- o parâmetro já contém um modelo de dados.
-- Configurações:
--      1 : type de dados a afixar (nascimento / n, morte / m, ou data / d)
--      1 : Data ou data de nascimento
--      2 : Data de morte se tipo n ou m
--      qualificativo = sufixo de páginas de dados para vincular (exemplo: na música)
--      nolinks : não mostrar link
--      préfixe : prefixo para exibir se houver um dia (por padrão '')
--      prefixo sem dia: prefixo a ser exibido se não houver dia (padrão: '')
function fun.dataInfobox( frame )
    local args = frame.args
    if type( args ) ~= 'table' or not ( args[1] and args[2] ) then
    return
    end
 
    -- analisarData separa os dados do conteúdo a seguir, exclui os links e, se possível, retorna uma tabela com os anos
    local function analisarData( d )
    if trim( d ) then
        local analisar = d:match( ' ou ') or d:match( 'entre ' ) or d:match( 'para ' ) or d:match( 'depois ' ) or d:match( 'antes ' )
        if analisar then
        return d
        end
        analisar = d:match( 'datetime="([%d-]+)"' ) or d
        -- separa os dados (com seus links) de uma referência ou conteúdo que começa com um espaço)
        local inicio, fim = analisar:match( '(.-%d%d%d%]*%-?)([\127 ].+)' )
        if not inicio then
        -- separa os dados do conteúdo começando com <br>
        inicio, fim = analisar:match( '(.-%d%d%d%]*%-?)(<br ?/?>.+)' )
        end
        analisar = inicio or analisar
        -- excluir links
        analisar = analisar:gsub(
        '%[%[([^%[%]|]*)|?([^%[%]]*)%]%]',
        function ( l, t )
            return trim( t ) or l
        end
        )
        local t, r = fun.separationDiaMesAno( analisar )
        if t then
        return r, fim
        else
        return d, fim
        end
    end
    end
    -- prefixo adiciona um prefixo dependendo da presença ou ausência do dia se o parâmetro "prefixo sem dia" for definido
    local function prefix( dataString )
    if dataString then
        local datetime = dataString:match( 'datetime="([U%d%-]+)"' )
        if datetime and datetime:match('%-%d%d%-%d%d') and trim( args['prefixo'] ) then
        return args['prefixo'] .. ' ' .. dataString
        end
        if trim( args['prefixo sem dia'] ) then
        return args['prefixo sem dia'] .. ' ' .. dataString
        end
    end
    return dataString
    end
 
    local nascimento = args[1]:match( '^n' ) == 'n'
    local morte = args[1]:match( '^m' ) or args[1]:match( 'morte' )
    local mostrarData, qualificativo = args[2], args[4]
    local mostrarDataTab, resultadoData, complementData
    local dataNascimento, dataMorte
    if morte then
    mostrarData = args[3]
    end
    if not trim( mostrarData ) then
    return
    end
    if mostrarData:match( '</time>' ) then
    -- Se houver links, provavelmente já existe um modelo de dados, evite executá-lo uma segunda vez
    if ( nascimento or morte ) and ( mostrarData:match( 'wikidata%-linkback' ))  then
        dataNascimento = analisarData( args[2] )
        dataMorte = analisarData( args[3] )
        resultadoData = mostrarData
    else
        return prefix( mostrarData )
    end
    else
    mostrarDataTab, complementData = analisarData( mostrarData )
    if type( mostrarDataTab ) ~= 'table' then
        return mostrarDataTab
    else
        if nascimento then
        dataNascimento = mostrarDataTab
        dataMorte = analisarData( args[3] )
        elseif morte then
        dataNascimento = analisarData( args[2] )
        dataMorte = mostrarDataTab
        else
        qualificativo = args[3]
        end
        mostrarDataTab.nascimento = nascimento
        mostrarDataTab.morte = morte
        mostrarDataTab.qualificativo = args.qualificativo or qualificativo
        mostrarDataTab.nolinks = args.nolinks
        mostrarDataTab.nocat = args.nocat
        mostrarDataTab.juliano = args.juliano
    end
    end
    resultadoData = resultadoData or fun.modeloData( mostrarDataTab )
 
    local idade, prefixIdade, suffixIdade, calculoIdade = '', ' <span class="noprint">(', ')</span>', nil
    if nascimento and
    dataNascimento and
    not dataMorte and
    type( dataNascimento ) == 'table'
    then
    calculoIdade = fun.idade( dataNascimento.ano, dataNascimento.numMes, dataNascimento.dia )
    if calculoIdade and calculoIdade > 120 then
        calculoIdade = nil
    end
    elseif morte and
    dataNascimento and
    dataMorte and
    type( dataNascimento ) == 'table'
    and type( dataMorte ) == 'table'
    then
    calculoIdade = fun.idade(
        dataNascimento.ano,
        dataNascimento.numMes,
        dataNascimento.dia,
        dataMorte.ano,
        dataMorte.numMes,
        dataMorte.dia
    )
    prefixIdade = ' (a '
    suffixIdade = ')'
    end
    if tonumber( calculoIdade ) then
    if calculoIdade > 1 then
        idade = prefixIdade .. calculoIdade .. ' anos' .. suffixIdade
    elseif calculoIdade == 1 then
        idade = prefixIdade .. 'um ano' .. suffixIdade
    elseif calculoIdade == 0 then
        idade = prefixIdade .. 'menos de um ano' .. suffixIdade
    end
    if complementData and complementData:match( 'anos?%)' ) then
        complementData = ''
    end
    end
 
    return prefix( resultadoData ) .. ( complementData or '' ) .. idade
end


---
-- a função dataISO retorna um dado no formato aaaa-mm-dd (sem links)
-- o ano pode ser na forma 2013 ou [[2013 na literatura|2013]]
-- o mês pode estar em letra ou em número
-- o dia pode estar no formato '5',  ou 'Sexta 13'

function fun.dataISO( frame )
    local args = Ferramentas.extractArgs( frame )
    local ano = Ferramentas.notEmpty( args.ano, args.year, args.data )
    -- extração do ano
    if type( ano ) == 'string' then
    ano = ( tonumber( ano )       -- match '2013'
        or string.match ( ano, '%D(%d%d%d%d)%D' ) -- match  '[[2013 na música|2013]]'
        or string.match ( ano, '%D(%d%d%d%d)%D$' )  -- match '17 de setembro de 2013'
        or string.match ( ano, '^(%d%d%d%d)%D' )  -- match '2013-09-17'
    )
    end
    ano = tonumber( ano )
 
    -- O formato iso de dados é definido de acordo com o calendário gregoriano.
    -- Antes do ano de 1583 os dados são calendários é provavelmente do calendário juliano,
    -- então se abstenha.
    if ano and ano > 1582  then
    local mes = Ferramentas.notEmpty( args.mes, args.month )
    -- num mês encontra o número de mês, seja númerico ou texto, completo ou abreviado.
    local nomeMes, numMes = fun.determinationMes( mes )
    if numMes then
        mes = '-' .. string.sub( '0' .. numMes, -2 )
   
        local dia = Ferramentas.notEmpty( args.dia, args.day, args['calendário'] )
        if type( dia ) == 'string' then
        dia = tonumber( dia ) or tonumber( string.match ( dia, '%d+') )
        end
        dia = tonumber( dia )
        if dia and dia <= listaMes[numMes].nDia then
        dia = '-' .. string.sub( '0' .. dia, -2 )
        return ano .. mes .. dia
        else
        return ano .. mes
        end
    else
        return tostring( ano )
    end
    end
end

---
-- Rank do dia no ano
-- Uso: do_dayRank {ano, meu, dia}
function fun.do_dayRank(arguments)
    local yr = tonumber(arguments.year or arguments[1]) or 1
    local mt = tonumber(arguments.month or arguments[2]) or 1
    local dy = tonumber(arguments.day or arguments[3]) or 1
    -- Classificações do primeiro do mês
    local ranks = {0,31,59,90,120,151,181,212,243,273,304,334}
 
    local rank = (ranks[mt] or 0) + dy - 1
    if(fun.isLeapYear(yr) and (mt >= 3)) then
    rank = rank+1
    end
    return rank
end

-- Número de dias entre dois anos (de 1 de janeiro a 1 de janeiro)
-- Segue o calendário gregoriano
function fun.do_daysBetween(arguments)
    local yr1 = tonumber(arguments[1]) or 0
    local yr2 = tonumber(arguments[2]) or 0
 
    return fun.daysSinceOrigin(yr2) - fun.daysSinceOrigin(yr1)
end

-- Número de dias desde o ano 1 (de 1 de janeiro a 1 de janeiro)
function fun.daysSinceOrigin(year)
    local yr = year-1
    return 365*yr + math.floor(yr/4) - math.floor(yr/100) + math.floor(yr/400)
end

-- Teste do ano bissexto (segue o calendário gregoriano)
function fun.isLeapYear(year)
    local yr = tonumber(year) or 1
    return (yr%4 == 0) and ((yr%100 ~= 0) or (yr%400 == 0))
end

-- Convertendo um número em algarismos romanos
function fun.toRoman(number)
    local n = math.floor(number)
    local letters = {"I","V","X","L","C","D","M","",""}
    local pattern = {"","0","00","000","01","1","10","100","1000","02"}
    local result = ""
    if(n<=0 or n>=4000) then
    result = "---"
    else
    for i=1,7,2 do
        local p = pattern[n%10 + 1]
        for j=0,2 do
        p = string.gsub(p,tostring(j),letters[i+j])
        end
        result = p .. result
        n = math.floor(n/10)
    end
    end
    return result
end

---
-- Calculando um dado no calendário republicano
-- Supõe-se que os anos 4n + 3 são mais sextiles (3, 7, 11 ...)
function fun.do_toRepCal(arguments)
    local yr = tonumber(arguments.year or arguments[1]) or 2000
    -- gama absoluto do dia solicitado, sendo o dia 0 ou 22 de setembro de 1792 (1º dia do ano I)
    local repDays = fun.do_dayRank(arguments) + fun.do_daysBetween{1792,yr} - fun.do_dayRank{1792,9,22}
    local repYear = math.floor((repDays+731)/365.25) - 1
    local repDayRank = repDays - 365*(repYear-1) - math.floor(repYear/4)
    local repMonth, repDay = math.floor(repDayRank/30)+1, (repDayRank%30)+1
    return {repYear, repMonth, repDay}
end

---
-- Ver Predefinição:Idade
-- retorna a idade de acordo com os dados fornecidos. O valor retornado é do tipo 'número'
-- Parâmetros:
-- 1, 2, 3: ano, mês dia de nascimento (suposto no calendário gregoriano)
-- 4, 5, 6: year, mês, dia do cálculo (opcional, por padrão, os dados UTC atuais).
function fun.idade( an, mn, jn, ac, mc, jc )
    if ac == nil then
    local today = os.date( '!*t' )
    ac = today.year
    mc = today.month
    jc = today.day
    else
    ac = tonumber( ac )
    mc = tonumber( mc )
    jc = tonumber( jc )
    end

    local an = tonumber( an )
    local mn = tonumber( mn )
    local jn = tonumber( jn )

    if an == nil or ac == nil or mn == nil or mc == nil then
    -- nenhuma mensagem de erro que possa travar a função de chamada
    -- para ela gerenciar esse retorno.
    return
    end
 
    local idade = ac - an
    if mc == mn then
    if jc == nil or jn == nil then
        return
    end
    return idade-tonumber( jc < jn and 1 or 0 )
    else
    return idade-tonumber( mc < mn and 1 or 0 )
    end
end

function fun.modeloIdade( frame )
    local args = frame.getParent().args
    local idade = fun.idade (
    args[1] or args['ano'],
    args[2] or args['mês'] or args['mes'],
    args[3] or args['dia'],
    args[4],
    args[5],
    args[6]
    )
    if idade then
    return idade
    else
    return '<span class="error">Parâmetro incorretos ou insuficientes para calcular a precisão da idade</span>'
    end
end

---
-- calcula o dia juliano à partir de uma data do calendário gregoriano
function fun.julianDay( year, month, day, hour, min, sec )
    local julian
    julian = math.floor( math.floor( ( year * 12 + month + 57609 ) / 12 - 1 ) * 1461 / 4 )
        - math.floor( math.floor( ( year * 12 + month + 57609 ) / 12 - 1 ) / 100 )
        + math.floor( math.floor( ( year * 12 + month + 57609 ) / 12 - 1 ) / 400 )
        + math.floor( ( math.fmod( month + 57609, 12 ) + 4 ) * 153 / 5 )
        + day + ( hour or 12 ) / 24 + ( min or 0 ) / 1440 + ( sec or 0 ) / 86400
        - 32167.5
    return julian
end

---
-- cálculo do dia juliano a partir de um dado do calendário juliano
function fun.julianDayJulian( year, month, day, hour, min, sec )
    local julian
    julian = math.floor( math.floor( ( year * 12 + month + 57609 ) / 12 - 1 ) * 1461 / 4 )
        + math.floor( ( math.fmod( month + 57609, 12 ) + 4 ) * 153 / 5 )
        + day + ( hour or 12 ) / 24 + ( min or 0 ) / 1440 + ( sec or 0 ) / 86400
        - 32205.5
    return julian
end

---
-- cálculo de um dado no calendário gregoriano do dia juliano
function fun.julianDayToGregorian( julianDay )
    local base = math.floor( julianDay + 32044.5 )    -- 1 March -4800 (proleptic Gregorian data)
    local nCentury = math.floor( ( base * 4 + 3 ) / 146097 )
    local sinceCentury = base - math.floor( nCentury * 146097 / 4 )
    local nYear = math.floor( ( sinceCentury * 4 + 3 ) / 1461 )
    local sinceYear = sinceCentury - math.floor( nYear * 1461 / 4 )
    local nMonth = math.floor( ( sinceYear * 5 + 2 ) / 153 )
 
    local day = sinceYear - math.floor( (  nMonth  * 153 + 2 ) / 5 ) + 1
    local month = nMonth  - math.floor(     nMonth     / 10 ) * 12 + 3
    local year = math.floor( sinceYear / 306 ) + nYear + 100 * nCentury - 4800
 
    return year, month, day
end

---
-- cálculo de um dado no calendário juliano do dia juliano
function fun.julianDayToJulian( julianDay )
    local year = math.modf( ( julianDay * 4 - 6884469 ) / 1461 )
    local r2 = julianDay - math.modf( ( 1461 * year + 6884472 ) / 4 )
    local month = math.modf( ( 5 * r2 + 461 ) / 153 )
    local day = r2 - math.modf( ( 153 * month - 457 ) / 5 ) + 1
    if month > 12 then
    year = year + 1
    month = month - 12
    end
    return year, month, day
end

---
-- cálculo de um dado no calendário gregoriano a partir de um dado no calendário juliano
function fun.julianToGregorian( year, month, day )
    return fun.julianDayToGregorian( fun.julianDayJulian( year, month, day ) )
end

---
-- cálculo de um dado no calendário juliano a partir de um dado no calendário gregoriano
function fun.gregorianToJulian( year, month, day )
    year = tonumber(year)
    if month then month = tonumber(month) else month = 6 end --leva um valor central para dar um melhor "palpite"
    if day then day = tonumber(day) else day = 15 end
    return fun.julianDayToJulian( fun.julianDay( year, month, day ) )
end


--[[
  Esta função devolve "CET" ou "CEST" dependendo se no pseudo timezone atual
     é hora de verão ou inverno.
   Esta função só faz sentido para predefinições usados na Europa
 
   Parâmetro opcional sem nome: "sem link": retorna o texto CET / CEST. caso contrário
     retornar o mesmo texto com um wikilink para os artigos correspondentes
--]]
function fun.CEST(frame)
    -- opção : não crie wikilink
    local opt = trim(frame.args[1] or frame:getParent().args[1])
    -- recuperamos as informações na zona atual
    local t = mw.getContentLanguage():formatDate("I", nil, true)
 
    if (t == "1") then    -- hora de Verão
    if (opt == "sem link") then
        return "CEST"
    elseif (opt == "desvio") then
        return "2"
    else
        return "[[Horário de Verão da Europa Central|CEST]]"
    end
    else  -- horário de inverno (ou outra área onde não se aplica)
    if (opt == "sem link") then
        return "CET"
    elseif (opt == "desvio") then
        return "1"
    else
        return "[[Horário da Europa Central|CET]]"
    end
    end
end

return fun