Module:Wikidata date
Jump to navigation
Jump to search
Lua
CodeDiscussionEditHistoryLinksLink count Subpages:DocumentationTestsResultsSandboxLive code All modules
Uses Lua: |
This Module handles pulling data type properties from Wikidata. It was mainly intended for calls from other Lua modules, but it can be also called directly from templates. The module can read and parse and display dates in all precision's (year, decade, century, millennium, ettc.) and calendars (Gregorian and Julian) used on Wikidata. It can also interpret all qualifiers used for wider range of dates. Dates are localized (displayed in the language of the user) using:
- Module:ISOdate (also used by {{ISOdate}})
- Module:Complex date (also used by {{Complex date}} and {{Other date}})
Properties
Module recognizes several qualifiers, by themselves and in groups:
- start time (P580) maps to {{Complex date}}'s from option
- end time (P582) maps to {{Complex date}}'s until option
- start time (P580) and end time (P582) maps to {{Complex date}}'s from-until option
- earliest date (P1319) maps to {{Complex date}}'s after option
- latest date (P1326) maps to {{Complex date}}'s before option
- earliest date (P1319) and latest date (P1326) maps to {{Complex date}}'s between option
- sourcing circumstances (P1480) with following items: circa (Q5727902), probably (Q56644435), presumably (Q18122778), possibly (Q30230067) maps to {{Complex date}}'s certainty parameter with corresponding value (introduced in September 2018; circa was possible as an adjective already before)
- refine date (P4241) with following items: beginning of (Q40719727), middle of (Q40719748), end of (Q40719766), first quarter (Q40690303), second quarter (Q40719649), third quarter (Q40719662), fourth quarter (Q40719674), spring (northern hemisphere) (Q40720559), summer (northern hemisphere) (Q40720564), autumn (northern hemisphere) (Q40720568), winter (northern hemisphere) (Q40720553), first half (Q40719687), second half (Q40719707), circa (Q5727902) maps to {{Complex date}} with corresponding option
Calling from templates
{{#invoke:Wikidata date|date|item=Q5600|property=P569|lang=en}}
- Inputs
- item - wikidata item ID
- property - property to capture
- lang - what language to display it in. If skipped than date will be shown in the language of the user.
Code
--[[
__ __ _ _ __ ___ _ _ _ _ _ _
| \/ | ___ __| |_ _| | ___ \ \ / (_) | _(_) __| | __ _| |_ __ _ __| | __ _| |_ ___
| |\/| |/ _ \ / _` | | | | |/ _ (_) \ /\ / /| | |/ / |/ _` |/ _` | __/ _` | / _` |/ _` | __/ _ \
| | | | (_) | (_| | |_| | | __/_ \ 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