Module:TimedeltaAbstract

local util_args = require("Module:ArgsUtil") local util_table = require("Module:TableUtil") local util_time = require("Module:TimeUtil") local util_vars = require("Module:VarsUtil") local LCS = require('Module:LuaClassSystem')

local p = LCS.class.abstract local h = {}

p.DAYS_PER_MONTH = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }

function p:main(s, e, fuzzy) -- expect string inputs local sDate = util_time.strToDateFuzzy(s) local eDate = util_time.strToDateFuzzy(e) if not sDate.year or not eDate.year then return self.outputUnknown end if not sDate.month or not eDate.month then return self:approxDurationFuzzyMonth(sDate, eDate) end if not sDate.day or not eDate.day then return self:approxDurationFuzzyDay(sDate, eDate) end return self:addFuzzyPrefixIffNeeded(self:approxDurationExact(sDate, eDate), fuzzy) end

function p:approxDurationFuzzyMonth(sDate, eDate) if sDate.year >= eDate.year - 1 then return self:approxDurationFuzzyMonthSameYear(sDate, eDate) .. self.clarifyApprox end if not sDate.month then sDate.month = 7 sDate.day = eDate.day or 1 end if not eDate.day then eDate.month = 7 eDate.day = sDate.day or 1 end return self.fuzzyPrefix .. self:approxDurationExact(sDate, eDate) end

function p:approxDurationFuzzyMonthSameYear(sDate, eDate) if (not sDate.month or sDate.month == 1) and (not eDate.month or eDate.month == 12) then return self.lessThanOneYear end sDate.month = sDate.month or 1 eDate.month = eDate.month or 12 sDate.day = sDate.day or eDate.day or 1 eDaysPerMonth = h.getDaysPerMonth(eDate) eDate.day = eDate.day or sDate.day or eDaysPerMonth[eDate.month] return self.lessThan .. self:approxDurationExact(sDate, eDate) end

function p:approxDurationFuzzyDay(sDate, eDate) if sDate.month == eDate.month then return self:approxDurationFuzzyDaySameMonth(sDate, eDate) end if not sDate.day and not eDate.day then -- since February rounds differently, and if we don't know either day we want to say just 1 month -- in this case just "cheat" and assign both as 1 instead of dealing with midpoint differences sDate.day = 1 eDate.day = 1 elseif not sDate.day then sDate.day = sDate.month == 2 and 14 or 15 elseif not eDate.day then eDate.day = eDate.month == 2 and 14 or 15 end return self.fuzzyPrefix .. self:approxDurationExact(sDate, eDate) end

function p:addFuzzyPrefixIffNeeded(output, fuzzy) if not util_args.castAsBool(fuzzy) then return output end if output:find('≈') then return output end return '≈' .. output end

function p:approxDurationFuzzyDaySameMonth(sDate, eDate) local eDaysPerMonth = h.getDaysPerMonth(eDate) if (not sDate.day or sDate.day == 1) and (not eDate.day or eDate.day == eDaysPerMonth[eDate.month]) then return self:lessThanOneMonth(eDaysPerMonth[eDate.month]) end if not sDate.day then sDate.day = 1 end if not eDate.day then eDate.day = eDaysPerMonth[eDate.month] end return self.lessThan .. self:approxDurationExact(sDate, eDate) end

function p:lessThanOneMonth(month) end

function p:approxDurationExact(sDate, eDate) local numberOfYears = h.getYearDiff(sDate, eDate) local numberOfMonths = h.getMonthDiff(sDate, eDate) local numberOfDays = h.getDayDiff(sDate, eDate) local tbl = { h.printNumberOfYears(numberOfYears), h.printNumberOfMonths(numberOfMonths), h.printNumberOfDays(numberOfYears, numberOfMonths, numberOfDays), }	util_table.removeFalseEntries(tbl) return table.concat(tbl, ' ') end

function h.getYearDiff(sDate, eDate) local monthOffset = eDate.month >= sDate.month and 0 or 1 return eDate.year - sDate.year - monthOffset end

function h.getMonthDiff(sDate, eDate) if not eDate.day then error(('Missing day input in end: %s'):format(e)) end if not sDate.day then error(('Missing day input in start: %s'):format(s)) end local dayOffset = eDate.day >= sDate.day and 0 or 1 if eDate.month >= sDate.month then return eDate.month - sDate.month - dayOffset end return 12 - (sDate.month - eDate.month) - dayOffset end

function h.getDayDiff(sDate, eDate) if eDate.day >= sDate.day then return eDate.day - sDate.day end local sDaysPerMonth = h.getDaysPerMonth(sDate) return sDaysPerMonth[sDate.month] - (sDate.day - eDate.day) end

function h.getDaysPerMonth(date) local ret = mw.clone(p.DAYS_PER_MONTH) if date.month ~= 2 then return ret end if math.floor(date.year / 1000) == date.year / 1000 then ret[2] = 29 return ret end if math.floor(date.year / 100) == date.year / 100 then return ret end if math.floor(date.year / 4) ~= date.year / 4 then return ret end ret[2] = 29 return ret end

function h.printNumberOfYears(numberOfYears) if numberOfYears <= 0 then return false end return ('%syr'):format(numberOfYears) end

function h.printNumberOfMonths(numberOfMonths) if numberOfMonths <= 0 then return false end return ('%smo'):format(numberOfMonths) end

function h.printNumberOfDays(numberOfYears, numberOfMonths, numberOfDays) if numberOfYears > 0 and numberOfMonths > 0 then return false end if numberOfDays <= 0 then return '' end return ('%sd'):format(numberOfDays) end return p