Documentation for this module may be created at ماڊيول:Wikidata2/time/doc

local p = {}
------------------------------------------------------------------------------
-- module local variables and functions

local wiki = 
{
	langcode = mw.language.getContentLanguage().code
}

-- internationalisation
local i18n =
{    ["errors"] = {
        ["property-param-not-provided"] = "Property parameter not provided.",
        ["entity-not-found"] = "Entity not found.",
        ["unknown-claim-type"] = "Unknown claim type.",
        ["unknown-snak-type"] = "Unknown snak type.",
        ["unknown-datatype"] = "Unknown datatype.",
        ["unknown-entity-type"] = "Unknown entity type.",
        ["unknown-value-module"] = "You must set both value-module and value-function parameters.",
        ["value-module-not-found"] = "The module pointed by value-module not found.",
        ["value-function-not-found"] = "The function pointed by value-function not found."
    },
    ["datetime"] =
	{
		-- $1 is a placeholder for the actual number
		[0] = "$1 ارب سال",	--a درستي:مليار سال
		[1] = "$100 ملين سال",	--a درستي: مئات من ملايين السنين
		[2] = "$10 ملين سال",	--a درستي: عشرة ملايين سال
		[3] = "$1 ملين سال",	--a درستي: مليون سال
		[4] = "$100,000 سال",	--a درستی: مئات آلاف السنين
		[5] = "$10,000 سال",	--a درستی: عشرة آلاف سال
		[6] = "$1 ھزارين",	 	--a درستی: ألفية
		[7] = "صدي $1",		--a درستی: قرن
		[8] = "ڏھاڪو $1",		--a درستی: عقد
		-- the following use the format of #time parser function
		[9]  = "Y",		-- درستی: سال, 
		[10] = "F Y",		-- درستی: شهر
		[11] = "j F Y",		-- درستی: ڏھاڙو
		[12] = "j F Y ga",		-- درستی: ساعة
		[13] = "j F Y g:ia",		-- درستی: دقائق
		[14] = "j F Y g:i:sa",	-- درستی: ثواني
		["beforenow"] = "$1 ق م",	-- how to format negative numbers for precisions 0 to 5
		["afternow"] = "$1ء",		-- how to format positive numbers for precisions 0 to 5
		["bc"] = '$1 "ق م"',		-- How to print negative years
		["ad"] = '$1"ء"',				-- How to print positive years
		-- the following are for function getDateValue()
		["default-format"] = "dmy", -- القيمة الافتراضية ل #3 (getDateValue)
		["default-addon"] = "ق م",   -- default value of the #4 (getDateValue)
		["prefix-addon"] = false,   -- set to true for languages put "BC" in front of the
									-- datetime string; or the addon will be suffixed
		["addon-sep"] = " ",		-- separator between datetime string and addon (or inverse)
		["format"] =				-- options of the 3rd argument
		{
			["j F Y"] = "j F Y",
			["dmy"] = "j F Y",
			["j F"] = "j F",
			["longdate"] = "j F Y",
			["my"] = "F Y",
			["y"] = "Y",
			["Y"] = "Y",
			["F"] = "F",
			["n"] = "n",
			["m"] = "F",
			["j"] = "j",
			["d"] = "j",
			["ymd"] = "Y-m-d",
			["ym"] = "Y-m"
		}
	},
	["ordinal"] = 
	{
		[1] = "", --st
		[2] = "", --nd
		[3] = "", --rd
		["default"] = "" --th
	}
}
local categorywikidatadata = '[[زمرو:وڪي ڊيٽا سان صفحا]]';

-- this function needs to be internationalised along with the above:
-- we need three exceptions in English for 1st, 2nd, 3rd
-- takes cardinal numer as a numeric and returns the ordinal as a string
local function makeOrdinal(cardinal)
	local ordsuffix = i18n.ordinal.default
	if cardinal % 10 == 1 then
		ordsuffix = i18n.ordinal[1]
	elseif cardinal % 10 == 2 then
		ordsuffix = i18n.ordinal[2]
	elseif cardinal % 10 == 3 then
		ordsuffix = i18n.ordinal[3]
	end
	return tostring(cardinal) .. ordsuffix
end

local function formatError(code)
	return '<span class="error">' .. (i18n.errors[code] or code) .. '</span>'
end

local function parseDateValue(timestamp, 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
	local function d(f)
		local year_suffix
		local tstr = ""
		local lang_obj = mw.language.new(wiki.langcode)
		local f_parts = mw.text.split(f, '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
		if addon ~= "" and prefix_addon then
			return addon .. addon_sep .. tstr
		elseif addon ~= "" then
			return tstr .. addon_sep .. addon
		else
			return tstr
		end
	end
	local _date_format = i18n["datetime"]["format"][date_format]
	if _date_format ~= nil then
		return d(_date_format)
	else
		return formatError("unknown-datetime-format")
	end
end

-- This local function combines the year/month/day/BC/BCE handling of parseDateValue{}
-- with the millennium/century/decade handling of formatDate()
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 (8), centuries (7) and millennia (6)
	local era, card
	if precision == 6 then
		card = math.floor((intyear - 1) / 1000) + 1
		era = mw.ustring.gsub(i18n.datetime[6], "$1", makeOrdinal(card))
	end
	if precision == 7 then
		card = math.floor((intyear - 1) / 100) + 1
		era = mw.ustring.gsub(i18n.datetime[7], "$1", makeOrdinal(card))
	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 formatError("unknown-datetime-format")
	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 - second
local 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), year
end

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)
	end
end

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 formatError("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 "[[" .. linkTarget  .. "|" .. (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 nil end --"[[:d:" .. id .. "|" .. id .. "]]" end
			end
		else
			return data[parameter]
		end
	else
		return mw.wikibase.label(id) or id
	end
end

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)
	end
end

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
	end
end

-- 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
       if v.mainsnak.datavalue.value.precision == 9 then
    	timestamp = string.gsub(timestamp ,'-00','-01') end
					out[#out + 1] = parseDateFull(timestamp, dateprecision, date_format, date_addon)
				end
			end
			return table.concat(out, "، ") 
		else
			return ""
		end
	else
		return input_parm
	end
end
function p.getdate(time1, options)
		if options.modifytime and options.modifytime ~= ""
		then 
			formatd = options.modifytime
		else 
			formatd = i18n["datetime"]["default-format"]
		end
		local date_format = mw.text.trim(formatd)
		local timestamp = time1.time
		local dateprecision = time1.precision

       if dateprecision == 9 then
    	timestamp = string.gsub(timestamp ,'-00','-01') end

		local date_addon = mw.text.trim(options.date_addon or i18n["datetime"]["default-addon"])
		local tid = parseDateFull(timestamp, dateprecision, date_format, date_addon)
	return tid

end


function p.getdate1(time1, modifytime )
		if modifytime  and modifytime  ~= '' 
		then 
			formatd = modifytime 
		else 
			formatd = i18n["datetime"]["default-format"]
		end
		local date_format = mw.text.trim(formatd)
		local timestamp = time1.time
		local dateprecision = time1.precision

       if dateprecision == 9 then
    	timestamp = string.gsub(timestamp ,'-00','-01') end

		local date_addon = mw.text.trim(i18n["datetime"]["default-addon"])
		local tid = parseDateFull(timestamp, dateprecision, date_format, date_addon)
	return tid

end

-- returns the page id (Q...) of the current page or nothing of the page is not connected to Wikidata
function p.pageId(frame)
	local entity = mw.wikibase.getEntityObject()
	if not entity then return nil else return entity.id end
end



return p