Модуль:Wikidata/Дати


Обчислює і відображає дати народження і смерті з Вікіданих з підтримкою відображення дат за старим стилем, якщо в Вікідані вказана модель пролептичного юліанського календаря (d:Q1985786).

Виклик всіх функцій даного шаблону здійснюється тільки з проміжних шаблонів вікідані/pNNN, wikidata/pNNN:

Тести

Усі тести пройдено.

test_formatAsJulian:

Текст Очікуваний Фактичний
YesТак {{#invoke:Wikidata/Дати | formatAsJulian |-14825980800}} 28 лютого 1500 28 лютого 1500
YesТак {{#invoke:Wikidata/Дати | formatAsJulian |-14825894400}} 29 лютого 1500 29 лютого 1500
YesТак {{#invoke:Wikidata/Дати | formatAsJulian |-14825808000}} 1 березня 1500 1 березня 1500
YesТак {{#invoke:Wikidata/Дати | formatAsJulian |-11670220800}} 28 лютого (9 березня) 1600 28 лютого (9 березня) 1600
YesТак {{#invoke:Wikidata/Дати | formatAsJulian |-11670134400}} 29 лютого (10 березня) 1600 29 лютого (10 березня) 1600
YesТак {{#invoke:Wikidata/Дати | formatAsJulian |-11670048000}} 1 (11) березня 1600 1 (11) березня 1600
YesТак {{#invoke:Wikidata/Дати | formatAsJulian | -8514460800}} 28 лютого (10 березня) 1700 28 лютого (10 березня) 1700
YesТак {{#invoke:Wikidata/Дати | formatAsJulian | -8514374400}} 29 лютого (11 березня) 1700 29 лютого (11 березня) 1700
YesТак {{#invoke:Wikidata/Дати | formatAsJulian | -8514288000}} 1 (12) березня 1700 1 (12) березня 1700
YesТак {{#invoke:Wikidata/Дати | formatAsJulian | -2208988800}} 20 грудня 1899 (1 січня 1900) 20 грудня 1899 (1 січня 1900)
YesТак {{#invoke:Wikidata/Дати | formatAsJulian | -1734480000}} 2 (15) січня 1915 2 (15) січня 1915
YesТак {{#invoke:Wikidata/Дати | formatAsJulian | 946684800}} некоректна дата (юліанський календар не використовується після 1918-01-26) некоректна дата (юліанський календар не використовується після 1918-01-26)

--settings
local categoryUnknownBirthDate = '[[Категорія:Персоналії без дати народження]]';
local categoryUnknownDeathDate = '[[Категорія:Персоналії без дати смерті]]';
local categoryBigCurrentAge = '[[Категорія:Статті про персоналії з великим поточним віком]]'
local categoryBigDeathAge = '[[Категорія:Статті про персоналії з великим віком на час смерті]]'
 
local moduleDates = require( "Module:Dates" )
local moduleWikidata = require( "Module:Wikidata" )
 
function deepcopy(orig)
    local orig_type = type(orig)
    local copy
    if orig_type == 'table' then
        copy = {}
        for orig_key, orig_value in next, orig, nil do
            copy[deepcopy(orig_key)] = deepcopy(orig_value)
        end
        setmetatable(copy, deepcopy(getmetatable(orig)))
    else -- number, string, boolean, etc
        copy = orig
    end
    return copy
end
 
-- accepts table of time+precision values
function ageCurrent ( bTable )
    local possibleAge = "NYA" -- it meansm "Not Yet Assigned", not what you imagined!
 
    for bKey, bValue in pairs(bTable) do
        if ( bValue.unknown ) then
            return nil
        end
        local bStructure = bValue.structure
        local bPrecision = bValue.precision
 
        local dStructure = os.date( "*t" )
 
        local calculatedAge = ageImpl ( bStructure, bPrecision, dStructure, 11 )
        if ( possibleAge == "NYA" ) then
            possibleAge = calculatedAge
        else
            if ( possibleAge ~= calculatedAge ) then
                possibleAge = nil
            end
        end
    end
 
    return possibleAge
end
 
-- accepts tables of time+precision values
function age ( bTable, dTable )
    local possibleAge = "NYA" -- it meansm "Not Yet Assigned", not what you imagined!
 
    for bKey, bValue in pairs( bTable ) do
        if ( bValue.unknown ) then
            return nil
        end
        local bStructure = bValue.structure
        local bPrecision = bValue.precision
 
        for dKey, dValue in pairs( dTable ) do
            if ( dValue.unknown ) then
                return nil
            end
            local dStructure = dValue.structure
            local dPrecision = dValue.precision
 
            local calculatedAge = ageImpl ( bStructure, bPrecision, dStructure, dPrecision )
            if ( possibleAge == "NYA" ) then
                possibleAge = calculatedAge
            else
                if ( possibleAge ~= calculatedAge ) then
                    possibleAge = nil
                end
            end
        end
    end
 
    return possibleAge
end
 
function ageImpl ( bStructure, bPrecision, dStructure, dPrecision )
    if ( not bStructure or not dStructure or bPrecision < 10 or dPrecision < 10 ) then
        return nil
    end
 
    if ( bPrecision == 10 or dPrecision == 10 ) then
        if ( bStructure.month < dStructure.month ) then
            return dStructure.year - bStructure.year
        end
        if ( bStructure.month == dStructure.month ) then
            return nil
        end
        if ( bStructure.month > dStructure.month ) then
            return dStructure.year - bStructure.year - 1
        end
    end
 
    if ( bStructure.month < dStructure.month ) then
        return dStructure.year - bStructure.year
    end
    if ( bStructure.month == dStructure.month ) then
        if ( bStructure.day <= dStructure.day ) then
            return dStructure.year - bStructure.year
        else 
            return dStructure.year - bStructure.year - 1
        end
    end
    if ( bStructure.month > dStructure.month ) then
        return dStructure.year - bStructure.year - 1
    end
 
    return nil
end
 
-- returns table of time+precision values for specified property
function parseProperty ( context, options, propertyId )
    if ( not context ) then error( 'context not specified'); end;
    if ( not options ) then error( 'options not specified'); end;
    if ( not options.entity ) then error( 'options.entity is missing'); end;
    if ( not propertyId ) then error( 'propertyId not specified'); end;
 
    local claims = context.selectClaims( options, propertyId );
    if not claims then
        return nil
    end
 
    local result = {}
    for key, claim in pairs( claims ) do
        table.insert ( result, parseClaim( claim ) );
    end
    return result
end
 
function parseClaim ( claim )
    if ( claim.mainsnak.snaktype == "value" ) then
        -- The calendar model used for saving the data is always the proleptic Gregorian calendar according to ISO 8601.
        local timeISO8601 = string.gsub( string.gsub( tostring( claim.mainsnak.datavalue.value.time ), '-00%-', '-01-' ), '-00T', '-01T' )
        local unixtime = parseISO8601( timeISO8601 )
        local structure = os.date("*t", unixtime)
        local precision = tonumber( claim.mainsnak.datavalue.value.precision )
        local item = { structure=structure, precision=precision }
        return item;
    elseif ( claim.mainsnak.snaktype == "novalue" ) then
        -- novalue
        return { unknown="novalue" };
    else
        --unknown 
        return { unknown="unknown" };
    end
end
 
-- проверка на совпадающие даты с разной моделью календаря
function checkDupDates( t )
    if #t > 1 then
        local removed = false;
        local j = 1;
        -- проверка на совпадающие даты с разной моделью календаря
        while (j <= #t)  do
            local i = 1;
            while (i <= #t)  do
                if i ~= j then
                    if (os.time(t[j].structure) == os.time(t[i].structure)) then
                        if ((t[j].calendarmodel == 'gregorian') and 
                            (t[i].calendarmodel == 'julian')) then
                            removed = true;
                            break;
                        else
                            table.remove(t, i)
                        end
                    else
                      i = i + 1;
                    end
                else
                    i = i + 1;
                end
            end
            if removed then
                removed = false;
                table.remove(t, j);
            else
                j = j+1;
            end
        end
    end
end
 
-- returns first qualifier of specified propertyId
function getQualifierDataValue( statement, qualifierPropertyId )
    if ( statement.qualifiers
            and statement.qualifiers[qualifierPropertyId] ) then
        local qualifiers = statement.qualifiers[qualifierPropertyId];
        for _, qualifier in ipairs( qualifiers ) do
            if (qualifier.datavalue) then
                return qualifier.datavalue;
            end
        end
    end
    return nil;
end
 
local p = {}
 
function p.formatDateOfBirthClaim( context, options, statement )
    if ( not context ) then error( 'context not specified'); end;
    if ( not options ) then error( 'options not specified'); end;
    if ( not options.entity ) then error( 'options.entity is missing'); end;
    if ( not statement ) then error( 'statement not specified'); end;
 
    if ( statement.mainsnak.snaktype == 'somevalue' ) then
        local qNotSoonerThan = getQualifierDataValue( statement, 'P1319' );
        local qNotLaterThan = getQualifierDataValue( statement, 'P1326' );
        if ( qNotSoonerThan or qNotLaterThan ) then
            local results = {};
            if ( qNotSoonerThan ) then
                table.insert( results, 'не&nbsp;раніше&nbsp;' .. formatDateImpl( qNotSoonerThan.value, {}, nil, nil ) );
            end
            if ( qNotLaterThan ) then
                table.insert( results, 'не&nbsp;пізніше&nbsp;' .. formatDateImpl( qNotLaterThan.value, {}, nil, nil ) );
            end
            return mw.text.listToText( results, ' і ' , ' і ' ) .. categoryUnknownBirthDate;
        end
    end
 
    options['conjunction'] = '&#32;або&#32;';
    options['value-module'] = 'Wikidata/Дати';
    options['value-function'] = 'formatBirthDate';
    options.i18n.somevalue = '\'\'невідомо\'\'' .. categoryUnknownBirthDate;
    options.i18n.approximate = '<span style="border-bottom: 1px dotted; cursor: help;" title="близько">бл. </span>';
    local result = context.formatStatementDefault( context, options, statement );

    local bTable = { parseClaim( statement ) };
    local dTable = parseProperty ( context, options, 'P570' )
 
    if ( bTable and not dTable ) then
        local age = ageCurrent( bTable )
        if ( age ) then
            result = result .. ' <span style="white-space:nowrap;">(' .. age .. ' ' .. mw.language.new( 'uk' ):plural( age, 'рік', 'роки', 'років') .. ')</span>'
            if ( age > 150 and not options.nocat ) then
                result = result .. categoryBigCurrentAge;
            end
        end
    end
 
    return result;
end
 
function p.formatDateOfDeathClaim( context, options, statement )
    local result = p.formatDateOfDeathClaimWithoutAge( context, options, statement )
 
    local bTable = parseProperty ( context, options, 'P569' )
    local dTable = { parseClaim( statement ) };
 
    if ( bTable and dTable ) then
        local age = age( bTable, dTable )
        if ( age ) then
            result = result .. ' <span style="white-space:nowrap;">(' .. age .. ' ' .. mw.language.new( 'uk' ):plural( age, 'рік', 'роки', 'років') .. ')</span>'
            if ( age > 150 and (not context.options or not context.options.nocat) ) then
                result = result .. categoryBigDeathAge;
            end
        end
    end
 
    return result;
end

function p.formatDateOfDeathClaimWithoutAge( context, options, statement )
    if ( not context ) then error( 'context not specified'); end;
    if ( not options ) then error( 'options not specified'); end;
    if ( not options.entity ) then error( 'options.entity is missing'); end;
    if ( not statement ) then error( 'statement not specified'); end;
 
    if ( statement.mainsnak.snaktype == 'somevalue' ) then
        local qNotSoonerThan = getQualifierDataValue( statement, 'P1319' );
        local qNotLaterThan = getQualifierDataValue( statement, 'P1326' );
        if ( qNotSoonerThan or qNotLaterThan ) then
            local results = {};
            if ( qNotSoonerThan ) then
                table.insert( results, 'не&nbsp;раніше&nbsp;' .. formatDateImpl( qNotSoonerThan.value, {}, nil, nil ) );
            end
            if ( qNotLaterThan ) then
                table.insert( results, 'не&nbsp;пізніше&nbsp;' .. formatDateImpl( qNotLaterThan.value, {}, nil, nil ) );
            end
            return mw.text.listToText( results, ' і ' , ' і ' ) .. categoryUnknownDeathDate;
        end
    end
 
    options['conjunction'] = '&#32;або&#32;';
    options['value-module'] = 'Wikidata/Дати';
    options['value-function'] = 'formatDeathDate';
    options.i18n.somevalue = '\'\'невідомо\'\'' .. categoryUnknownDeathDate;
    options.i18n.approximate = '<span style="border-bottom: 1px dotted; cursor: help;" title="близько">бл. </span>';
    return context.formatStatementDefault( context, options, statement );
end
 
-- Reentry point for Wikidata Snak formatting
function p.formatBirthDate( context, options, value )
    if ( not context ) then error( 'context not specified'); end;
    if ( not options ) then error( 'options not specified'); end;
    if ( not value ) then error( 'value not specified'); end;
 
    if ( options.nocat ) then
        return formatDateImpl( value, options, options.microformat, nil )
    else
        return formatDateImpl( value, options, options.microformat, 'Народились' )
    end
end
 
-- Reentry point for Wikidata Snak formatting
function p.formatDeathDate( context, options, value )
    if ( not context ) then error( 'context not specified'); end;
    if ( not options ) then error( 'options not specified'); end;
    if ( not value ) then error( 'value not specified'); end;
 
    if ( options.nocat ) then
        return formatDateImpl( value, options, options.microformat, nil )
    else
        return formatDateImpl( value, options, options.microformat, 'Померли' )
    end
end
 
-- Reentry point for Wikidata Snak formatting -- default one
function p.formatDate( context, options, value )
    if ( not context ) then error( 'context not specified'); end;
    if ( not options ) then error( 'options not specified'); end;
    if ( not value ) then error( 'value not specified'); end;
 
    local microformatClass = options.microfortmat or nil;
    if ( options.nocat ) then
        return formatDateImpl( value, options, microformatClass, nil )
    else
        local categoryPrefix = options.categoryPrefix or nil;
        return formatDateImpl( value, options, microformatClass, categoryPrefix )
    end
end
 
function formatDateImpl( value, options, microformatClass, categoryPrefix )
    if ( not value ) then error( 'value not specified'); end;
    if ( not options ) then error( 'options not specified'); end;
 
    -- The calendar model used for saving the data is always the proleptic Gregorian calendar according to ISO 8601.
    local timeISO8601 = string.gsub( string.gsub( tostring( value.time ), '-00%-', '-01-' ), '-00T', '-01T' )
    local unixtime = parseISO8601( timeISO8601 )
 
    local structure = os.date("*t", unixtime)
    local precision = options.precision or tonumber( value.precision )
 
    if precision == 7 then
        return formatCentury(structure, categoryPrefix)
    end
 
    if precision == 8 then
        return formatDecade(structure, categoryPrefix)
    end
 
    if precision == 9 then
        local tCopy = deepcopy( structure )
        tCopy.day = nil
        tCopy.month = nil
        return moduleDates.formatWikiImpl(tCopy, tCopy, microformatClass, categoryPrefix)
    end
 
    -- year and month only
    if precision == 10 then
        local tCopy = deepcopy( structure )
        tCopy.day = nil
        return moduleDates.formatWikiImpl(tCopy, tCopy, microformatClass, categoryPrefix)
    end
 
    local calendarmodel = 'gregorian';
    if (mw.ustring.find(value.calendarmodel, 'Q1985786', 1, true)) then
        calendarmodel = 'julian';
    end
 
    if (calendarmodel == 'gregorian') then
        return moduleDates.formatWikiImpl( structure, structure, microformatClass, categoryPrefix )
    else
        return p.formatAsJulian( unixtime, microformatClass, categoryPrefix )
    end
end
 
function formatDecade( time, categoryNamePrefix )
    local year = math.floor( time.year / 10 ) * 10;
    if ( categoryNamePrefix ) then
        return year .. '-е[[К:' .. categoryNamePrefix .. ' у ' .. year .. '-і роки]]';
    else
        return '' .. year;
    end
end
 
function formatCentury( time, categoryNamePrefix )
    local moduleRoman = require( "Module:RomanNumber" )
    if ( time.year < 0 ) then
        local century = math.floor( (math.abs( time.year) - 1) / 100 ) + 1;
        if ( moduleRoman ) then
            century = moduleRoman.toRomanNumber( century );
        end
        if ( categoryNamePrefix ) then
            return '[[' .. century .. ' століття до н. е.]][[К:' .. categoryNamePrefix .. ' у ' .. century .. ' столітті до н. е.]]'
        else
            return '[[' .. century .. ' століття до н. е.]]'
        end
    else
        local century = math.floor( (time.year - 1) / 100 ) + 1;
        if ( moduleRoman ) then
            century = moduleRoman.toRomanNumber( century );
        end
        if ( categoryNamePrefix ) then
            return '[[' .. century .. ' століття]][[К:' .. categoryNamePrefix .. ' у ' .. century .. ' столітті]]'
        else
            return '[[' .. century .. ' століття]]'
        end
    end
end
 
function parseISO8601Date(str)
    local pattern = "(%-?%d+)%-(%d+)%-(%d+)T"
    local Y, M, D = mw.ustring.match( str, pattern )
    return tonumber(Y), tonumber(M), tonumber(D)
end
 
function parseISO8601Time(str)
    local pattern = "T(%d+):(%d+):(%d+)%Z"
    local H, M, S = mw.ustring.match( str, pattern)
    return tonumber(H), tonumber(M), tonumber(S)
end
 
function parseISO8601Offset(str)
    if str:sub(-1)=="Z" then return 0,0 end -- ends with Z, Zulu time
 
    -- matches ±hh:mm, ±hhmm or ±hh; else returns nils 
    local pattern = "([-+])(%d%d):?(%d?%d?)$"
    local sign, oh, om = mw.ustring.match( str, pattern) 
    sign, oh, om = sign or "+", oh or "00", om or "00"
 
    return tonumber(sign .. oh), tonumber(sign .. om)
end
 
function parseISO8601(str)
    if 'table'==type(str) then
        if str.args and str.args[1] then
            str = '' .. str.args[1]
        else
            return 'unknown argument type: ' .. type( str ) .. ': ' .. table.tostring( str )
        end
    end
    local Y,M,D = parseISO8601Date(str)
    local h,m,s = parseISO8601Time(str)
    local oh,om = parseISO8601Offset(str)
    return tonumber(os.time({year=Y, month=M, day=D, hour=(h+oh), min=(m+om), sec=s}))
end
 
local lowestBoundary = parseISO8601('-900-02-20T00:00:00Z')
local mainBoundary = parseISO8601('1582-10-15T00:00:00Z')
local lastBoundary = parseISO8601('1918-01-26T00:00:00Z');
 
boundaries = {
    -- from (G) till next will be diff(G = J + diff), at current
    { lowestBoundary,                         -9 },
    { parseISO8601('-700-02-21T00:00:00Z'), -8 },
    { parseISO8601('-600-02-22T00:00:00Z'), -7 },
    { parseISO8601('-500-02-23T00:00:00Z'), -6 },
    { parseISO8601('-300-02-24T00:00:00Z'), -5 },
    { parseISO8601('-200-02-25T00:00:00Z'), -4 },
    { parseISO8601('-100-02-26T00:00:00Z'), -3 },
    { parseISO8601('0000-00-00T00:00:00Z'), -2 },
    { parseISO8601('0100-02-27T00:00:00Z'), -1 },
    { parseISO8601('0200-02-28T00:00:00Z'),  0 },
    { parseISO8601('0300-03-01T00:00:00Z'),  1 },
    { parseISO8601('0500-03-02T00:00:00Z'),  2 },
    { parseISO8601('0600-03-03T00:00:00Z'),  3 },
    { parseISO8601('0700-03-04T00:00:00Z'),  4 },
    { parseISO8601('0900-03-05T00:00:00Z'),  5 },
    { parseISO8601('1000-03-06T00:00:00Z'),  6 },
    { parseISO8601('1100-03-07T00:00:00Z'),  7 },
    { parseISO8601('1300-03-08T00:00:00Z'),  8 },
    { parseISO8601('1400-03-09T00:00:00Z'),  9 },
    { parseISO8601('1500-03-10T00:00:00Z'), 10 },
    { parseISO8601('1700-03-11T00:00:00Z'), 11 },
    { parseISO8601('1800-03-12T00:00:00Z'), 12 },
    { parseISO8601('1900-03-13T00:00:00Z'), 13 },
    { lastBoundary, '' },
};
 
-- Передаваемое время обязано быть по Григорианскому календарю (новому стилю)
function p.formatAsJulian( time, infocardClass, categoryNamePrefix )
    if 'table'==type( time ) then
        if time.args and time.args[1] then
            time = tonumber( time.args[1] )
        else
            return 'unknown argument type: ' .. type( time ) .. ': ' .. table.tostring( time )
        end
    end
 
    local t = os.date("*t", time)
    if ( time <= lowestBoundary ) then
        return "''некоректна дата (недосяжна точність)''";
    end
    if ( time >= lastBoundary ) then
        return "''некоректна дата (юліанський календар не використовується після 1918-01-26)''";
    end
 
    for i=1,#boundaries,1 do
        local b1 = boundaries[i][1];
        local b2 = boundaries[i + 1][1];
        if ( b1 <= time and time < b2 ) then
            local b1s = os.date("*t", b1)
            if ( b1s.year == t.year and b1s.month == t.month and b1s.day == t.day) then
                if ( time < mainBoundary ) then
                    -- only julian
                    return moduleDates.formatWikiImpl( {year=t.year, month=2, day=29}, {year=t.year, month=2, day=29}, infocardClass, categoryNamePrefix );
                else
                    --julian and grigorian
                    return moduleDates.formatWikiImpl( {year=t.year, month=2, day=29}, t, infocardClass, categoryNamePrefix )
                end
            else
                local julian = os.date("*t", time - boundaries[i][2] * 24 * 60 * 60);
                if ( time < mainBoundary ) then
                    -- only julian
                    return moduleDates.formatWikiImpl( julian, julian, infocardClass, categoryNamePrefix );
                else
                    --julian and grigorian
                    return moduleDates.formatWikiImpl( julian, t, infocardClass, categoryNamePrefix )
                end
            end
        end
    end
 
    if ( time >= lastBoundary ) then
        return "''помилка у модулі Модуль:Wikidata/Дати (не знайдений зсув конвертації календаря)''";
    end
end
 
return p
Увага! Опитування читачів ВУЕ. Заповнити анкету ⟶