Відмінності між версіями «Модуль:Wikidata/date»

Матеріал з Вікіпедії — вільної енциклопедії.
Перейти до навігації Перейти до пошуку
[неперевірена версія][перевірена версія]
(доробляйте)
м (Скасування редагування № 22062341 користувача Avatar6 (обговорення) воно російськомовне!)
Мітка: Скасування
Рядок 2: Рядок 2:
 
local categoryUnknownBirthDate = '[[Категорія:Персоналії без дати народження]]';
 
local categoryUnknownBirthDate = '[[Категорія:Персоналії без дати народження]]';
 
local categoryUnknownDeathDate = '[[Категорія:Персоналії без дати смерті]]';
 
local categoryUnknownDeathDate = '[[Категорія:Персоналії без дати смерті]]';
local categoryBigCurrentAge = '[[Категорія:Статті про персоналії з великим поточним віком]]';
+
local categoryBigCurrentAge = '[[Категорія:Статті про персоналії з великим поточним віком]]'
-- local categoryBigDeathAge = '[[Категорія:Статті про персоналії з великим віком на час смерті]]'; //deleted -d.bratchuk 05-07-2016
+
local categoryBigDeathAge = '[[Категорія:Статті про персоналії з великим віком на час смерті]]'
  +
local categoryBiographiesOfLivingPersons = '[[Категория:Википедия:Биографии современников]]'
 
local categoryRecentlyDeceased = '[[Категория:Википедия:Персоналии, умершие менее года назад]]'
 
local nowLabel = 'наст. время'
 
 
 
local moduleDates = require( "Module:Dates" )
 
local moduleDates = require( "Module:Dates" )
 
local moduleWikidata = require( "Module:Wikidata" )
 
local moduleWikidata = require( "Module:Wikidata" )
Рядок 28: Рядок 25:
 
-- accepts table of time+precision values
 
-- accepts table of time+precision values
 
function ageCurrent ( bTable )
 
function ageCurrent ( bTable )
local possibleAge = "NYA" -- it meansm "Not Yet Assigned", not what you imagined!
+
local possibleAge = "NYA" -- it meansm "Not Yet Assigned", not what you imagined!
  +
 
for bKey, bValue in pairs(bTable) do
+
for bKey, bValue in pairs(bTable) do
if ( bValue.unknown ) then
+
if ( bValue.unknown ) then
return nil
+
return nil
end
+
end
local bStructure = bValue.structure
+
local bStructure = bValue.structure
local bPrecision = bValue.precision
+
local bPrecision = bValue.precision
  +
 
local dStructure = os.date( "*t" )
+
local dStructure = os.date( "*t" )
  +
 
local calculatedAge = ageImpl ( bStructure, bPrecision, dStructure, 11 )
+
local calculatedAge = ageImpl ( bStructure, bPrecision, dStructure, 11 )
if ( possibleAge == "NYA" ) then
+
if ( possibleAge == "NYA" ) then
possibleAge = calculatedAge
+
possibleAge = calculatedAge
else
+
else
if ( possibleAge ~= calculatedAge ) then
+
if ( possibleAge ~= calculatedAge ) then
possibleAge = nil
+
possibleAge = nil
  +
end
end
 
end
+
end
end
+
end
  +
 
return possibleAge
+
return possibleAge
 
end
 
end
  +
 
 
-- accepts tables of time+precision values
 
-- accepts tables of time+precision values
 
function age ( bTable, dTable )
 
function age ( bTable, dTable )
local possibleAge = "NYA" -- it meansm "Not Yet Assigned", not what you imagined!
+
local possibleAge = "NYA" -- it meansm "Not Yet Assigned", not what you imagined!
  +
 
for bKey, bValue in pairs( bTable ) do
+
for bKey, bValue in pairs( bTable ) do
if ( bValue.unknown ) then
+
if ( bValue.unknown ) then
return nil
+
return nil
end
+
end
local bStructure = bValue.structure
+
local bStructure = bValue.structure
local bPrecision = bValue.precision
+
local bPrecision = bValue.precision
  +
 
for dKey, dValue in pairs( dTable ) do
+
for dKey, dValue in pairs( dTable ) do
if ( dValue.unknown ) then
+
if ( dValue.unknown ) then
return nil
+
return nil
  +
end
end
 
local dStructure = dValue.structure
+
local dStructure = dValue.structure
local dPrecision = dValue.precision
+
local dPrecision = dValue.precision
  +
 
