Модуль:Wikidata/date

Для документации этого модуля может быть создана страница Модуль:Wikidata/date/doc

--settingslocal categoryUnknownBirthDate = '[[Category:Персоналии, чья дата рождения не установлена]]';local categoryUnknownDeathDate = '[[Category:Персоналии, чья дата смерти не установлена]]';local categoryBigCurrentAge = '[[Category:Википедия:Статьи о персоналиях с большим текущим возрастом]]'-- local categoryBigDeathAge = '[[Category:Википедия:Статьи о персоналиях с большим возрастом во время смерти]]' //deleted -d.bratchuk 05-07-2016local categoryBiographiesOfLivingPersons = '[[Category:Википедия:Биографии современников]]'local categoryRecentlyDeceased = '[[Category:Википедия:Персоналии, умершие менее года назад]]'local nowLabel = 'наст.&nbsp;время'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 copyend-- accepts table of time+precision valuesfunction ageCurrent ( bTable )local possibleAge = "NYA" -- it meansm "Not Yet Assigned", not what you imagined!for bKey, bValue in pairs(bTable) doif ( bValue.unknown ) thenreturn nilendlocal bStructure = bValue.structurelocal bPrecision = bValue.precisionlocal dStructure = os.date( "*t" )local calculatedAge = ageImpl ( bStructure, bPrecision, dStructure, 11 )if ( possibleAge == "NYA" ) thenpossibleAge = calculatedAgeelseif ( possibleAge ~= calculatedAge ) thenpossibleAge = nilendendendreturn possibleAgeend-- accepts tables of time+precision valuesfunction age ( bTable, dTable )local possibleAge = "NYA" -- it meansm "Not Yet Assigned", not what you imagined!for bKey, bValue in pairs( bTable ) doif ( bValue.unknown ) thenreturn nilendlocal bStructure = bValue.structurelocal bPrecision = bValue.precisionfor dKey, dValue in pairs( dTable ) doif ( dValue.unknown ) thenreturn nilendlocal dStructure = dValue.structurelocal dPrecision = dValue.precisionlocal calculatedAge = ageImpl ( bStructure, bPrecision, dStructure, dPrecision )if ( possibleAge == "NYA" ) thenpossibleAge = calculatedAgeelseif ( possibleAge ~= calculatedAge ) thenpossibleAge = nilendendendendreturn possibleAgeendfunction ageImpl ( bStructure, bPrecision, dStructure, dPrecision )if ( not bStructure or not dStructure or bPrecision < 10 or dPrecision < 10 ) thenreturn nilendlocal shift = 0;if ( bStructure.year < 0 and dStructure.year > 0 ) thenshift = -1;end if ( bPrecision == 10 or dPrecision == 10 ) then if ( bStructure.month < dStructure.month ) then return dStructure.year - bStructure.year + shift end if ( bStructure.month == dStructure.month ) then return nil end if ( bStructure.month > dStructure.month ) then return dStructure.year - bStructure.year - 1 + shift end end   if ( bStructure.month < dStructure.month ) then return dStructure.year - bStructure.year + shift end if ( bStructure.month == dStructure.month ) then  if ( bStructure.day <= dStructure.day ) then return dStructure.year - bStructure.year + shift else  return dStructure.year - bStructure.year - 1 + shift end end if ( bStructure.month > dStructure.month ) then return dStructure.year - bStructure.year - 1 + shift endreturn nilend-- returns table of time+precision values for specified propertyfunction 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 thenreturn nilendlocal result = {}for key, claim in pairs( claims ) dotable.insert ( result, parseClaim( claim ) );endreturn resultendfunction 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-- novaluereturn { unknown="novalue" };else--unknown return { unknown="unknown" };endend-- проверка на совпадающие даты с разной моделью календаряfunction checkDupDates( t )if #t > 1 thenlocal removed = false;local j = 1;-- проверка на совпадающие даты с разной моделью календаряwhile (j <= #t)  dolocal i = 1;while (i <= #t)  doif i ~= j thenif (os.time(t[j].structure) == os.time(t[i].structure)) thenif ((t[j].calendarmodel == 'gregorian') and (t[i].calendarmodel == 'julian')) thenremoved = true;break;elsetable.remove(t, i)endelse  i = i + 1;endelsei = i + 1;endendif removed thenremoved = false;table.remove(t, j);elsej = j+1;endendendend-- returns first qualifier of specified propertyIdfunction getQualifierDataValue( statement, qualifierPropertyId )if ( statement.qualifiersand statement.qualifiers[qualifierPropertyId] ) thenlocal qualifiers = statement.qualifiers[qualifierPropertyId];for _, qualifier in ipairs( qualifiers ) doif (qualifier.datavalue) thenreturn qualifier.datavalue;endendendreturn nil;endlocal 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' ) thenlocal qNotSoonerThan = getQualifierDataValue( statement, 'P1319' );local qNotLaterThan = getQualifierDataValue( statement, 'P1326' );if ( qNotSoonerThan or qNotLaterThan ) thenlocal results = {};if ( qNotSoonerThan ) thentable.insert( results, 'не&nbsp;ранее&nbsp;' .. formatDateImpl( qNotSoonerThan.value, {}, nil, nil ) );endif ( qNotLaterThan ) thentable.insert( results, 'не&nbsp;позднее&nbsp;' .. formatDateImpl( qNotLaterThan.value, {}, nil, nil ) );end    return mw.text.listToText( results, ' и ' , ' и ' ) .. categoryUnknownBirthDate;    endend    options['conjunction'] = '&#32;или&#32;';    options['value-module'] = 'Wikidata/date';    options['value-function'] = 'formatBirthDate';    options.i18n.somevalue = '\'\'неизвестно\'\'' .. categoryUnknownBirthDate;    options.i18n.circa = '<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 ) thenlocal age = ageCurrent( bTable )if ( age ) thenresult = result .. ' <span style="white-space:nowrap;">(' .. age .. ' ' .. mw.language.new( 'ru' ):plural( age, 'и', 'и', 'и') .. ')</span>'if ( not options.nocat ) thenif ( age > 115 ) thenresult = result .. categoryBigCurrentAge;elseresult = result .. categoryBiographiesOfLivingPersons;endendendendreturn result;endfunction p.formatDateOfDeathClaim( 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' ) thenlocal qNotSoonerThan = getQualifierDataValue( statement, 'P1319' );local qNotLaterThan = getQualifierDataValue( statement, 'P1326' );if ( qNotSoonerThan or qNotLaterThan ) thenlocal results = {};if ( qNotSoonerThan ) thentable.insert( results, 'не&nbsp;ранее&nbsp;' .. formatDateImpl( qNotSoonerThan.value, {}, nil, nil ) );endif ( qNotLaterThan ) thentable.insert( results, 'не&nbsp;позднее&nbsp;' .. formatDateImpl( qNotLaterThan.value, {}, nil, nil ) );end    return mw.text.listToText( results, ' и ' , ' и ' ) .. categoryUnknownDeathDate;    endend    options['conjunction'] = '&#32;или&#32;';    options['value-module'] = 'Wikidata/date';    options['value-function'] = 'formatDeathDate';    options.i18n.somevalue = '\'\'неизвестно\'\'' .. categoryUnknownDeathDate;    options.i18n.circa = '<span style="border-bottom: 1px dotted; cursor: help;" title="около">ок. </span>';local result = context.formatStatementDefault( context, options, statement );local bTable = parseProperty ( context, options, 'P569' )local dTable = { parseClaim( statement ) };if ( bTable and dTable ) thenlocal age = age( bTable, dTable )if ( age ) thenresult = result .. ' <span style="white-space:nowrap;">(' .. age .. ' ' .. mw.language.new( 'ru' ):plural( age, 'и', 'и', 'и') .. ')</span>'end-- returns category to recently deceased personslocal unixAvailable, unixDateOfDeath = pcall(function()local r = os.time(dTable[1].structure)if ( r ~= os.time() ) thenreturn renderror()end)if ( unixAvailable and os.time() - unixDateOfDeath < 31536000 and not options.nocat ) thenresult = result .. categoryRecentlyDeceasedendendreturn result;end-- Reentry point for Wikidata Snak formattingfunction 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 ) thenreturn formatDateImpl( value, options, 'bday', nil )elsereturn formatDateImpl( value, options, 'bday', 'шачшывлӓ' )endend-- Reentry point for Wikidata Snak formattingfunction 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 and options.nocat ~= '' ) thenreturn formatDateImpl( value, options, 'dday', nil )elsereturn formatDateImpl( value, options, 'dday', 'колышывлӓ' )endend-- Reentry point for Wikidata Snak formatting -- default onefunction 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 and options.nocat ~= '' ) thenreturn formatDateImpl( value, options, microformatClass, nil )elselocal categoryPrefix = options.categoryPrefix or nil;return formatDateImpl( value, options, microformatClass, categoryPrefix )endendfunction p.formatDateIntervalProperty( context, options )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 missing' ); end;-- Получение нужных утвержденийlocal WDS = require( 'Module:WikidataSelectors' );local fromProperty = options.propertyif options.from and options.from ~= '' thenfromProperty = options.fromendlocal fromClaims = WDS.filter( options.entity.claims, fromProperty );local toClaims = WDS.filter( options.entity.claims, options.to );    if fromClaims == nil and toClaims == nil then        return ''    end    local formattedFromClaims = {}    if fromClaims then    for i, claim in ipairs( fromClaims ) do        local formattedStatement = context.formatStatement( options, claim )        if formattedStatement then            formattedStatement = '<span class="wikidata-claim" data-wikidata-property-id="' .. string.upper( options.property ) .. '" data-wikidata-claim-id="' .. claim.id .. '">' .. formattedStatement .. '</span>'            table.insert( formattedFromClaims, formattedStatement )        end    end    end    local formattedToClaims = {}    local toOptions = deepcopy( options )    toOptions.property = options.to    toOptions.novalue = nowLabel    if toClaims then    for i, claim in ipairs( toClaims ) do        local formattedStatement = context.formatStatement( toOptions, claim )        if formattedStatement then            formattedStatement = '<span class="wikidata-claim" data-wikidata-property-id="' .. string.upper( toOptions.property ) .. '" data-wikidata-claim-id="' .. claim.id .. '">' .. formattedStatement .. '</span>'            table.insert( formattedToClaims, formattedStatement )        end    end    endlocal out = ''    local fromOut = mw.text.listToText( formattedFromClaims, options.separator, options.conjunction )    local toOut = mw.text.listToText( formattedToClaims, options.separator, options.conjunction )    if fromOut ~= '' or toOut ~= '' then    if fromOut ~= '' then    out = fromOutelseout = '?'endout = out .. ' — 'if toOut ~= '' thenout = out .. toOutelseout = out .. nowLabelendend    if out ~= '' then    if options.before then    out = options.before .. outend    if options.after then    out = out .. options.afterendend    return outendfunction 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 = tonumber( value.precision )if precision <= 6 thenreturn formatMillenium(structure, categoryPrefix)endif precision == 7 thenreturn formatCentury(structure, categoryPrefix)endif precision == 8 thenreturn formatDecade(structure, categoryPrefix)endif precision == 9 thenlocal tCopy = deepcopy( structure )tCopy.day = niltCopy.month = nilreturn moduleDates.formatWikiImpl(tCopy, tCopy, infoclass, categoryPrefix)end-- year and month onlyif precision == 10 thenlocal tCopy = deepcopy( structure )tCopy.day = nilreturn moduleDates.formatWikiImpl(tCopy, tCopy, infoclass, categoryPrefix)endlocal calendarmodel = 'gregorian';if (mw.ustring.find(value.calendarmodel, 'Q1985786', 1, true)) thencalendarmodel = 'julian';endif (calendarmodel == 'gregorian') then    return moduleDates.formatWikiImpl( structure, structure, microformatClass, categoryPrefix )    elsereturn p.formatAsJulian( unixtime, infoclass, categoryPrefix )endendfunction formatDecade( time, categoryNamePrefix )if ( time.year < 0 ) thenlocal year = math.floor( math.abs(time.year) / 10 ) * 10;if ( categoryNamePrefix ) thenreturn year .. '-е до н. э.[[Category:' .. categoryNamePrefix .. ' в ' .. year .. '-е годы до н. э.]]';elsereturn '' .. year .. ' до н. э.';endelselocal year = math.floor( time.year / 10 ) * 10;if ( categoryNamePrefix ) thenreturn year .. '-е[[Category:' .. categoryNamePrefix .. ' в ' .. year .. '-е годы]]';elsereturn '' .. year;endendendfunction formatCentury( time, categoryNamePrefix )local moduleRoman = require( "Module:RomanNumber" )if ( time.year < 0 ) thenlocal century = math.floor( (math.abs( time.year) - 1) / 100 ) + 1;if ( moduleRoman ) thencentury = moduleRoman.toRomanNumber( century );endif ( categoryNamePrefix ) thenreturn '[[' .. century .. ' век до н. э.]][[Category:' .. categoryNamePrefix .. ' в ' .. century .. ' веке до н. э.]]'elsereturn '[[' .. century .. ' век до н. э.]]'endelselocal century = math.floor( (time.year - 1) / 100 ) + 1;if ( moduleRoman ) thencentury = moduleRoman.toRomanNumber( century );endif ( categoryNamePrefix ) thenreturn '[[' .. century .. ' век]][[Category:' .. categoryNamePrefix .. ' в ' .. century .. ' веке]]'elsereturn '[[' .. century .. ' век]]'endendendfunction formatMillenium( time, categoryNamePrefix )local moduleRoman = require( "Module:RomanNumber" )if ( time.year < 0 ) thenlocal millenium = math.floor( (math.abs( time.year) - 1) / 1000 ) + 1;if ( categoryNamePrefix ) thenreturn '[[' .. millenium .. ' тысячелетие до н. э.]][[Category:' .. categoryNamePrefix .. ' в ' .. millenium .. ' тысячелетии до н. э.]]'elsereturn '[[' .. millenium .. ' тысячелетие до н. э.]]'endelselocal millenium = math.floor( (time.year - 1) / 1000 ) + 1;if ( moduleRoman ) thenmillenium = moduleRoman.toRomanNumber( millenium );endif ( categoryNamePrefix ) thenif ( millenium == 'II' ) thenreturn '[[' .. millenium .. ' тысячелетие]][[Category:' .. categoryNamePrefix .. ' во ' .. millenium .. ' тысячелетии]]'elsereturn '[[' .. millenium .. ' тысячелетие]][[Category:' .. categoryNamePrefix .. ' в ' .. millenium .. ' тысячелетии]]'endelsereturn '[[' .. millenium .. ' тысячелетие]]'endendendfunction 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) thenif str.args and str.args[1] thenstr = '' .. str.args[1]elsereturn 'unknown argument type: ' .. type( str ) .. ': ' .. table.tostring( str )endendlocal 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}))endlocal lowestBoundary = parseISO8601('-900-02-20T00:00:00Z')local mainBoundary = parseISO8601('1582-10-05T00:00:00Z')local lastBoundary = parseISO8601('1918-01-31T00:00:00Z');boundaries = {-- from (G) till next will be diff(G = J + diff), at current{ lowestBoundary,                       -9 },{ parseISO8601('-700-02-29T00:00:00Z'), -8 },{ parseISO8601('-600-02-29T00:00:00Z'), -7 },{ parseISO8601('-500-02-29T00:00:00Z'), -6 },{ parseISO8601('-300-02-29T00:00:00Z'), -5 },{ parseISO8601('-200-02-29T00:00:00Z'), -4 },{ parseISO8601('-100-02-29T00:00:00Z'), -3 },{ parseISO8601('0000-00-00T00:00:00Z'), -2 },{ parseISO8601('0100-02-29T00:00:00Z'), -1 },{ parseISO8601('0200-02-29T00:00:00Z'),  0 },{ parseISO8601('0300-02-29T00:00:00Z'),  1 },{ parseISO8601('0500-02-29T00:00:00Z'),  2 },{ parseISO8601('0600-02-29T00:00:00Z'),  3 },{ parseISO8601('0700-02-29T00:00:00Z'),  4 },{ parseISO8601('0900-02-29T00:00:00Z'),  5 },{ parseISO8601('1000-02-29T00:00:00Z'),  6 },{ parseISO8601('1100-02-29T00:00:00Z'),  7 },{ parseISO8601('1300-02-29T00:00:00Z'),  8 },{ parseISO8601('1400-02-29T00:00:00Z'),  9 },{ parseISO8601('1500-02-29T00:00:00Z'), 10 },{ parseISO8601('1700-02-29T00:00:00Z'), 11 },{ parseISO8601('1800-02-29T00:00:00Z'), 12 },{ parseISO8601('1900-02-29T00:00:00Z'), 13 },{ lastBoundary, '' },};-- Передаваемое время обязано быть по Юлианскому календарю (старому стилю)function p.formatAsJulian( julTime, infocardClass, categoryNamePrefix )if 'table' == type( julTime ) thenif julTime.args and julTime.args[1] thenjulTime = tonumber( julTime.args[1] )elsereturn 'unknown argument type: ' .. type( julTime ) .. ': ' .. table.tostring( julTime )endendlocal t = os.date( "*t", julTime )if ( julTime <= lowestBoundary ) thenreturn "''некорректная дата (недостижимая точность)''";endif ( julTime >= lastBoundary ) thenreturn "''некорректная дата (юлианский не используется после 1918-01-26)''";end    for i=1,#boundaries,1 dolocal b1 = boundaries[i][1];local b2 = boundaries[i + 1][1];if ( b1 <= julTime and julTime < b2 ) thenlocal b1s = os.date( "*t", b1 )if ( b1s.year == t.year and b1s.month == t.month and b1s.day == t.day) thenif ( julTime < mainBoundary ) then-- only julianreturn moduleDates.formatWikiImpl( {year=t.year, month=2, day=29}, {year=t.year, month=2, day=29}, infocardClass, categoryNamePrefix );else--julian and grigorianreturn moduleDates.formatWikiImpl( {year=t.year, month=2, day=29}, t, infocardClass, categoryNamePrefix )endelselocal gregTime = os.date( "*t", julTime + boundaries[i][2] * 24 * 60 * 60 );if ( julTime < mainBoundary ) then-- only julianreturn moduleDates.formatWikiImpl( t, t, infocardClass, categoryNamePrefix );else--julian and grigorianreturn moduleDates.formatWikiImpl( t, gregTime, infocardClass, categoryNamePrefix )endendendend    if ( julTime >= lastBoundary ) thenreturn "''ошибка в модуле Модуль:Wikidata/date (не найден сдвиг конвертации календаря)''";endendreturn p