Module:Sandbox/RexxS/GetDateValue explained

-- vim: set noexpandtab ft=lua ts=4 sw=4:require('strict')

local p = local debug = false

-- module local variableslocal wiki =

-- internationalisationlocal i18n =

-- Credit to http://stackoverflow.com/a/1283608/2644759-- cc-by-sa 3.0local function tableMerge(t1, t2) for k,v in pairs(t2) do if type(v)

"table" then if type(t1[k] or false)

"table" then tableMerge(t1[k] or, t2[k] or) else t1[k] = v end else t1[k] = v end end return t1end

local function loadI18n local exist, res = pcall(require, "Module:Wikidata-i18n") if exist then tableMerge(i18n, res.i18n) endend

loadI18n

local function printError(code) return '

' .. (i18n.errors[code] or code) .. ''end

if debug then function p.inspectI18n(frame) local val = i18n for _, key in pairs(frame.args) do key = mw.text.trim(key) val = val[key] end return val endend

local function parseDateFull(timestamp, precision, date_format, date_addon) local prefix_addon = i18n["datetime"]["prefix-addon"] local addon_sep = i18n["datetime"]["addon-sep"] local addon = ""

-- check for negative date if string.sub(timestamp, 1, 1)

'-' then timestamp = '+' .. string.sub(timestamp, 2) addon = date_addon end -- get the next four characters after the + (should be the year now in all cases) -- ok, so this is dirty, but let's get it working first local intyear = tonumber(string.sub(timestamp, 2, 5)) if intyear

0 and precision <= 9 then return "" end -- precision is 10000 years or more if precision <= 5 then local factor = 10 ^ ((5 - precision) + 4) local y2 = math.ceil(math.abs(intyear) / factor) local relative = mw.ustring.gsub(i18n.datetime[precision], "$1", tostring(y2)) if addon ~= "" then -- negative date relative = mw.ustring.gsub(i18n.datetime.beforenow, "$1", relative) else relative = mw.ustring.gsub(i18n.datetime.afternow, "$1", relative) end return relative end

-- precision is decades, centuries and millennia local era if precision

6 then era = mw.ustring.gsub(i18n.datetime[6], "$1", tostring(math.floor((math.abs(intyear) - 1) / 1000) + 1)) end if precision

7 then era = mw.ustring.gsub(i18n.datetime[7], "$1", tostring(math.floor((math.abs(intyear) - 1) / 100) + 1)) end if precision

8 then era = mw.ustring.gsub(i18n.datetime[8], "$1", tostring(math.floor(math.abs(intyear) / 10) * 10)) end if era then if addon ~= "" then era = mw.ustring.gsub(mw.ustring.gsub(i18n.datetime.bc, '"', ""), "$1", era) else era = mw.ustring.gsub(mw.ustring.gsub(i18n.datetime.ad, '"', ""), "$1", era) end return era end local _date_format = i18n["datetime"]["format"][date_format] if _date_format ~= nil then -- check for precision is year and override supplied date_format if precision

9 then _date_format = i18n["datetime"][9] end local year_suffix local tstr = "" local lang_obj = mw.language.new(wiki.langcode) local f_parts = mw.text.split(_date_format, 'Y', true) for idx, f_part in pairs(f_parts) do year_suffix = if string.match(f_part, "x[mijkot]$") then -- for non-Gregorian year f_part = f_part .. 'Y' elseif idx < #f_parts then -- supress leading zeros in year year_suffix = lang_obj:formatDate('Y', timestamp) year_suffix = string.gsub(year_suffix, '^0+', , 1) end tstr = tstr .. lang_obj:formatDate(f_part, timestamp) .. year_suffix end local fdate if addon ~= "" and prefix_addon then fdate = addon .. addon_sep .. tstr elseif addon ~= "" then fdate = tstr .. addon_sep .. addon else fdate = tstr end return fdate else return printError("unknown-datetime-format") endend

