Module:Age/sandbox

This is an old revision of this page, as edited by Johnuniq (talk | contribs) at 07:19, 29 May 2016 (try a substitute for unpack). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
-- Implement various "age of" and other date-related templates.

local datemod = require('Module:Date')
local current = datemod._current
local Date = datemod._Date
local MINUS = '−'  -- Unicode U+2212 MINUS SIGN

local function strip_to_nil(text)
	-- If text is a string, return its trimmed content, or nil if empty.
	-- Otherwise return text (which may, for example, be nil).
	if type(text) == 'string' then
		text = text:match('(%S.-)%s*$')
	end
	return text
end

local function number_name(number, singular, plural, sep)
	-- Return the given number, converted to a string, with the
	-- separator (default space) and singular or plural name appended.
	plural = plural or (singular .. 's')
	sep = sep or ' '
	return tostring(number) .. sep .. ((number == 1) and singular or plural)
end

local function message(msg, nocat)
	-- Return formatted message text for an error.
	-- Can append "#FormattingError" to URL of a page with a problem to find it.
	local anchor = '<span id="FormattingError" />'
	local category
	if not nocat and mw.title.getCurrentTitle():inNamespaces(0, 10) then
		-- Category only in namespaces: 0=article, 10=template.
		category = '[[Category:Age error]]'
	else
		category = ''
	end
	return anchor ..
		'<strong class="error">Error: ' ..
		msg ..
		'</strong>' ..
		category .. '\n'
end

local function date_component(named, positional, component)
	-- Return the first of the two arguments (named like {{example|year=2001}}
	-- or positional like {{example|2001}}) that is not nil and is not empty.
	-- If both are nil, return the current date component, if specified.
	-- This translates empty arguments passed to the template to nil, and
	-- optionally replaces a nil argument with a value from the current date.
	named = strip_to_nil(named)
	if named then
		return named
	end
	positional = strip_to_nil(positional)
	if positional then
		return positional
	end
	if component then
		return current[component]
	end
	return nil
end

local function get_dates(frame, want_mixture)
	-- Return date1, date2 from template parameters.
	-- Either may be nil if given arguments are invalid.
	-- If want_mixture is true, a missing date component is replaced
	-- from the current date, so can get a bizarre mixture of
	-- specified/current y/m/d as has been done by some "age" templates.
	-- TODO Accept a date as a string like "1 April 2016".
	local date1, date2
	local args = frame:getParent().args
	if want_mixture then
		date1 = Date(
			date_component(args.year1 , args[1], 'year' ),
			date_component(args.month1, args[2], 'month'),
			date_component(args.day1  , args[3], 'day'  )
		)
		date2 = Date(
			date_component(args.year2 , args[4], 'year' ),
			date_component(args.month2, args[5], 'month'),
			date_component(args.day2  , args[6], 'day'  )
		)
	else
		local fields = {}
		for i = 1, 6 do
			fields[i] = strip_to_nil(args[i])
		end
		date1 = Date(fields[1], fields[2], fields[3])
		if fields[4] and fields[5] and fields[6] then
			date2 = Date(fields[4], fields[5], fields[6])
		else
			date2 = Date('currentdate')
		end
	end
	return date1, date2
end

local function age_days(frame)
	-- Return age in days between two given dates, or
	-- between given date and current date.
	-- This code implements the logic in [[Template:Age in days]].
	-- Like {{Age in days}}, a missing argument is replaced from the current
	-- date, so can get a bizarre mixture of specified/current y/m/d.
	local date1, date2 = get_dates(frame, true)
	if not (date1 and date2) then
		return message('Need valid year, month, day')
	end
	local sign = ''
	local result = date2.jd - date1.jd
	if result < 0 then
		sign = '−'
		result = -result
	end
	return sign .. tostring(result)
end

local function _age_ym(diff)
	-- Return text specifying date difference in years, months.
	local sign = diff.isnegative and MINUS or ''
	local years, months = diff:age('ym')
	local mtext = number_name(months, 'month')
	local result
	if years > 0 then
		local ytext = number_name(years, 'year')
		if months == 0 then
			result = ytext
		else
			result = ytext .. ',&nbsp;' .. mtext
		end
	else
		if months == 0 then
			sign = ''
		end
		result = mtext
	end
	return sign .. result
end

local function age_ym(frame)
	-- Return age in years and months between two given dates, or
	-- between given date and current date.
	-- The older date is usually first; result is negative if the first is newer.
	local date1, date2 = get_dates(frame, false)
	if not date1 then
		return message('Need valid year, month, day')
	end
	if not date2 then
		return message('Second date should be year, month, day')
	end
	return _age_ym(date2 - date1)
end

--[[ TODO
All these templates usually have the older date first (otherwise result is negative).
                    * = date1, date2 positional; date2 defaults to current
age_days(frame)		* also accepts named y,m,d and text dates
age_years(frame)	*
age_yd(frame)		* also accepts text dates
age_ym(frame)		*
age_ymd(frame)		* also accepts named y,m,d; can omit d for "or"
age_ymwd(frame)		. named only; defaults to current
]]
local function age_years(frame)
	-- TODO d1, d2 positional; d2 defaults to current; usually d1 < d2
end
local function age_yd(frame)
	-- TODO
end
local function age_ymd(frame)
	-- TODO
end
local function age_ymwd(frame)
	-- TODO
end

local function gsd_ymd(frame)
	-- Return Gregorian serial date of the given date, or the current date.
	-- Like {{Gregorian serial date}}, a missing argument is replaced from the
	-- current date, so can get a bizarre mixture of specified/current y/m/d.
	-- This also accepts positional arguments, although the original template does not.
	-- The returned value is negative for dates before 1 January 1 AD despite
	-- the fact that GSD is not defined for earlier dates.
	local args = frame:getParent().args
	local date = Date(
		date_component(args.year , args[1], 'year' ),
		date_component(args.month, args[2], 'month'),
		date_component(args.day  , args[3], 'day'  )
	)
	if date then
		return tostring(date.gsd)
	end
	return message('Need valid year, month, day')
end

local function ymd_from_jd(frame)
	-- Return formatted date from a Julian date.
	-- The result is y-m-d or y-m-d H:M:S if input includes a fraction.
	-- The word 'Julian' is accepted for the Julian calendar.
	local args = frame:getParent().args
	local date = Date('juliandate', args[1], args[2])
	if date then
		return date:text()
	end
	return message('Need valid Julian date number')
end

local function unpacked(args)
	local fields = {}
	for i = 1, 100 do
		local arg = strip_to_nil(args[i])
		if not arg then
			break
		end
		fields[i] = arg
	end
	return unpack(fields)
end

local function ymd_to_jd(frame)
	-- Return Julian date (a number) from a date (y-m-d), or datetime (y-m-d H:M:S),
	-- or the current date ('currentdate') or current datetime ('currentdatetime').
	-- The word 'Julian' is accepted for the Julian calendar.
	local args = frame:getParent().args
	local date = Date(unpacked(args))
	if date then
		return tostring(date.jd)
	end
	return message('Need valid year/month/day or "currentdate"')
end

return {
	age_days = age_days,
	age_years = age_years,
	age_yd = age_yd,
	age_ym = age_ym,
	age_ymd = age_ymd,
	age_ymwd = age_ymwd,
	gsd = gsd_ymd,
	JULIANDAY = ymd_to_jd,
	ymd_from_jd = ymd_from_jd,
	ymd_to_jd = ymd_to_jd,
}