Module:Sandbox/Thayts/Wd Explained

-- Original module located at and .

local p = local arg = ...local i18n

--

-- Public declarations and initializations --

--

p.claimCommands =

p.generalCommands =

p.flags =

p.args =

--

-- Public constants --

--

-- An Ogham space that, just like a normal space, is not accepted by Wikidata as a valid single-character string value,-- but which does not get trimmed as leading/trailing whitespace when passed in an invocation's named argument value.-- This allows it to be used as a special character representing the special value 'somevalue' unambiguously.-- Another advantage of this character is that it is usually visible as a dash instead of whitespace.p.SOMEVALUE = " "p.JULIAN = "Julian"

--

-- Private constants --

--

local NB_SPACE = " "local ENC_PIPE = "|"local SLASH = "/"local LAT_DIR_N_EN = "N"local LAT_DIR_S_EN = "S"local LON_DIR_E_EN = "E"local LON_DIR_W_EN = "W"local PROP = "prop"local RANK = "rank"local CLAIM = "_claim"local REFERENCE = "_reference"local UNIT = "_unit"local UNKNOWN = "_unknown"

--

-- Private declarations and initializations --

--

local aliasesP =

local aliasesQ =

local parameters =

local formats =

local hookNames =

local defaultSeparators =

local rankTable =

--

-- Private functions --

--

-- used to merge output arrays together;-- note that it currently mutates the first input arraylocal function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end

return a1end

-- used to make frame.args mutable, to replace #frame.args (which is always 0)-- with the actual amount and to simply copy tables;-- does a shallow copy, so nested tables are not copied but linkedlocal function copyTable(tIn) if not tIn then return nil end

local tOut =

for i, v in pairs(tIn) do tOut[i] = v end

return tOutend

-- implementation of pairs that skips numeric keyslocal function npairs(t) return function(t, k) local v

repeat k, v = next(t, k) until k

nil or type(k) ~= 'number'

return k, v end, t, nilend

local function toString(object, insideRef, refs) local mt, value

insideRef = insideRef or false refs = refs or

if not object then refs.squashed = false return "" end

mt = getmetatable(object)

if mt.sep then local array =

for _, obj in ipairs(object) do local ref = refs[1]

if not insideRef and array[1] and mt.sep[1] ~= "" then refs[1] = end

value = toString(obj, insideRef, refs)