local calculatedAge = ageImpl ( bStructure, bPrecision, dStructure, dPrecision )
+
local calculatedAge = ageImpl ( bStructure, bPrecision, dStructure, dPrecision )
if ( possibleAge == "NYA" ) then
+
if ( possibleAge == "NYA" ) then
possibleAge = calculatedAge
+
possibleAge = calculatedAge
else
+
else
if ( possibleAge ~= calculatedAge ) then
+
if ( possibleAge ~= calculatedAge ) then
possibleAge = nil
+
possibleAge = nil
  +
end
end
 
  +
end
end
 
end
+
end
end
+
end
  +
 
return possibleAge
+
return possibleAge
 
end
 
end
   
Рядок 160: Рядок 157:
 
end
 
end
   
-- проверка на совпадающие даты с разной моделью календаря
+
-- перевірка на співпадаючі дати з різною моделлю календаря
 
function checkDupDates( t )
 
function checkDupDates( t )
 
if #t > 1 then
 
if #t > 1 then
 
local removed = false;
 
local removed = false;
 
local j = 1;
 
local j = 1;
-- проверка на совпадающие даты с разной моделью календаря
+
-- перевірка на співпадаючі дати з різною моделлю календаря
 
while (j <= #t) do
 
while (j <= #t) do
 
local i = 1;
 
local i = 1;
Рядок 217: Рядок 214:
 
if ( not statement ) then error( 'statement not specified'); 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' );
+
local qNotSoonerThan = getQualifierDataValue( statement, 'P1319' );
  +
local qNotLaterThan = getQualifierDataValue( statement, 'P1326' );
if ( qNotSoonerThan or qNotLaterThan ) then
 
  +
if ( qNotSoonerThan or qNotLaterThan ) then
local results = {};
 
  +
local results = {};
if ( qNotSoonerThan ) then
 
  +
if ( qNotSoonerThan ) then
table.insert( results, 'не&nbsp;ранее&nbsp;' .. formatDateImpl( qNotSoonerThan.value, {}, nil, nil ) );
 
  +
table.insert( results, 'не&nbsp;раніше&nbsp;' .. formatDateImpl( qNotSoonerThan.value, {}, nil, nil ) );
end
 
  +
end
if ( qNotLaterThan ) then
 
  +
if ( qNotLaterThan ) then
table.insert( results, 'не&nbsp;позднее&nbsp;' .. formatDateImpl( qNotLaterThan.value, {}, nil, nil ) );
 
  +
table.insert( results, 'не&nbsp;пізніше&nbsp;' .. formatDateImpl( qNotLaterThan.value, {}, nil, nil ) );
end
 
  +
end
return mw.text.listToText( results, ' и ' , ' и ' ) .. categoryUnknownBirthDate;
 
  +
return mw.text.listToText( results, ' і ' , ' і ' );
  +
end
 
end
 
end
   
options['conjunction'] = '&#32;или&#32;';
+
options['conjunction'] = '&#32;або&#32;';
 
options['value-module'] = 'Wikidata/date';
 
options['value-module'] = 'Wikidata/date';
 
options['value-function'] = 'formatBirthDate';
 
options['value-function'] = 'formatBirthDate';
options.i18n.somevalue = '\'\'неизвестно\'\'' .. categoryUnknownBirthDate;
+
options.i18n.somevalue = '\'\'невідомо\'\'';
options.i18n.circa = '<span style="border-bottom: 1px dotted; cursor: help;" title="около">ок. </span>';
+
options.i18n.circa = '<span style="border-bottom: 1px dotted; cursor: help;" title="близько">бл. </span>';
 
local result = context.formatStatementDefault( context, options, statement );
 
local result = context.formatStatementDefault( context, options, statement );
   
Рядок 243: Рядок 242:
 
local age = ageCurrent( bTable )
 
local age = ageCurrent( bTable )
 
if ( age ) then
 
if ( age ) then
result = result .. ' <span style="white-space:nowrap;">(' .. age .. ' ' .. mw.language.new( 'ru' ):plural( age, 'год', 'года', 'лет') .. ')</span>'
+
result = result .. ' <span style="white-space:nowrap;">(' .. age .. ' ' .. mw.language.new( 'uk' ):plural( age, 'рік', 'роки', 'років') .. ')</span>'
 
if ( not options.nocat ) then
 
if ( not options.nocat ) then
if ( age > 115 ) then
+
if ( age > 150 ) then
 
result = result .. categoryBigCurrentAge;
 
result = result .. categoryBigCurrentAge;
else
 
result = result .. categoryBiographiesOfLivingPersons;
 
 
end
 
end
 
end
 
end
Рядок 263: Рядок 260:
 
if ( not statement ) then error( 'statement not specified'); 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' );
+
local qNotSoonerThan = getQualifierDataValue( statement, 'P1319' );
  +
local qNotLaterThan = getQualifierDataValue( statement, 'P1326' );
if ( qNotSoonerThan or qNotLaterThan ) then
 
  +
if ( qNotSoonerThan or qNotLaterThan ) then
local results = {};
 
  +
local results = {};
if ( qNotSoonerThan ) then
 
  +
if ( qNotSoonerThan ) then
table.insert( results, 'не&nbsp;ранее&nbsp;' .. formatDateImpl( qNotSoonerThan.value, {}, nil, nil ) );
 
  +
table.insert( results, 'не&nbsp;раніше&nbsp;' .. formatDateImpl( qNotSoonerThan.value, {}, nil, nil ) );
end
 
  +
end
if ( qNotLaterThan ) then
 
  +
if ( qNotLaterThan ) then
table.insert( results, 'не&nbsp;позднее&nbsp;' .. formatDateImpl( qNotLaterThan.value, {}, nil, nil ) );
 
  +
table.insert( results, 'не&nbsp;пізніше&nbsp;' .. formatDateImpl( qNotLaterThan.value, {}, nil, nil ) );
end
 
  +
end
return mw.text.listToText( results, ' и ' , ' и ' ) .. categoryUnknownDeathDate;
 
  +
return mw.text.listToText( results, ' і ' , ' і ' );
  +
end
 
end
 
end
   
options['conjunction'] = '&#32;или&#32;';
+
options['conjunction'] = '&#32;або&#32;';
 
options['value-module'] = 'Wikidata/date';
 
options['value-module'] = 'Wikidata/date';
 
options['value-function'] = 'formatDeathDate';
 
options['value-function'] = 'formatDeathDate';
options.i18n.somevalue = '\'\'неизвестно\'\'' .. categoryUnknownDeathDate;
+
options.i18n.somevalue = '\'\'невідомо\'\'';
options.i18n.circa = '<span style="border-bottom: 1px dotted; cursor: help;" title="около">ок. </span>';
+
options.i18n.circa = '<span style="border-bottom: 1px dotted; cursor: help;" title="близько">бл. </span>';
 
local result = context.formatStatementDefault( context, options, statement );
 
local result = context.formatStatementDefault( context, options, statement );
   
Рядок 289: Рядок 288:
 
local age = age( bTable, dTable )
 
local age = age( bTable, dTable )
 
if ( age ) then
 
if ( age ) then
result = result .. ' <span style="white-space:nowrap;">(' .. age .. ' ' .. mw.language.new( 'ru' ):plural( age, 'год', 'года', 'лет') .. ')</span>'
+
result = result .. ' <span style="white-space:nowrap;">(' .. age .. ' ' .. mw.language.new( 'uk' ):plural( age, 'рік', 'роки', 'років') .. ')</span>'
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 .. categoryRecentlyDeceased
 
 
end
 
end
 
end
 
end
Рядок 317: Рядок 304:
 
return formatDateImpl( value, options, 'bday', nil )
 
return formatDateImpl( value, options, 'bday', nil )
 
else
 
else
return formatDateImpl( value, options, 'bday', 'Родившиеся' )
+
return formatDateImpl( value, options, 'bday', 'Народились' )
 
end
 
end
 
end
 
end
Рядок 327: Рядок 314:
 
if ( not value ) then error( 'value not specified'); end;
 
if ( not value ) then error( 'value not specified'); end;
   
if ( options.nocat and options.nocat ~= '' ) then
+
if ( options.nocat ) then
 
return formatDateImpl( value, options, 'dday', nil )
 
return formatDateImpl( value, options, 'dday', nil )
 
else
 
else
return formatDateImpl( value, options, 'dday', 'Умершие' )
+
return formatDateImpl( value, options, 'dday', 'Померли' )
 
end
 
end
 
end
 
end
Рядок 341: Рядок 328:
   
 
local microformatClass = options.microfortmat or nil;
 
local microformatClass = options.microfortmat or nil;
if ( options.nocat and options.nocat ~= '' ) then
+
if ( options.nocat ) then
 
return formatDateImpl( value, options, microformatClass, nil )
 
return formatDateImpl( value, options, microformatClass, nil )
 
else
 
else
Рядок 347: Рядок 334:
 
return formatDateImpl( value, options, microformatClass, categoryPrefix )
 
return formatDateImpl( value, options, microformatClass, categoryPrefix )
 
end
 
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 );
 
 
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
 
out = out .. ' — '
 
if toOut ~= '' then
 
out = out .. toOut
 
else
 
out = out .. nowLabel
 
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
 
end
   
Рядок 429: Рядок 343:
 
local timeISO8601 = string.gsub( string.gsub( tostring( value.time ), '-00%-', '-01-' ), '-00T', '-01T' )
 
local timeISO8601 = string.gsub( string.gsub( tostring( value.time ), '-00%-', '-01-' ), '-00T', '-01T' )
 
local unixtime = parseISO8601( timeISO8601 )
 
local unixtime = parseISO8601( timeISO8601 )
 
if not unixtime then
 
return ''
 
end
 
   
 
local structure = os.date("*t", unixtime)
 
local structure = os.date("*t", unixtime)
 
local precision = tonumber( value.precision )
 
local precision = tonumber( value.precision )
 
 
if precision <= 6 then
+
if precision == 6 then
 
return formatMillenium(structure, categoryPrefix)
 
return formatMillenium(structure, categoryPrefix)
 
end
 
end
 
if precision == 7 then
 
if precision == 7 then
 
return formatCentury(structure, categoryPrefix)
 
return formatCentury(structure, categoryPrefix)
end
 
 
if precision == 8 then
 
return formatDecade(structure, categoryPrefix)
 
 
end
 
end
   
Рядок 471: Рядок 377:
 
else
 
else
 
return p.formatAsJulian( unixtime, infoclass, categoryPrefix )
 
return p.formatAsJulian( unixtime, infoclass, categoryPrefix )
end
 
end
 
 
function formatDecade( time, categoryNamePrefix )
 
if ( time.year < 0 ) then
 
local year = math.floor( math.abs(time.year) / 10 ) * 10;
 
if ( categoryNamePrefix ) then
 
return year .. '-е до н. э.[[Category:' .. categoryNamePrefix .. ' в ' .. year .. '-е годы до н. э.]]';
 
else
 
return '' .. year .. ' до н. э.';
 
end
 
else
 
local year = math.floor( time.year / 10 ) * 10;
 
if ( categoryNamePrefix ) then
 
return year .. '-е[[Category:' .. categoryNamePrefix .. ' в ' .. year .. '-е годы]]';
 
else
 
return '' .. year;
 
end
 
 
end
 
end
 
end
 
end
   
 
function formatCentury( time, categoryNamePrefix )
 
function formatCentury( time, categoryNamePrefix )
local moduleRoman = require( "Module:RomanNumber" )
 
 
if ( time.year < 0 ) then
 
if ( time.year < 0 ) then
 
local century = math.floor( (math.abs( time.year) - 1) / 100 ) + 1;
 
local century = math.floor( (math.abs( time.year) - 1) / 100 ) + 1;
if ( moduleRoman ) then
 
century = moduleRoman.toRomanNumber( century );
 
end
 
 
if ( categoryNamePrefix ) then
 
if ( categoryNamePrefix ) then
return '[[' .. century .. ' век до н. э.]][[Category:' .. categoryNamePrefix .. ' в ' .. century .. ' веке до н. э.]]'
+
return '[[' .. century .. ' століття до н. е.]][[Category:' .. categoryNamePrefix .. ' у ' .. century .. ' столітті до н. е.]]'
 
else
 
else
return '[[' .. century .. ' век до н. э.]]'
+
return '[[' .. century .. ' століття до н. е.]]'
 
end
 
end
 
else
 
else
 
local century = math.floor( (time.year - 1) / 100 ) + 1;
 
local century = math.floor( (time.year - 1) / 100 ) + 1;
if ( moduleRoman ) then
 
century = moduleRoman.toRomanNumber( century );
 
end
 
 
if ( categoryNamePrefix ) then
 
if ( categoryNamePrefix ) then
return '[[' .. century .. ' век]][[Category:' .. categoryNamePrefix .. ' в ' .. century .. ' веке]]'
+
return '[[' .. century .. ' століття]][[Category:' .. categoryNamePrefix .. ' у ' .. century .. ' столітті]]'
 
else
 
else
return '[[' .. century .. ' век]]'
+
return '[[' .. century .. ' століття]]'
 
end
 
end
 
end
 
end
Рядок 522: Рядок 403:
 
local millenium = math.floor( (math.abs( time.year) - 1) / 1000 ) + 1;
 
local millenium = math.floor( (math.abs( time.year) - 1) / 1000 ) + 1;
 
if ( categoryNamePrefix ) then
 
if ( categoryNamePrefix ) then
return '[[' .. millenium .. ' тысячелетие до н. э.]][[Category:' .. categoryNamePrefix .. ' в ' .. millenium .. ' тысячелетии до н. э.]]'
+
return '[[' .. millenium .. ' тисячолітті до н. е.]][[Category:' .. categoryNamePrefix .. ' у ' .. millenium .. ' тисячолітті до н. е.]]'
 
else
 
else
return '[[' .. millenium .. ' тысячелетие до н. э.]]'
+
return '[[' .. millenium .. ' тисячолітті до н. е.]]'
 
end
 
end
 
else
 
else
Рядок 530: Рядок 411:
 
if ( moduleRoman ) then
 
if ( moduleRoman ) then
 
millenium = moduleRoman.toRomanNumber( millenium );
 
millenium = moduleRoman.toRomanNumber( millenium );
end
 
if ( categoryNamePrefix ) then
 
if ( millenium == 'II' ) then
 
return '[[' .. millenium .. ' тысячелетие]][[Category:' .. categoryNamePrefix .. ' во ' .. millenium .. ' тысячелетии]]'
 
else
 
return '[[' .. millenium .. ' тысячелетие]][[Category:' .. categoryNamePrefix .. ' в ' .. millenium .. ' тысячелетии]]'
 
end
 
else
 
return '[[' .. millenium .. ' тысячелетие]]'
 
 
end
 
end
 
end
 
end
Рядок 567: Рядок 439:
 
 
 
function parseISO8601(str)
 
function parseISO8601(str)
if 'table' == type(str) then
+
if 'table'==type(str) then
 
if str.args and str.args[1] then
 
if str.args and str.args[1] then
 
str = '' .. str.args[1]
 
str = '' .. str.args[1]
Рядок 574: Рядок 446:
 
end
 
end
 
end
 
end
 
 
local Y,M,D = parseISO8601Date(str)
 
local Y,M,D = parseISO8601Date(str)
 
local h,m,s = parseISO8601Time(str)
 
local h,m,s = parseISO8601Time(str)
 
local oh,om = parseISO8601Offset(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}))
 
