Module:Wikidata date
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://data.wikishia.net/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