Module:Wikidata date
From WWII Archives
Documentation for this module may be created at Module:Wikidata date/doc
--[[ __ __ _ _ __ ___ _ _ _ _ _ _ | \/ | ___ __| |_ _| | ___ \ \ / (_) | _(_) __| | __ _| |_ __ _ __| | __ _| |_ ___ | |\/| |/ _ \ / _` | | | | |/ _ (_) \ /\ / /| | |/ / |/ _` |/ _` | __/ _` | / _` |/ _` | __/ _ \ | | | | (_) | (_| | |_| | | __/_ \ V V / | | <| | (_| | (_| | || (_| | | (_| | (_| | || __/ |_| |_|\___/ \__,_|\__,_|_|\___(_) \_/\_/ |_|_|\_\_|\__,_|\__,_|\__\__,_| \__,_|\__,_|\__\___| This module displays content of wikidata "time" properties, with special emphasis on complex dates. Dates are localized using Module:Complex_date Please do not modify this code without applying the changes first at Module:Wikidata date/sandbox and testing at Module:Wikidata date/sandbox/testcases. Authors and maintainers: * User:Jarekt - original version ]] local cDate = require("Module:Complex date") -- used for internationalization of dates local ISOdate = require('Module:ISOdate')._ISOdate local date2jdn = require('Module:Calendar')._date2jdn -- ================================================== -- === local helper functions ======================= -- ================================================== local function processFrame(frame) -- inputs in any upper or lower case local args = {} for name, value in pairs( frame.args ) do if value ~= '' then -- nuke empty strings args[string.lower(name)] = value end end args.item = args.item or args.wikidata if not (args.lang and mw.language.isSupportedLanguage(args.lang)) then args.lang = frame:callParserFunction( "int", "lang" ) -- get user's chosen language end return args end local function formatDate(conj, date1, date2, certainty, lang) return cDate._complex_date_cer(conj, date1.adj, date1.date, date1.units, date1.era, date2.adj, date2.date, date2.units, date2.era, certainty, lang) end local function parse_item_snak(snak) if (snak.snaktype == "value") then return snak.datavalue.value.id end end local function parse_time_snak(snak) -- Converts a "time" snak into structure with obj.calendar, obj.date, obj.precision, and obj.era -- fields. Converts a "wikibase-item" snak into a string with q-code local obj = { date='', debug='' } if (snak.snaktype == "value" and snak.datavalue.type == 'time') then local units = {[6]='millennium', [7]='century', [8]='decade'} -- precision to units conversion local calendars = { Q1985727='gregorian', Q1985786='julian'} local v = snak.datavalue.value local calendar = calendars[string.gsub(v.calendarmodel, 'http://www.wikidata.org/entity/', '')] obj.units = units[v.precision] obj.debug = string.format(" (time=%s/%i, calendar=%s)", v.time, v.precision, calendar) -- string used for debuging obj.timestamp = v.time local year = tonumber(string.sub( v.time, 1, string.find( string.sub(v.time,2), '-') ) ) if year<0 then obj.era = 'BC' elseif year<100 then obj.era = 'AD' end if calendar == 'julian' and year>1583 and year<1923 then obj.calendar = 'julian' -- if julian calenar in a period of time usually associated with gregorian calendar end if v.precision >= 9 then -- assign year if precission higher than a decade obj.year = year; end local den = math.pow(10,9-v.precision) year = math.floor((math.abs(year)-1)/den)+1 if v.precision >= 11 then -- day obj.date = string.sub(v.time,2,11) -- date in YYYY-MM-DD format elseif v.precision == 10 then -- month obj.date = string.sub(v.time,2,8) -- date in YYYY-MM format elseif v.precision == 9 then -- year obj.date = string.sub(v.time,2,5) -- date in YYYY format elseif v.precision == 8 then -- decade obj.date = string.sub(v.time,2,4)..'0' -- date in YYY0 format elseif v.precision == 7 then -- century obj.date = tostring(year) elseif v.precision == 6 then -- millennium obj.date = tostring(year) elseif v.precision <= 5 then -- millions of years obj.date = tostring(year*den) end return obj end return nil end -- ================================================== -- === External functions =========================== -- ================================================== local p = {} -- =========================================================================== -- === Version of the function to be called from other LUA codes -- =========================================================================== function p._qualifierDate(snak, lang) local date1 = parse_time_snak(snak) local gregorian = 1 if date1.calendar=='julian' then gregorian = 0 end local jdn = date2jdn(date1.timestamp, gregorian) or 0 local dateStr if (date1.calendar or date1.era or date1.units ) then -- check the main statement dateStr = formatDate(date1.calendar, date1, { date='', debug='' }, '', lang) else dateStr = ISOdate(date1.date, lang) end return {str=dateStr, year=date1.year, jdn=jdn} end function p._date(item, prop, lang) -- Interpret date stored in "item"'s "prop" property and display it using [[Module:Complex date]] -- module using language "lang". local str, iso, year, year2return, iso2return, entity local dateTable = {} -- table to store QuickStatements -- Step 1: clean up the input parameters if type(item)=='string' then -- "item" is a q-code entity = mw.wikibase.getEntity(item); else entity = item -- "item" is the entity end lang = string.lower(lang) or 'en' -- lang comming from p.date(frame) will be clean, others might not be -- Step 2: parse all the statements in property "prop" and call Module:Complex_data if entity and entity.claims and entity.claims[prop] then -- if we have wikidata item and item has the property for _, statement in pairs( entity:getBestStatements( prop )) do -- harvest few date-type qualifiers local data = {} -- parse time datatype properties local qualifiers = {['from']='P580', ['until_']='P582', ['after']='P1319', ['before']='P1326'} for field,qual in pairs( qualifiers ) do if statement.qualifiers and statement.qualifiers[qual] then data[field] = parse_time_snak(statement.qualifiers[qual][1]) end end -- parse item datatype properties local qualifiers = {sourcing='P1480', refine='P4241', validity='P5102'} for field,qual in pairs( qualifiers ) do if statement.qualifiers and statement.qualifiers[qual] then -- only one P1480 qualifier per date so no "presumably circa" dates, etc. data[field] = parse_item_snak(statement.qualifiers[qual][1]) end end -- check on P4241 ("refine date") and P1480 ("sourcing circumstances") qualifiers local LUT = { Q40719727='early' , Q40719748='mid', Q40719766='late', Q40690303='1quarter' , Q40719649='2quarter' , Q40719662='3quarter', Q40719674='4quarter', Q40720559='spring' , Q40720564='summer' , Q40720568='autumn' , Q40720553='winter', Q40719687='firsthalf', Q40719707='secondhalf', Q5727902='circa', Q56644435='probably', Q18122778='presumably', Q30230067='possibly' } local adj = LUT[data.refine] -- check on P4241 ("refine date") item-type qualifier local certainty = LUT[data.sourcing] or LUT[data.validity] -- check on P1480 ("sourcing circumstances") item-type qualifier if data.sourcing and not certainty then certainty = 'uncertain' end -- initialize local nulDate = { date='', debug='' } -- nul parameter to pass to formatDate local dateStr = nil -- check 'P580' ("start time" aka "from" "since") and 'P582' ("end time" aka "until") qualifiers: if data.from and data.until_ then dateStr = formatDate('from-until', data.from, data.until_, certainty, lang) if data.from.year==data.until_.year then year = data.from.year end elseif data.from and not data.from.calendar then data.from.adj = adj dateStr = formatDate('from', data.from, nulDate, certainty, lang) elseif data.from then data.from.adj = 'from' dateStr = formatDate(data.from.calendar, data.from, nulDate, certainty, lang) elseif data.until_ and not data.until_.calendar then data.until_.adj = adj dateStr = formatDate('until', data.until_, nulDate, certainty, lang) elseif data.until_ then data.until_.adj = 'until' dateStr = formatDate(data.until_.calendar, data.until_, nulDate, certainty, lang) end -- check 'P1319' ("earliest date" aka "after this date") and 'P1326' ("latest date" aka "before this date") qualifiers: if data.after and data.before and certainty=='circa' then dateStr = formatDate('circa', data.after, data.before, '', lang) --module:Complex_date has custom 2-date "circa" option based on "between" option if data.after.year==data.before.year then year = data.before.year end elseif data.after and data.before then dateStr = formatDate('between', data.after, data.before, certainty, lang) if data.after.year==data.before.year then year = data.before.year end elseif data.after and data.after.calendar then data.after.adj = 'after' dateStr = formatDate(data.after.calendar, data.after, nulDate, certainty, lang) elseif data.after then data.after.adj = adj dateStr = formatDate('after', data.after, nulDate, certainty, lang) elseif data.before and data.before.calendar then data.before.adj = 'before' dateStr = formatDate(data.before.calendar, data.before, nulDate, certainty, lang) elseif data.before then data.before.adj = adj dateStr = formatDate('before', data.before, nulDate, certainty, lang) end -- if no above qualifiers than look at the main snack if not dateStr then data.main = parse_time_snak(statement.mainsnak) if data.main then year = data.main.year if (data.main.calendar or adj or data.main.era or data.main.units or certainty ) then -- check the main statement data.main.adj = adj dateStr = formatDate(data.main.calendar, data.main, nulDate, certainty, lang) else iso = data.main.date dateStr = ISOdate(iso, lang) end end end table.insert( dateTable, dateStr) if not year2return then year2return = year elseif year2return and year2return~=year then year2return = nil -- if years conflict than nul end if not iso2return then iso2return = iso elseif iso2return then iso2return = nil -- if date conflict than nul end end -- for loop end -- if entity then local dateStr = mw.text.trim(table.concat( dateTable, ' / ')) if dateStr=='' then dateStr=nil; end return {str=dateStr, year=year2return, iso=iso2return} end -- =========================================================================== -- === Functions to be called from template namespace -- =========================================================================== function p.date(frame) local args = processFrame(frame) local result = p._date(args.item, args.property, args.lang) return result.str or '' end function p.year(frame) -- return only year string local args = processFrame(frame) local result = p._date(args.item, args.property, args.lang) return tostring(result.year) or '' end function p.isoDate(frame) -- return only year string local args = processFrame(frame) local result = p._date(args.item, args.property, args.lang) return result.iso or 'nil' end function p.timestamp(frame) -- debuging function which might go away local entity = mw.wikibase.getEntity(frame.args.item); local dateTable = {} -- table to store QuickStatements if entity and entity.claims and entity.claims[frame.args.property] then -- if we have wikidata item and item has the property for _, statement in pairs( entity:getBestStatements( frame.args.property )) do local snak = statement.mainsnak if (snak.snaktype == "value" and snak.datavalue.type == 'time') then local v = snak.datavalue.value table.insert( dateTable, v.time ..'/' .. v.precision) end end -- for loop end -- if entity then return table.concat( dateTable, ' / ') or '' end return p