return tonumber(os.time({year=Y, month=M, day=D, hour=(h+oh), min=(m+om), sec=s}))
 
end
 
end
Рядок 618: Рядок 484:
 
};
 
};
   
  +
-- Переданий час має бути за Юліанським календарем (старим стилем)
-- Передаваемое время обязано быть по Юлианскому календарю (старому стилю)
 
 
function p.formatAsJulian( julTime, infocardClass, categoryNamePrefix )
 
function p.formatAsJulian( julTime, infocardClass, categoryNamePrefix )
 
if 'table' == type( julTime ) then
 
if 'table' == type( julTime ) then
Рядок 630: Рядок 496:
 
local t = os.date( "*t", julTime )
 
local t = os.date( "*t", julTime )
 
if ( julTime <= lowestBoundary ) then
 
if ( julTime <= lowestBoundary ) then
return "''некорректная дата (недостижимая точность)''";
+
return "''неправильна дата (досяжна точність)''";
 
end
 
end
 
if ( julTime >= lastBoundary ) then
 
if ( julTime >= lastBoundary ) then
return "''некорректная дата (юлианский не используется после 1918-01-26)''";
+
return "''неправильна дата (юліанський не використовується після 1918-01-26)''";
 
end
 
end
   

Версія за 19:16, 18 лютого 2018

