Modul:Time interval

--[[ Calculate date and time intervals by invoking Module Date
Currently only provides age in years (age_years) for tests.
Planned to replace various age-related templates, for example:
        {{Age in days}}                 age_days
        {{Age in years and months}}     age_ym
        {{Gregorian serial date}}       gsd_ymd
]]

local Date = require('Module:Date')._Date

local MINUS = '−'  -- Unicode U+2212 MINUS SIGN

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)
    -- this uses an interesting trick of Lua:
    --  * and reurns false if the first argument is false, and the second otherwise, so (number==1) and singular returns singular if its 1, returns false if it is only 1
    --  * or returns the first argument if it is not false, and the second argument if the first is false
    --  * so, if number is 1, and evaluates (true and singular) returning (singular); or evaluates (singular or plural), finds singular non-false, and returns singular
    --  * but, if number is not 1, and evaluates (false and singular) returning (false); or evaluates (false or plural), and is forced to return plural
end

local function strip_to_nil(text)
    -- If text is a non-blank string, return its content with no leading
    -- or trailing whitespace.
    -- Otherwise return nil (a nil or empty string argument gives a nil
    -- result, as does a string argument of only whitespace).
    if type(text) == 'string' then
        local result = text:match("^%s*(.-)%s*$")
        if result ~= '' then
            return result
        end
    end
    return nil
end

local function show_args(frame)
    local args = frame:getParent().args
    local out = ''
	for k, v in pairs(args) do
		v = strip_to_nil(v)
		out = out .. k .. '=' .. (v or 'nil') .. ', '
	end
	return '<span style="color:black; background-color:#cfc;">' .. 'Received arguments: <br/>' .. out .. '</span>'
end

local function error_wikitext(text)
    -- Return message for display when template parameters are invalid.
    local prefix = '[[Module talk:Time interval|Module error]]:'
    local cat = '[[Category:Error in time interval]]'
    return '<span style="color:black; background-color:pink;">' ..
            prefix .. ' ' .. text .. cat .. '</span>'
end

local function time_interval(frame)
    -- Compute date and time interval between two given dates,
    -- or between given date and current date.
    local args = frame:getParent().args
    local fields = {}
    local date1, date2
    for i = 1, 2 do
        fields[i] = strip_to_nil(args[i])
    end
    if fields[1] then
    	date1 = Date(fields[1])
	    if not date1 then
	        return error_wikitext('Invalid start date in first parameter')
	    end
    end
    if fields[2] then
	    date2 = Date(fields[2])
        if not date2 then
            return error_wikitext('Invalid end date in second parameter')
        end
	else
		date2 = Date('currentdatetime')
	end
	interval = date2 - date1
	return interval
end

local function age_years(frame)
    -- Formats time interval as an age in years
    -- Default shows "15 years"
    -- Option disp=raw suppresses unit --> "15"
    -- Option disp=age adds " old" --> "15 years old"
    local args = frame:getParent().args
    local out
    local interval = time_interval(frame)
	if interval and interval.years then
	    if args.disp == 'raw' then
    		out = tostring(interval.years)
    	else
			out = number_name(interval.years, 'year')
		end
    	if args.disp == 'age' then
    		out = out .. ' old'
		end
	else
		out = ''
	end
	if frame.args.verbose then
		out = out .. '<br/>' .. show_args(frame)
	end
	return out
end

local function age_generic(frame)
    -- Formats time interval as a generic age
    -- Default shows "15 years"
    -- Option disp=raw suppresses unit --> "15"
    -- Option disp=age adds " old" --> "15 years old"
    -- Ages under 3 include years and months
    -- Ages under 1 include months and days
    local args = frame:getParent().args
    local out
    local interval = time_interval(frame)
	if interval and interval.years then
	    if args.disp == 'raw' then
    		out = tostring(interval.years)
    	else
			out = number_name(interval.years, 'year')
		end
		if interval.years < 1 then
			if interval.months > 0 then
				out = number_name(interval.months, 'month')
			else
				out = '' -- don't display 0 year and 0 month
			end
			out = out .. ' ' .. number_name(interval.days, 'day')
		elseif interval.years < 3 then
			out = out .. ' ' .. number_name(interval.months, 'month')
		end
    	if args.disp == 'age' then
    		out = out .. ' old'
		end
	else
		out = ''
	end
	if frame.args.verbose then
		out = out .. '<br/>' .. show_args(frame)
	end
	return out
end

return { age_years = age_years, age = age_generic }