-- the "qualifiers" and "snaks" field have a respective "qualifiers-order" and "snaks-order" field-- use these as the second parameter and this function instead of the built-in "pairs" function-- to iterate over all qualifiers and snaks in the intended order.local function orderedpairs(array, order) if not order then return pairs(array) end -- return iterator function local i = 0 return function i = i + 1 if order[i] then return order[i], array[order[i]] end end end

-- precision: 0 - billion years, 1 - hundred million years, ..., 6 - millennia, 7 - century, 8 - decade, 9 - year, 10 - month, 11 - day, 12 - hour, 13 - minute, 14 - secondlocal function normalizeDate(date) date = mw.text.trim(date, "+") -- extract year local yearstr = mw.ustring.match(date, "^\-?%d+") local year = tonumber(yearstr) -- remove leading zeros of year return year .. mw.ustring.sub(date, #yearstr + 1), yearend

local function formatDate(date, precision, timezone) precision = precision or 11 local date, year = normalizeDate(date) if year

0 and precision <= 9 then return "" end -- precision is 10000 years or more if precision <= 5 then local factor = 10 ^ ((5 - precision) + 4) local y2 = math.ceil(math.abs(year) / factor) local relative = mw.ustring.gsub(i18n.datetime[precision], "$1", tostring(y2)) if year < 0 then relative = mw.ustring.gsub(i18n.datetime.beforenow, "$1", relative) else relative = mw.ustring.gsub(i18n.datetime.afternow, "$1", relative) end return relative end -- precision is decades, centuries and millennia local era if precision

6 then era = mw.ustring.gsub(i18n.datetime[6], "$1", tostring(math.floor((math.abs(year) - 1) / 1000) + 1)) end if precision

7 then era = mw.ustring.gsub(i18n.datetime[7], "$1", tostring(math.floor((math.abs(year) - 1) / 100) + 1)) end if precision

8 then era = mw.ustring.gsub(i18n.datetime[8], "$1", tostring(math.floor(math.abs(year) / 10) * 10)) end if era then if year < 0 then era = mw.ustring.gsub(mw.ustring.gsub(i18n.datetime.bc, '"', ""), "$1", era) elseif year > 0 then era = mw.ustring.gsub(mw.ustring.gsub(i18n.datetime.ad, '"', ""), "$1", era) end return era end -- precision is year if precision

9 then return year end -- precision is less than years if precision > 9 then --the following code replaces the UTC suffix with the given negated timezone to convert the global time to the given local time timezone = tonumber(timezone) if timezone and timezone ~= 0 then timezone = -timezone timezone = string.format("%.2d%.2d", timezone / 60, timezone % 60) if timezone[1] ~= '-' then timezone = "+" .. timezone end date = mw.text.trim(date, "Z") .. " " .. timezone end -- local formatstr = i18n.datetime[precision] if year

0 then formatstr = mw.ustring.gsub(formatstr, i18n.datetime[9], "") elseif year < 0 then -- Mediawiki formatDate doesn't support negative years date = mw.ustring.sub(date, 2) formatstr = mw.ustring.gsub(formatstr, i18n.datetime[9], mw.ustring.gsub(i18n.datetime.bc, "$1", i18n.datetime[9])) elseif year > 0 and i18n.datetime.ad ~= "$1" then formatstr = mw.ustring.gsub(formatstr, i18n.datetime[9], mw.ustring.gsub(i18n.datetime.ad, "$1", i18n.datetime[9])) end return mw.language.new(wiki.langcode):formatDate(formatstr, date) endend

local function printDatavalueEntity(data, parameter) -- data fields: entity-type [string], numeric-id [int, Wikidata id] local id if data["entity-type"]

"item" then id = "Q" .. data["numeric-id"] elseif data["entity-type"]

"property" then id = "P" .. data["numeric-id"] else return printError("unknown-entity-type") end if parameter then if parameter

"link" then local linkTarget = mw.wikibase.sitelink(id) local linkName = mw.wikibase.label(id) if linkTarget then -- if there is a local Wikipedia article link to it using the label or the article title return "" .. (linkName or linkTarget) .. "" else -- if there is no local Wikipedia article output the label or link to the Wikidata object to let the user input a proper label if linkName then return linkName else return "" .. id .. "" end end else return data[parameter] end else return mw.wikibase.label(id) or id endend

local function printDatavalueTime(data, parameter) -- data fields: time [ISO 8601 time], timezone [int in minutes], before [int], after [int], precision [int], calendarmodel [wikidata URI] -- precision: 0 - billion years, 1 - hundred million years, ..., 6 - millennia, 7 - century, 8 - decade, 9 - year, 10 - month, 11 - day, 12 - hour, 13 - minute, 14 - second -- calendarmodel: e.g. http://www.wikidata.org/entity/Q1985727 for the proleptic Gregorian calendar or http://www.wikidata.org/wiki/Q11184 for the Julian calendar] if parameter then if parameter

"calendarmodel" then data.calendarmodel = mw.ustring.match(data.calendarmodel, "Q%d+") -- extract entity id from the calendar model URI elseif parameter

"time" then data.time = normalizeDate(data.time) end return data[parameter] else return formatDate(data.time, data.precision, data.timezone) endend

local function printDatavalueMonolingualText(data, parameter) -- data fields: language [string], text [string] if parameter then return data[parameter] else local result = mw.ustring.gsub(mw.ustring.gsub(i18n.monolingualtext, "%%language", data["language"]), "%%text", data["text"]) return result endend

local function findClaims(entity, property) if not property or not entity or not entity.claims then return end if mw.ustring.match(property, "^P%d+$") then -- if the property is given by an id (P..) access the claim list by this id return entity.claims[property] else property = mw.wikibase.resolvePropertyId(property) if not property then return end

return entity.claims[property] endend

local function getSnakValue(snak, parameter) if snak.snaktype

"value" then -- call the respective snak parser if snak.datavalue.type

"string" then return snak.datavalue.value elseif snak.datavalue.type

"globecoordinate" then return printDatavalueCoordinate(snak.datavalue.value, parameter) elseif snak.datavalue.type

"quantity" then return printDatavalueQuantity(snak.datavalue.value, parameter) elseif snak.datavalue.type

"time" then return printDatavalueTime(snak.datavalue.value, parameter) elseif snak.datavalue.type

"wikibase-entityid" then return printDatavalueEntity(snak.datavalue.value, parameter) elseif snak.datavalue.type

"monolingualtext" then return printDatavalueMonolingualText(snak.datavalue.value, parameter) end end return mw.wikibase.renderSnak(snak)end local function getQualifierSnak(claim, qualifierId) -- a "snak" is Wikidata terminology for a typed key/value pair -- a claim consists of a main snak holding the main information of this claim, -- as well as a list of attribute snaks and a list of references snaks if qualifierId then -- search the attribute snak with the given qualifier as key if claim.qualifiers then local qualifier = claim.qualifiers[qualifierId] if qualifier then return qualifier[1] end end return nil, printError("qualifier-not-found") else -- otherwise return the main snak return claim.mainsnak endend local function getValueOfClaim(claim, qualifierId, parameter) local error local snak snak, error = getQualifierSnak(claim, qualifierId) if snak then return getSnakValue(snak, parameter) else return nil, error endend

local function getReferences(frame, claim) local result = "" -- traverse through all references for ref in pairs(claim.references or) do local refparts -- traverse through all parts of the current reference for snakkey, snakval in orderedpairs(claim.references[ref].snaks or, claim.references[ref]["snaks-order"]) do if refparts then refparts = refparts .. ", " else refparts = "" end -- output the label of the property of the reference part, e.g. "imported from" for P143 refparts = refparts .. tostring(mw.wikibase.label(snakkey)) .. ": " -- output all values of this reference part, e.g. "German Wikipedia" and "English Wikipedia" if the referenced claim was imported from both sites for snakidx = 1, #snakval do if snakidx > 1 then refparts = refparts .. ", " end refparts = refparts .. getSnakValue(snakval[snakidx]) end end if refparts then result = result .. frame:extensionTag("ref", refparts) end end return resultend

function p.claim(frame) local property = frame.args[1] or "" local id = frame.args["id"] -- "id" must be nil, as access to other Wikidata objects is disabled in Mediawiki configuration local qualifierId = frame.args["qualifier"] local parameter = frame.args["parameter"] local list = frame.args["list"] local references = frame.args["references"] local showerrors = frame.args["showerrors"] local default = frame.args["default"] if default then showerrors = nil end -- get wikidata entity local entity = mw.wikibase.getEntityObject(id) if not entity then if showerrors then return printError("entity-not-found") else return default end end -- fetch the first claim of satisfying the given property local claims = findClaims(entity, property) if not claims or not claims[1] then if showerrors then return printError("property-not-found") else return default end end -- get initial sort indices local sortindices = for idx in pairs(claims) do sortindices[#sortindices + 1] = idx end -- sort by claim rank local comparator = function(a, b) local rankmap = local ranka = rankmap[claims[a].rank or "normal"] .. string.format("%08d", a) local rankb = rankmap[claims[b].rank or "normal"] .. string.format("%08d", b) return ranka < rankb end table.sort(sortindices, comparator) local result local error if list then local value -- iterate over all elements and return their value (if existing) result = for idx in pairs(claims) do local claim = claims[sortindices[idx]] value, error = getValueOfClaim(claim, qualifierId, parameter) if not value and showerrors then value = error end if value and references then value = value .. getReferences(frame, claim) end result[#result + 1] = value end result = table.concat(result, list) else -- return first element local claim = claims[sortindices[1]] result, error = getValueOfClaim(claim, qualifierId, parameter) if result and references then result = result .. getReferences(frame, claim) end end if result then return result else if showerrors then return error else return default end endend

-- look into entity objectfunction p.ViewSomething(frame) local f = (frame.args[1] or frame.args.id) and frame or frame:getParent local id = f.args.id if id and (#id

0) then id = nil end local data = mw.wikibase.getEntityObject(id) if not data then return nil end

local i = 1 while true do local index = f.args[i] if not index then if type(data)

"table" then return mw.text.jsonEncode(data, mw.text.JSON_PRESERVE_KEYS + mw.text.JSON_PRETTY) else return tostring(data) end end data = data[index] or data[tonumber(index)] if not data then return end i = i + 1 endend

-- getting sitelink of a given wikifunction p.getSiteLink(frame) local f = frame.args[1] local entity = mw.wikibase.getEntity if not entity then return end local link = entity:getSitelink(f) if not link then return end return linkend

function p.Dump(frame) local f = (frame.args[1] or frame.args.id) and frame or frame:getParent local data = mw.wikibase.getEntityObject(f.args.id) if not data then return i18n.warnDump end

local i = 1 while true do local index = f.args[i] if not index then return "

"..mw.dumpObject(data).."
".. i18n.warnDump end

data = data[index] or data[tonumber(index)] if not data then return i18n.warnDump end

i = i + 1 endend

-- This is used to get a date value for date_of_birth (P569), etc. which won't be linked-- Dates and times are stored in ISO 8601 format (sort of).-- At present the local formatDate(date, precision, timezone) function doesn't handle timezone-- So I'll just supply "Z" in the call to formatDate below:p.getDateValue = function(frame) local propertyID = mw.text.trim(frame.args[1] or "") local input_parm = mw.text.trim(frame.args[2] or "") local date_format = mw.text.trim(frame.args[3] or i18n["datetime"]["default-format"]) local date_addon = mw.text.trim(frame.args[4] or i18n["datetime"]["default-addon"]) if input_parm

"FETCH_WIKIDATA" then local entity = mw.wikibase.getEntityObject if entity.claims[propertyID] ~= nil then local out = for k, v in pairs(entity.claims[propertyID]) do if v.mainsnak.datavalue.type

'time' then local timestamp = v.mainsnak.datavalue.value.time local dateprecision = v.mainsnak.datavalue.value.precision out[#out + 1] = parseDateFull(timestamp, dateprecision, date_format, date_addon) end end return table.concat(out, ", ") else return "" end else return input_parm endend

return p