{{i}} Документація модуля[створити]
--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

	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

-- 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, ' і ' , ' і ' );
    	end
	end

    options['conjunction'] = '&#32;або&#32;';
    options['value-module'] = 'Wikidata/date';
    options['value-function'] = 'formatBirthDate';
    options.i18n.somevalue = '\'\'невідомо\'\'';
    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 ) then
		local age = ageCurrent( bTable )
		if ( age ) then
			result = result .. ' <span style="white-space:nowrap;">(' .. age .. ' ' .. mw.language.new( 'uk' ):plural( age, 'рік', 'роки', 'років') .. ')</span>'
			if ( not options.nocat ) then
				if ( age > 150 ) then
					result = result .. categoryBigCurrentAge;
				end
			end
		end
	end

	return result;
end

function 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' ) 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, ' і ' , ' і ' );
    	end
	end

    options['conjunction'] = '&#32;або&#32;';
    options['value-module'] = 'Wikidata/date';
    options['value-function'] = 'formatDeathDate';
    options.i18n.somevalue = '\'\'невідомо\'\'';
    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 ) then
		local age = age( bTable, dTable )
		if ( age ) then
			result = result .. ' <span style="white-space:nowrap;">(' .. age .. ' ' .. mw.language.new( 'uk' ):plural( age, 'рік', 'роки', 'років') .. ')</span>'
		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;
	
	if ( options.nocat ) then
		return formatDateImpl( value, options, 'bday', nil )
	else
		return formatDateImpl( value, options, 'bday', 'Народились' )
	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, 'dday', nil )
	else
		return formatDateImpl( value, options, 'dday', 'Померли' )
	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 = tonumber( value.precision )
	
	if precision == 6 then
		return formatMillenium(structure, categoryPrefix)
	end	
	if precision == 7 then
		return formatCentury(structure, categoryPrefix)
	end

	if precision == 9 then
		local tCopy = deepcopy( structure )
		tCopy.day = nil
		tCopy.month = nil
		return moduleDates.formatWikiImpl(tCopy, tCopy, infoclass, categoryPrefix)
	end

	-- year and month only
	if precision == 10 then
		local tCopy = deepcopy( structure )
		tCopy.day = nil
		return moduleDates.formatWikiImpl(tCopy, tCopy, infoclass, 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, infoclass, categoryPrefix )
	end