if value ~= "" or (refs.squashed and not array[1]) then array[#array + 1] = value else refs[1] = ref end end

value = table.concat(array, mt.sep[1]) else if mt.hash then if refs[1][mt.hash] then refs.squashed = true return "" end

insideRef = true end

if mt.format then local ref, squashed, array

local function processFormat(format) local array = local params =

-- see if there are required parameters to expand if format.req then

-- before expanding any parameters, check that none of them is nil for i, _ in pairs(format.req) do if not object[i] then return array -- empty end end end

-- process the format and childs (+1 is needed to process trailing childs) for i = 1, #format + 1 do if format.childs and format.childs[i] then for _, child in ipairs(format.childs[i]) do local ref = copyTable(refs[1]) local squashed = refs.squashed

local childArray = processFormat(child)

if not childArray[1] then refs[1] = ref refs.squashed = squashed else mergeArrays(array, childArray) end end end

if format.params and format.params[i] then array[#array + 1] = toString(object[format[i]], insideRef, refs)

if array[#array]

"" and not refs.squashed then return end elseif format[i] then array[#array + 1] = format[i]

if not insideRef then refs[1] = end end end

return array end

ref = copyTable(refs[1]) squashed = refs.squashed

array = processFormat(mt.format)

if not array[1] then refs[1] = ref refs.squashed = squashed end

value = table.concat(array) else if mt.expand then local args =

for i, j in npairs(object) do args[i] = toString(j, insideRef) end

value = mw.getCurrentFrame:expandTemplate elseif object.label then value = object.label else value = table.concat(object) end

if not insideRef and not mt.hash and value ~= "" then refs[1] = end end

if mt.sub then for i, j in pairs(mt.sub) do value = mw.ustring.gsub(value, i, j) end end

if value ~= "" and mt.tag then value = mw.getCurrentFrame:extensionTag(mt.tag[1], value, mt.tag[2])

if mt.hash then refs[1][mt.hash] = true end end

refs.squashed = false end

if mt.trail then value = value .. mt.trail

if not insideRef then refs[1] = refs.squashed = false end end

return valueend

local function loadI18n(aliasesP, frame) local title

if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle else -- current module included by other module, get its title from ... title = arg end

if not i18n then i18n = require(title .. "/i18n").init(aliasesP) endend

local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end

return idend

local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return textend

local function throwError(errorMessage, param) error(errorText(errorMessage, param))end

local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1)end

local function padZeros(num, numDigits) local numZeros local negative = false

if num < 0 then negative = true num = num * -1 end

num = tostring(num) numZeros = numDigits - num:len

for _ = 1, numZeros do num = "0"..num end

if negative then num = "-"..num end

return numend

local function replaceSpecialChar(chr) if chr

'_' then -- replace underscores with spaces return ' ' else return chr endend

local function replaceSpecialChars(str) local chr local esc = false local strOut = ""

for i = 1, #str do chr = str:sub(i,i)

if not esc then if chr

'\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end

return strOutend

local function isPropertyID(id) return id:match('^P%d+$')end

local function buildLink(target, label) local mt =

if not label then mt.format = return setmetatable(mt), mt else mt.format = return setmetatable(mt), mt endend

local function buildWikilink(target, label) local mt =

if not label or target

label then mt.format = return setmetatable(mt), mt else mt.format = return setmetatable(mt), mt endend

-- does a shallow copy of both the object and the metatable's format,-- so nested tables are not copied but linkedlocal function copyValue(vIn) local vOut = copyTable(vIn) local mtIn = getmetatable(vIn) local mtOut = return setmetatable(vOut, mtOut)end

local function split(str, del, from) local i, j

from = from or 1 i, j = str:find(del, from)

if i and j then return str:sub(1, i - 1), str:sub(j + 1), i, j end

return strend

local function urlEncode(url) local i, j, urlSplit, urlPath local urlPre = "" local count = 0 local pathEnc = local delim = ""

i, j = url:find("//", 1, true)

-- check if a hostname is present if i

1 or (i and url:sub(i - 1, i - 1)

':') then urlSplit = urlPre = urlSplit[1]

-- split the path from the hostname if urlSplit[2] then urlPath = url:sub(urlSplit[3], urlSplit[4]) .. urlSplit[2] else urlPath = "" end else urlPath = url -- no hostname is present, so it's a path end

-- encode each part of the path for part in mw.text.gsplit(urlPath, "[;/?:@&=+$,#]") do pathEnc[#pathEnc + 1] = delim pathEnc[#pathEnc + 1] = mw.uri.encode(mw.uri.decode(part, "PATH"), "PATH") count = count + #part + 1 delim = urlPath:sub(count, count) end

-- return the properly encoded URL return urlPre .. table.concat(pathEnc)end

local function parseWikidataURL(url) local id

if url:match('^http[s]?://') then id = [2]

if id then return "Q" .. id end end

return nilend

local function parseDate(dateStr, precision) precision = precision or "d"

local i, j, index, ptr local parts =

if dateStr

nil then return parts[1], parts[2], parts[3] -- year, month, day end

-- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]")

if i then dateStr = dateStr:sub(1, i-1) end

local from = 1

if dateStr:sub(1,1)

"-" then -- this is a negative number, look further ahead from = 2 end

index = 1 ptr = 1

i, j = dateStr:find("-", from)

if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error)

if parts[index]

-0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end

if precision

"y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end

index = index + 1 ptr = i + 1

i, j = dateStr:find("-", ptr)

if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10)

if precision

"m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end

index = index + 1 ptr = i + 1 end end

if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end

return parts[1], parts[2], parts[3] -- year, month, dayend

local function datePrecedesDate(dateA, dateB) if not dateA[1] or not dateB[1] then return nil end

dateA[2] = dateA[2] or 1 dateA[3] = dateA[3] or 1 dateB[2] = dateB[2] or 1 dateB[3] = dateB[3] or 1

if dateA[1] < dateB[1] then return true end

if dateA[1] > dateB[1] then return false end

if dateA[2] < dateB[2] then return true end

if dateA[2] > dateB[2] then return false end

if dateA[3] < dateB[3] then return true end

return falseend

local function newOptionalHook(hooks) return function(state, claim) state:callHooks(hooks, claim)

return true endend

local function newPersistHook(params) return function(state, claim) local param0

if not state.resultsByStatement[claim][1] then local mt = copyTable(state.metaTable) mt.rank = claim.rank state.resultsByStatement[claim][1] = setmetatable(mt)

local rankPos = (rankTable[claim.rank] or)[1]

if rankPos and rankPos < state.conf.foundRank then state.conf.foundRank = rankPos end end

for param, _ in pairs(params) do if not state.resultsByStatement[claim][1][param] then state.resultsByStatement[claim][1][param] = state.resultsByStatement[claim][param] -- persist result

-- if we need to persist "q", then also persist "q1", "q2", etc. if param

parameters.qualifier then for i = 1, state.conf.qualifiersCount do param0 = param..i

if state.resultsByStatement[claim][param0][1] then state.resultsByStatement[claim][1][param0] = state.resultsByStatement[claim][param0] end end end end end

return true endend

local function parseFormat(state, formatStr, i) local iNext, childHooks, param0 local esc = false local param = 0 local str = "" local hooks = local optionalHooks = local parsedFormat = local params = local childs = local req =

i = i or 1

local function flush if str ~= "" then parsedFormat[#parsedFormat + 1] = str

if param > 0 then req[str] = true params[#parsedFormat] = true

if not state.hooksByParam[str] then if state.conf.statesByParam[str] or str

parameters.separator then state:newValueHook(str) elseif str

parameters.qualifier and state.conf.statesByParam[str.."1"] then state:newValueHook(str)

for i = 1, state.conf.qualifiersCount do param0 = str..i

if not state.hooksByParam[param0] then state:newValueHook(param0) end end end end

hooks[#hooks + 1] = state.hooksByParam[str] end

str = "" end

param = 0 end

while i <= #formatStr do chr = formatStr:sub(i,i)

if not esc then if chr

'\\' then if param > 0 then flush end

esc = true elseif chr

'%' then flush param = 2 elseif chr

'[' then flush iNext = #parsedFormat + 1 if not childs[iNext] then childs[iNext] = end

childs[iNext][#childs[iNext] + 1], childHooks, i = parseFormat(state, formatStr, i + 1)

if childHooks[1] then optionalHooks[#optionalHooks + 1] = newOptionalHook(childHooks) end elseif chr

']' then break else if param > 1 then param = param - 1 elseif param

1 and not chr:match('%d') then flush end

str = str .. replaceSpecialChar(chr) end else str = str .. chr esc = false end

i = i + 1 end

flush

if hooks[1] then hooks[#hooks + 1] = newPersistHook(req) end

mergeArrays(hooks, optionalHooks)

parsedFormat.params = params parsedFormat.childs = childs parsedFormat.req = req

return parsedFormat, hooks, iend

-- this function must stay in sync with the getValue functionlocal function parseValue(value, datatype) if datatype

'quantity' then return elseif datatype

'time' then local tail local dateValue =

dateValue.len = 4 -- length used for comparing

value, tail = split(value, SLASH)

if tail and tail:lower

p.JULIAN:lower then dateValue[4] = p.JULIAN end

if value:sub(1,1)

"-" then dateValue[1], value = split(value, "-", 2) else dateValue[1], value = split(value, "-") end

dateValue[1] = tonumber(dateValue[1])

if value then dateValue[2], value = split(value, "-") dateValue[2] = tonumber(dateValue[2])

if value then dateValue[3] = tonumber(value) end end

return dateValue elseif datatype

'globecoordinate' then local part, partsIndex local coordValue =

coordValue.len = 6 -- length used for comparing

for i = 1, 4 do part, value = split(value, SLASH) coordValue[i] = tonumber(part)

if not coordValue[i] or not value or i

4 then coordValue[i] = nil partsIndex = i - 1 break end end

if part:upper

LAT_DIR_S_EN then for i = 1, partsIndex do coordValue[i] = -coordValue[i] end end

if value then partsIndex = partsIndex + 3

for i = 4, partsIndex do part, value = split(value, SLASH) coordValue[i] = tonumber(part)

if not coordValue[i] or not value then partsIndex = i - 1 break end end

if value and value:upper

LON_DIR_W_EN then for i = 4, partsIndex do coordValue[i] = -coordValue[i] end end end

return coordValue elseif datatype

'wikibase-entityid' then return end

return end

local function getEntityId(arg, eid, page, allowOmitPropPrefix) local id = nil local prop = nil

if arg then if arg:sub(1,1)

":" then page = arg eid = nil elseif arg:sub(1,1):upper

"Q" or arg:sub(1,9):lower

"property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end

if eid then if eid:sub(1,9):lower

"property:" then id = replaceAlias(mw.text.trim(eid:sub(10)))

if id:sub(1,1):upper ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1)

":" then page = mw.text.trim(page:sub(2)) end

id = mw.wikibase.getEntityIdForTitle(page) or "" end

if not id then id = mw.wikibase.getEntityIdForCurrentPage or "" end

id = id:upper

if not mw.wikibase.isValidEntityId(id) then id = "" end

return id, propend

local function nextArg(args) local arg = args[args.pointer]

if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil endend

--

-- Classes --

--

local Config =

-- allows for recursive callsfunction Config:new local cfg = setmetatable(self) self.__index = self

cfg.separators =

cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = cfg.qualifierIDsAndValues = cfg.qualifiersCount = 0

cfg.bestRank = true cfg.ranks = -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.filterBeforeRank = false

cfg.periods = -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = -- today as cfg.curTime = os.time

cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false

cfg.inSitelinks = false

cfg.emptyAllowed = false

cfg.langCode = mw.language.getContentLanguage.code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode)

cfg.siteID = mw.wikibase.getGlobalSiteId

cfg.movSeparator = cfg.separators["sep%s"] cfg.puncMark = cfg.separators["punc"]

cfg.statesByParam = cfg.statesByID = cfg.curState = nil

cfg.sortKeys =

return cfgend

local State =

function State:new(cfg, level, param, id) local stt = setmetatable(self) self.__index = self

stt.conf = cfg stt.level = level stt.param = param

stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.freeUnit = false stt.freeNumber = false stt.maxResults = 0 -- 0 means unlimited

stt.metaTable = nil stt.results = stt.resultsByStatement = stt.references = stt.hooksByParam = stt.hooksByID = stt.valHooksByIdOrParam = stt.valHooks = stt.sortable = stt.sortPaths = stt.propState = nil

if level and level > 1 then stt.hooks = stt.separator = cfg.separators["sep%"..param] or cfg.separators["sep"] -- fall back to "sep" for getAlias and getBadge stt.resultsDatatype = nil else stt.hooks = stt.separator = cfg.separators["sep"] stt.resultsDatatype = end

if id then cfg:addToStatesByID(stt, id) elseif param then cfg.statesByParam[param] = stt end

return sttend

function Config:addToStatesByID(state, id) if not self.statesByID[id] then self.statesByID[id] = end

self.statesByID[id][#self.statesByID[id] + 1] = stateend

-- if id

nil then item connected to current page is usedfunction Config:getLabel(id, raw, link, short, emptyAllowed) local label local mt = local value = setmetatable(mt)

if not id then id = mw.wikibase.getEntityIdForCurrentPage

if not id then return value, mt -- empty value end end

id = id:upper -- just to be sure

-- check if given id actually exists if not mw.wikibase.isValidEntityId(id) or not mw.wikibase.entityExists(id) then return value, mt -- empty value end

if raw then label = id else -- try short name first if requested if short then label = p.property -- get short name

if label

"" then label = nil end end

-- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) end

if not label and not emptyAllowed then return value, mt -- empty value end

value.label = label or "" end

-- split id for potential numeric sorting value[1] = id:sub(1,1) value[2] = tonumber(id:sub(2))

-- build a link if requested if link then if raw or value[1]

"P" then -- link to Wikidata if raw or if property (which has no sitelink) value.target = id

if value[1]

"P" then value.target = "Property:" .. value.target end

value.target = "d:" .. value.target else -- else, value[1]

"Q" value.target = mw.wikibase.getSitelink(id) end

if value.target and label then mt.format = [2].format end end

return value, mtend

function Config:getEditIcon local value = "" local prefix = "" local front = NB_SPACE local back = ""

if self.entityID:sub(1,1)

"P" then prefix = "Property:" end

if self.editAtEnd then front = '

' back = '' end

value = ""

return front .. value .. backend

function Config:convertUnit(unit, raw, link, short) local itemID local mt = local value = setmetatable(mt)

if unit

"" or unit

"1" then return value, mt end

itemID = parseWikidataURL(unit)

if itemID then if itemID

aliasesQ.percentage then value[1] = itemID:sub(1,1) value[2] = itemID:sub(2)

if not raw then value.label = "%" elseif link then value.target = "d:" .. itemID mt.format = [2].format end else value, mt = self:getLabel(itemID, raw, link, short)

if value.label then value.unitSep = NB_SPACE end end end

return value, mtend

function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.freeUnit, self.freeNumber, false, self.conf.emptyAllowed, self.param:sub(1, 1))end

-- returns a value object in the general form with metatable ;-- 'format' is the string representation of the value in unconcatenated form to exploit Lua's string internalization to reduce memory usage;-- this function must stay in sync with the parseValue functionfunction Config:getValue(snak, raw, link, short, anyLang, freeUnit, freeNumber, noSpecial, emptyAllowed, param) local mt = local value = setmetatable(mt)

if snak.snaktype

'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value

mt.datatype =

if datatype

'string' then local datatypes =

value[1] = datavalue mt.datatype = datatypes

if subtype

'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] value, mt = buildLink(datavalue) else value, mt = buildLink(datavalue, datavalue) end

mt.datatype = datatypes return value elseif subtype

'commonsMedia' then if link then value, mt = buildWikilink("c:File:" .. datavalue, datavalue) mt.datatype = datatypes elseif not raw then mt.format = end

return value elseif subtype

'geo-shape' and link then value, mt = buildWikilink("c:" .. datavalue, datavalue) mt.datatype = datatypes return value elseif subtype

'math' and not raw then local attribute = nil

if (param

parameters.property or (param

parameters.qualifier and self.propertyID

aliasesP.hasPart)) and snak.property

aliasesP.definingFormula then attribute = end

mt.tag = return value elseif subtype

'musical-notation' and not raw then mt.tag = return value elseif subtype

'external-id' and link then local url = p.property -- get formatter URL

if url ~= "" then url = urlEncode(mw.ustring.gsub(url, "$1", datavalue)) value, mt = buildLink(url, datavalue) mt.datatype = datatypes end

return value else return value end elseif datatype

'monolingualtext' then if anyLang or datavalue['language']

self.langCode then value[1] = datavalue['text'] value.language = datavalue['language'] end

return value elseif datatype

'quantity' then local valueStr, unit

if freeNumber or not freeUnit then -- get value and strip + signs from front valueStr = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") value[1] = tonumber(valueStr)

-- assertion; we should always have a value if not value[1] then return value end

if not raw then -- replace decimal mark based on locale valueStr = replaceDecimalMark(valueStr)

-- add delimiters for readability valueStr = i18n.addDelimiters(valueStr)

mt.format = end end

if freeUnit or (not freeNumber and not raw) then local mtUnit

unit, mtUnit = self:convertUnit(datavalue['unit'], raw, link, short)

if freeUnit and not freeNumber then value = unit mt = mtUnit mt.datatype = elseif unit[1] then value[#value + 1] = unit[1] value[#value + 1] = unit[2]

value.len = 1 -- (max) length used for sorting value.target = unit.target value.unitLabel = unit.label value.unitSep = unit.unitSep

if raw then mt.format = mergeArrays(mt.format, mtUnit.format or unit) else mt.format[#mt.format + 1] = unit.unitSep -- may be nil mergeArrays(mt.format, mtUnit.format or) end end end

return value elseif datatype

'time' then local y, m, d, p, yDiv, yRound, yFull, yRaw, mStr, ce, calendarID, target local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision']

if precision

11 then p = "d" elseif precision

10 then p = "m" else p = "y" yFactor = 10^(9-precision) end

y, m, d = parseDate(datavalue['time'], p)

if y < 0 then sign = -1 y = math.abs(y) end

-- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor

-- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true

if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv)

-- take the first year of the century/millennium as the raw year -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRaw = (yRound - 1) * yFactor + 1

if not raw then if precision

6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end

suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium yRound = yRaw end else -- precision

8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor yRaw = yRound

if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end

if sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRaw = yRaw + yFactor - 1

if raw then yRound = yRaw end end else local yReFactor, yReDiv, yReRound

-- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5)

if yRound

0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5)

if yReDiv

yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end

if yRound

0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end

if precision >= 1 and y ~= 0 then yFull = yRound * yFactor

yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5)

if yReDiv

yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5)

if yReDiv

yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end

yRaw = yRound * yFactor

if not raw then if precision

3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision

0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRaw if yRound

1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRaw end end else yRound = y yRaw = yRound mayAddCalendar = true end

value[1] = yRaw * sign value[2] = m value[3] = d value.len = 3 -- (max) length used for sorting value.precision = precision mt.format =

if not raw then if prefix ~= "" then mt.format[1] = prefix end

if m then mStr = self.langObj:formatDate("F", "1-"..m.."-1")

if d then if self.mdyDate then mt.format[#mt.format + 1] = mStr mt.format[#mt.format + 1] = " " mt.format[#mt.format + 1] = tostring(d) mt.format[#mt.format + 1] = "," else mt.format[#mt.format + 1] = tostring(d) mt.format[#mt.format + 1] = " " mt.format[#mt.format + 1] = mStr end else mt.format[#mt.format + 1] = mStr end

mt.format[#mt.format + 1] = " " end

mt.format[#mt.format + 1] = tostring(yRound)

if suffix ~= "" then mt.format[#mt.format + 1] = suffix end

if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end

if ce then mt.format[#mt.format + 1] = " "

if link then target = mw.wikibase.getSitelink(aliasesQ.commonEra)

if target then mergeArrays(mt.format, [2].format) else mt.format[#mt.format + 1] = ce end else mt.format[#mt.format + 1] = ce end end else mt.format[1] = padZeros(yRound * sign, 4)

if m then mt.format[#mt.format + 1] = "-" mt.format[#mt.format + 1] = padZeros(m, 2)

if d then mt.format[#mt.format + 1] = "-" mt.format[#mt.format + 1] = padZeros(d, 2) end end end

calendarID = parseWikidataURL(datavalue['calendarmodel'])

if calendarID and calendarID

aliasesQ.prolepticJulianCalendar then value[4] = p.JULIAN -- as value.len

3, this will not be taken into account while sorting

if mayAddCalendar then if not raw then mt.format[#mt.format + 1] = " ("

if link then target = mw.wikibase.getSitelink(aliasesQ.julianCalendar)

if target then mergeArrays(mt.format, [2].format) else mt.format[#mt.format + 1] = i18n['datetime']['julian'] end else mt.format[#mt.format + 1] = i18n['datetime']['julian'] end

mt.format[#mt.format + 1] = ")" else mt.format[#mt.format + 1] = SLASH mt.format[#mt.format + 1] = p.JULIAN end end end

return value elseif datatype

'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1)

local precision, unitsPerDegree, numDigits, strFormat, globe local latitude, latConv, latLink local longitude, lonConv, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local latDegrees, latMinutes, latSeconds local lonDegrees, lonMinutes, lonSeconds local degSymbol, minSymbol, secSymbol, separator

local latSign = 1 local lonSign = 1

local latFormat = local lonFormat =

if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west']

degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = LAT_DIR_N_EN latDirectionS = LAT_DIR_S_EN lonDirectionE = LON_DIR_E_EN lonDirectionW = LON_DIR_W_EN

degSymbol = SLASH minSymbol = SLASH secSymbol = SLASH separator = SLASH end

latitude = datavalue['latitude'] longitude = datavalue['longitude']

if latitude < 0 then latDirection = latDirectionS latDirectionEN = LAT_DIR_S_EN latSign = -1 latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = LAT_DIR_N_EN end

if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = LON_DIR_W_EN lonSign = -1 longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = LON_DIR_E_EN end

precision = datavalue['precision']

if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end

-- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision

if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end

if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end

numDigits = math.ceil(-math.log10(unitsPerDegree * precision))

if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end

strFormat = "%." .. numDigits .. "f"

if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits

if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv

latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60)

latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60))

if not raw then latFormat[5] = replaceDecimalMark(latSeconds) lonFormat[5] = replaceDecimalMark(lonSeconds) else latFormat[5] = latSeconds lonFormat[5] = lonSeconds end

latFormat[6] = secSymbol lonFormat[6] = secSymbol

value[3] = tonumber(latSeconds) * latSign value[6] = tonumber(lonSeconds) * lonSign end

latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60)

latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60)

if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) else latMinutes = tostring(latMinutes) lonMinutes = tostring(lonMinutes) end

if not raw then latFormat[3] = replaceDecimalMark(latMinutes) lonFormat[3] = replaceDecimalMark(lonMinutes) else latFormat[3] = latMinutes lonFormat[3] = lonMinutes end

latFormat[4] = minSymbol lonFormat[4] = minSymbol

value[2] = tonumber(latMinutes) * latSign value[5] = tonumber(lonMinutes) * lonSign

latDegrees = tostring(latDegrees) lonDegrees = tostring(lonDegrees) end

if not raw then latFormat[1] = replaceDecimalMark(latDegrees) lonFormat[1] = replaceDecimalMark(lonDegrees) else latFormat[1] = latDegrees lonFormat[1] = lonDegrees end

latFormat[2] = degSymbol lonFormat[2] = degSymbol

value[1] = tonumber(latDegrees) * latSign value[4] = tonumber(lonDegrees) * lonSign value.len = 6 -- (max) length used for sorting

latFormat[#latFormat + 1] = latDirection lonFormat[#lonFormat + 1] = lonDirection

if link then globe = parseWikidataURL(datavalue['globe'])

if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower else globe = "earth" end

latLink = table.concat("_") lonLink = table.concat("_")

value.target = "https://tools.wmflabs.org/geohack/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe value.isWebTarget = true

mt.format =