Модуль:Wikidata/date: различия между версиями
Материал из Occultica
ru>Putnik (из Викиданных дата приходит с «+» в начале строки) |
м (1 версия импортирована) |
(нет различий)
|
Текущая версия от 03:29, 15 ноября 2022
Для документации этого модуля может быть создана страница Модуль:Wikidata/date/doc
--settings local nowLabel = 'наст. время' local moduleDates = require( "Module:Dates" ) local moduleWikidata = require( "Module:Wikidata" ) local dateCat = require("Module:Infocards/dateCat") local 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 local function ageImpl ( bStructure, bPrecision, dStructure, dPrecision ) if ( not bStructure or not dStructure or bPrecision < 10 or dPrecision < 10 ) then return nil end local shift = 0 if ( bStructure.year < 0 and dStructure.year > 0 ) then shift = -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 end return nil end -- accepts table of time+precision values local function ageCurrent ( bTable ) local possibleAge = "NYA" -- it means "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 local function age ( bTable, dTable ) local possibleAge = "NYA" -- it means "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 if ( bValue.calendar == 'julian' and dValue.calendar == 'gregorian' ) then -- to calculate age, need to adjust bStructure to gregorian calendar local shift = math.floor(bStructure.year/100-2) - math.floor(bStructure.year/400) -- TODO: re-implement this properly bStructure.day = bStructure.day + shift end 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 local 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 local 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 local 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 local 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) if not Y or not M or not D or not h or not m or not s or not oh or not om then return nil end return tonumber(os.time({year=Y, month=M, day=D, hour=(h+oh), min=(m+om), sec=s})) end local function parseClaim ( claim ) if ( claim.mainsnak.snaktype == "value" ) then 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 calendarmodel = 'gregorian' if (mw.ustring.find(claim.mainsnak.datavalue.value.calendarmodel, 'Q1985786', 1, true)) then calendarmodel = 'julian' end local item = { structure=structure, precision=precision, calendar = calendarmodel } return item elseif ( claim.mainsnak.snaktype == "novalue" ) then -- novalue return { unknown="novalue" } else --unknown return { unknown="unknown" } end end -- returns table of time+precision values for specified property local 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 -- проверка на совпадающие даты с разной моделью календаря local 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 local function getQualifierWithDataValue( 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 end end end return nil end local p = {} local function formatDecade( time, categoryNamePrefix ) local bce = '' local year if time.year < 0 then bce = ' до н. э.' year = math.floor( math.abs( time.year ) / 10 ) * 10 else year = math.floor( time.year / 10 ) * 10 end local unit = '-е' if isGenitive then unit = '-х' end local value = '' .. year .. unit .. bce if categoryNamePrefix then return value .. '[[Category:' .. categoryNamePrefix .. ' в ' .. year .. '-е годы' .. bce .. ']]' end return value end local function formatCentury( time, categoryNamePrefix, isGenitive ) local moduleRoman = require( 'Module:RomanNumber' ) local bce = '' local century if time.year < 0 then bce = ' до н. э.' century = math.floor( ( math.abs( time.year ) - 1 ) / 100 ) + 1 else century = math.floor( ( time.year - 1 ) / 100 ) + 1 end local unit = 'век' if isGenitive then unit = 'века' end local infix = ' в ' if century == 2 then infix = ' во ' end if moduleRoman then century = moduleRoman.toRomanNumber( century ) end local value = '[[' .. century .. ' век' .. bce .. '|' .. century .. ' ' .. unit .. bce .. ']]' if categoryNamePrefix then return value .. '[[Category:' .. categoryNamePrefix .. infix .. century .. ' веке' .. bce .. ']]' end return value end local function formatMillenium( time, categoryNamePrefix, isGenitive ) local bce = '' local millenium if time.year < 0 then bce = ' до н. э.' millenium = math.floor( ( math.abs( time.year ) - 1 ) / 1000 ) + 1 else millenium = math.floor( ( time.year - 1 ) / 1000 ) + 1 end local unit = '-е тысячелетие' if isGenitive then unit = '-го тысячелетия' end local value = '[[' .. millenium .. '-е тысячелетие' .. bce .. '|' .. millenium .. unit .. bce .. ']]' if categoryNamePrefix then local infix = ' в ' if millenium == 2 then infix = ' во ' end return value .. '[[Category:' .. categoryNamePrefix .. infix .. millenium .. '-м тысячелетии' .. bce .. ']]' else return value end end local function formatDateImpl( value, options, microformatClass, categoryPrefix, leftBracket, rightBracket, nolinks, isGenitive ) 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 ) if not unixtime then return '' end local structure = os.date("*t", unixtime) local precision = tonumber( value.precision ) if precision <= 6 then return formatMillenium( structure, categoryPrefix, isGenitive ) end if precision == 7 then return formatCentury( structure, categoryPrefix, isGenitive ) end if precision == 8 then return formatDecade( structure, categoryPrefix, isGenitive ) end if precision == 9 then local tCopy = deepcopy( structure ) tCopy.day = nil tCopy.month = nil return moduleDates.formatWikiImpl( tCopy, tCopy, infoclass, categoryPrefix, leftBracket, rightBracket, nolinks ) end -- year and month only if precision == 10 then local tCopy = deepcopy( structure ) tCopy.day = nil return moduleDates.formatWikiImpl( tCopy, tCopy, infoclass, categoryPrefix, leftBracket, rightBracket, nolinks ) 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, leftBracket, rightBracket, nolinks ) else return p.formatAsJulian( timeISO8601, infoclass, categoryPrefix, leftBracket, rightBracket, nolinks ) end end local function formatApproximateDateClaim( context, options, statement, unknownDateCategory ) 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 options.nocat then unknownDateCategory = "" end local qNotSoonerThan = getQualifierWithDataValue( statement, 'P1319' ) local qNotLaterThan = getQualifierWithDataValue( statement, 'P1326' ) if ( qNotSoonerThan or qNotLaterThan ) then local results = {} if ( qNotSoonerThan ) then local formattedDate = formatDateImpl( qNotSoonerThan.datavalue.value, {}, nil, nil, options.leftBracket, options.rightBracket, options.nolinks, true ) local value = 'не ранее ' .. context.wrapSnak( formattedDate, qNotSoonerThan.hash ) table.insert( results, context.wrapQualifier( value, 'P1319' ) ) end if ( qNotLaterThan ) then local formattedDate = formatDateImpl( qNotLaterThan.datavalue.value, {}, nil, nil, options.leftBracket, options.rightBracket, options.nolinks, true ) local value = 'не позднее ' .. context.wrapSnak( formattedDate, qNotLaterThan.hash ) table.insert( results, context.wrapQualifier( value, 'P1326' ) ) end return mw.text.listToText( results, ' и ' , ' и ' ) .. unknownDateCategory .. context.formatRefs( options, statement ) end return nil end function p.formatDateOfBirthClaim( context, options, statement ) local value = formatApproximateDateClaim( context, options, statement, dateCat.categoryUnknownBirthDate ) if value then return value end options['conjunction'] = ' или ' options['value-module'] = 'Wikidata/date' options['value-function'] = 'formatBirthDate' options.i18n.somevalue = '\'\'неизвестно\'\'' .. dateCat.categoryUnknownBirthDate local circumstances = context.getSourcingCircumstances( statement ) for _, itemId in ipairs( circumstances ) do if itemId == 'Q5727902' then options.isGenitive = true break end end 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 if ( options.suppressAge == nil or options.suppressAge == '' ) then result = result .. ' <span style="white-space:nowrap;">(' .. age .. ' ' .. mw.language.new( 'ru' ):plural( age, 'год', 'года', 'лет') .. ')</span>' end if ( not options.nocat ) then if ( age > 115 ) then result = result .. dateCat.categoryBigCurrentAge elseif (age >= 0) then result = result .. dateCat.categoryBiographiesOfLivingPersons else result = result .. dateCat.categoryNegativeAge end end end end return result end function p.formatDateOfDeathClaim( context, options, statement ) local value = formatApproximateDateClaim( context, options, statement, dateCat.categoryUnknownDeathDate ) if value then return value end options['conjunction'] = ' или ' options['value-module'] = 'Wikidata/date' options['value-function'] = 'formatDeathDate' options.i18n.somevalue = '\'\'неизвестно\'\'' .. dateCat.categoryUnknownDeathDate local circumstances = context.getSourcingCircumstances( statement ) for _, itemId in ipairs( circumstances ) do if itemId == 'Q5727902' then options.isGenitive = true break end end local result = context.formatStatementDefault( 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 if ( options.suppressAge == nil or options.suppressAge == '' ) then result = result .. ' <span style="white-space:nowrap;">(' .. age .. ' ' .. mw.language.new( 'ru' ):plural( age, 'год', 'года', 'лет') .. ')</span>' end if ( not options.nocat and age < 0) then result = result .. dateCat.categoryNegativeAge end end -- returns category to recently deceased persons local unixAvailable, unixDateOfDeath = pcall(function() local r = os.time(dTable[1].structure) if ( r ~= os.time() ) then return r end error() end) if ( unixAvailable and os.time() - unixDateOfDeath < 31536000 and not options.nocat ) then result = result .. dateCat.categoryRecentlyDeceased end end return result 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 local microformatClass = nil if options.microformat ~= '-' then microformatClass = options.microformat or 'bday' end if ( options.nocat ) then return formatDateImpl( value, options, microformatClass, nil, options.leftBracket, options.rightBracket, options.nolinks, options.isGenitive ) else return formatDateImpl( value, options, microformatClass, 'Родившиеся', options.leftBracket, options.rightBracket, options.nolinks, options.isGenitive ) 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 local microformatClass = nil if options.microformat ~= '-' then microformatClass = options.microformat or 'dday' end if ( options.nocat and options.nocat ~= '' ) then return formatDateImpl( value, options, microformatClass, nil, options.leftBracket, options.rightBracket, options.nolinks, options.isGenitive ) else return formatDateImpl( value, options, microformatClass, 'Умершие', options.leftBracket, options.rightBracket, options.nolinks, options.isGenitive ) 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.microformat or nil if ( options.nocat and options.nocat ~= '' ) then return formatDateImpl( value, options, microformatClass, nil, options.leftBracket, options.rightBracket, options.nolinks, options.isGenitive ) else local categoryPrefix = options.categoryPrefix or nil return formatDateImpl( value, options, microformatClass, categoryPrefix, options.leftBracket, options.rightBracket, options.nolinks, options.isGenitive ) end end function 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.property if options.from and options.from ~= '' then fromProperty = options.from end local fromClaims = WDS.filter( options.entity.claims, fromProperty ) local toClaims = WDS.filter( options.entity.claims, options.to ) local withinClaims = WDS.filter( options.entity.claims, options.within ) 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 end local 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 = fromOut else out = '?' end if toOut ~= '' then out = out .. ' — ' .. toOut else if withinClaims ~= nil then out = 'с ' .. out else out = out .. ' — ' .. nowLabel end end end if out ~= '' then if options.before then out = options.before .. out end if options.after then out = out .. options.after end end return out end local lowestBoundary = '1582-10-05T00:00:00Z' local lastBoundary = '1918-01-31T00:00:00Z' local boundaries = { -- from (G) till next will be diff(G = J + diff), at current { lowestBoundary, 10 }, { '1700-02-29T00:00:00Z', 11 }, { '1800-02-29T00:00:00Z', 12 }, { '1900-02-29T00:00:00Z', 13 }, { lastBoundary, '' }, } -- Передаваемое время обязано быть по Юлианскому календарю (старому стилю) function p.formatAsJulian( julTimeISO8601, infocardClass, categoryNamePrefix, leftBracket, rightBracket, nolinks ) if 'table' == type( julTimeISO8601 ) then if julTimeISO8601.args and julTimeISO8601.args[1] then julTimeISO8601 = julTimeISO8601.args[1] else return 'unknown argument type: ' .. type( julTime ) .. ': ' .. table.tostring( julTime ) end end julTimeISO8601 = mw.text.trim( julTimeISO8601 ) julTimeISO8601 = string.gsub( julTimeISO8601, '^+', '' ) local julTime = parseISO8601( julTimeISO8601 ) local t = os.date( "*t", julTime ) if ( julTime < parseISO8601( lowestBoundary ) ) then -- only julian if string.find( julTimeISO8601, '-02-29T', 1, true ) then t = { year = t.year, month = 2, day = 29 } end return moduleDates.formatWikiImpl( t, t, infocardClass, categoryNamePrefix, leftBracket, rightBracket, nolinks ) end if ( julTimeISO8601 >= lastBoundary ) then return "''некорректная дата (юлианский календарь не используется после 1918-01-26)''" end -- julian and grigorian for i = 1, #boundaries, 1 do local b1 = boundaries[i][1] local b2 = boundaries[i + 1][1] if ( b1 <= julTimeISO8601 and julTimeISO8601 < b2 ) then local diff = boundaries[i][2] if string.sub( julTimeISO8601, 1, 10 ) == string.sub( boundaries[i][1], 1, 10 ) then t = { year = t.year, month = 2, day = 29 } diff = diff - 1 end local gregTime = os.date( "*t", julTime + diff * 24 * 60 * 60 ) return moduleDates.formatWikiImpl( t, gregTime, infocardClass, categoryNamePrefix, leftBracket, rightBracket, nolinks ) end end return "''ошибка в модуле Модуль:Wikidata/date''" end return p