end

function formatCentury( time, categoryNamePrefix )
	if ( time.year < 0 ) then
		local century = math.floor( (math.abs( time.year) - 1) / 100 ) + 1;
		if ( categoryNamePrefix ) then
			return '[[' .. century .. ' століття до н. е.]][[Category:' .. categoryNamePrefix .. ' у ' .. century .. ' столітті до н. е.‎]]'
		else
			return '[[' .. century .. ' століття до н. е.]]'
		end
	else
		local century = math.floor( (time.year - 1) / 100 ) + 1;
		if ( categoryNamePrefix ) then
			return '[[' .. century .. ' століття]][[Category:' .. categoryNamePrefix .. ' у ' .. century .. ' столітті]]'
		else
			return '[[' .. century .. ' століття]]'
		end
	end
end

function formatMillenium( time, categoryNamePrefix )
	local moduleRoman = require( "Module:RomanNumber" )
	if ( time.year < 0 ) then
		local millenium = math.floor( (math.abs( time.year) - 1) / 1000 ) + 1;
		if ( categoryNamePrefix ) then
			return '[[' .. millenium .. ' тисячолітті до н. е.]][[Category:' .. categoryNamePrefix .. ' у ' .. millenium .. ' тисячолітті до н. е.]]'
		else
			return '[[' .. millenium .. ' тисячолітті до н. е.]]'
		end
	else
		local millenium = math.floor( (time.year - 1) / 1000 ) + 1;
		if ( moduleRoman ) then
			millenium = moduleRoman.toRomanNumber( millenium );
		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-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 ) then
		if julTime.args and julTime.args[1] then
			julTime = tonumber( julTime.args[1] )
		else
			return 'unknown argument type: ' .. type( julTime ) .. ': ' .. table.tostring( julTime )
		end
	end

	local t = os.date( "*t", julTime )
	if ( julTime <= lowestBoundary ) then
		return "''неправильна дата (досяжна точність)''";
	end
	if ( julTime >= 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 <= julTime and julTime < 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 ( julTime < 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 gregTime = os.date( "*t", julTime + boundaries[i][2] * 24 * 60 * 60 );
				if ( julTime < mainBoundary ) then
					-- only julian
					return moduleDates.formatWikiImpl( t, t, infocardClass, categoryNamePrefix );
				else
					--julian and grigorian
					return moduleDates.formatWikiImpl( t, gregTime, infocardClass, categoryNamePrefix )
				end
			end
		end
	end

    if ( julTime >= lastBoundary ) then
		return "''помилка у модулі Модуль:Wikidata/date (не знайдений зсув конвертації календаря)''";
	end
end

return p