local yesno = require('Module:Yesno')local lang = mw.language.getContentLanguagelocal N_YEAR_DIGITS = 12local MAX_YEAR = 10^N_YEAR_DIGITS - 1
---------------------------------------------------------------------------------- Dts class--------------------------------------------------------------------------------
local Dts = Dts.__index = Dts
Dts.months =
Dts.monthsAbbr =
function Dts._makeMonthSearch(t) local ret = for i, month in ipairs(t) do ret[month:lower] = i end return retendDts.monthSearch = Dts._makeMonthSearch(Dts.months)Dts.monthSearchAbbr = Dts._makeMonthSearch(Dts.monthsAbbr)Dts.monthSearchAbbr['sept'] = 9 -- Allow "Sept" to match September
Dts.formats =
function Dts.new(args) local self = setmetatable(Dts)
-- Parse date parameters. -- In this step we also record whether the date was in DMY or YMD format, -- and whether the month name was abbreviated. if args[2] or args[3] or args[4] then self:parseDateParts(args[1], args[2], args[3], args[4]) elseif args[1] then self:parseDate(args[1]) end
-- Raise an error on invalid values if self.year then if self.year
-- Set month abbreviation behaviour, i.e. whether we are outputting -- "January" or "Jan". if args.abbr then self.isAbbreviated = args.abbr
-- Set the format string if args.format then self.format = args.format else self.format = self.format or 'mdy' end if not Dts.formats[self.format] then error(string.format("'%s' is not a valid format", tostring(self.format) ), 0) end
-- Set addkey. This adds a value at the end of the sort key, allowing users -- to manually distinguish between identical dates. if args.addkey then self.addkey = tonumber(args.addkey) if not self.addkey or self.addkey < 0 or self.addkey > 9999 or math.floor(self.addkey) ~= self.addkey then error("the 'addkey' parameter must be an integer between 0 and 9999", 0) end end
-- Set whether the displayed date is allowed to wrap or not. self.isWrapping = args.nowrap
false
return selfend
function Dts:hasDate return (self.year or self.month or self.day) ~= nilend
-- Find the month number for a month name, and set the isAbbreviated flag as-- appropriate.function Dts:parseMonthName(s) s = s:lower local month = Dts.monthSearch[s] if month then return month else month = Dts.monthSearchAbbr[s] if month then self.isAbbreviated = true return month end end return nilend
-- Parses separate parameters for year, month, day, and era.function Dts:parseDateParts(year, month, day, bc) if year then self.year = tonumber(year) if not self.year then error(string.format("'%s' is not a valid year", tostring(year) ), 0) end end if month then if tonumber(month) then self.month = tonumber(month) elseif type(month)
'string' and bc:lower if bcLower
'bce' then if self.year and self.year > 0 then self.year = -self.year end elseif bcLower ~= 'ad' and bcLower ~= 'ce' then error(string.format("'%s' is not a valid era code (expected 'BC', 'BCE', 'AD' or 'CE')", tostring(bc) ), 0) end endend
-- This method parses date strings. This is a poor man's alternative to-- mw.language:formatDate, but it ends up being easier for us to parse the date-- here than to use mw.language:formatDate and then try to figure out after the-- fact whether the month was abbreviated and whether we were DMY or MDY.function Dts:parseDate(date) -- Generic error message. local function dateError error(string.format("'%s' is an invalid date", date ), 0) end
local function parseDayOrMonth(s) if s:find('^%d%d?$') then return tonumber(s) end end
local function parseYear(s) if s:find('^%d%d%d%d?$') then return tonumber(s) end end
-- Deal with year-only dates first, as they can have hyphens in, and later -- we need to split the string by all non-word characters, including -- hyphens. Also, we don't need to restrict years to 3 or 4 digits, as on -- their own they can't be confused as a day or a month number. self.year = tonumber(date) if self.year then return end
-- Split the string using non-word characters as boundaries. date = tostring(date) local parts = mw.text.split(date, '%W+') local nParts = #parts if parts[1]
or nParts > 3 then -- We are parsing a maximum of three elements, so raise an error if we -- have more. If the first or last elements were blank, then the start -- or end of the string was a non-word character, which we will also -- treat as an error. dateError elseif nParts < 1 then -- If we have less than one element, then something has gone horribly -- wrong. error(string.format("an unknown error occurred while parsing the date '%s'", date ), 0) end
if nParts
2 then -- This can be any of the following formats: -- DD Month -- Month DD -- Month YYYY -- YYYY-MM self.month = self:parseMonthName(parts[1]) if self.month then -- This is either Month DD or Month YYYY. self.year = parseYear(parts[2]) if not self.year then -- This is Month DD. self.format = 'mdy' self.day = parseDayOrMonth(parts[2]) if not self.day then dateError end end else self.month = self:parseMonthName(parts[2]) if self.month then -- This is DD Month. self.format = 'dmy' self.day = parseDayOrMonth(parts[1]) if not self.day then dateError end else -- This is YYYY-MM. self.year = parseYear(parts[1]) self.month = parseDayOrMonth(parts[2]) if not self.year or not self.month then dateError end end end elseif nParts
function Dts:makeSortKey local year, month, day local nYearDigits = N_YEAR_DIGITS if self:hasDate then year = self.year or os.date("*t").year if year < 0 then year = -MAX_YEAR - 1 - year nYearDigits = nYearDigits + 1 -- For the minus sign end month = self.month or 1 day = self.day or 1 else -- Blank transclusions should sort last. year = MAX_YEAR month = 99 day = 99 end return string.format('%0' .. nYearDigits .. 'd-%02d-%02d-%04d', year, month, day, self.addkey or 0 )end
function Dts:getMonthName if not self.month then return end if self.isAbbreviated then return self.monthsAbbr[self.month] else return self.months[self.month] endend
function Dts:makeDisplay if self.format
function Dts:__tostring local root = mw.html.create local span = root:tag('span') :attr('data-sort-value', self:makeSortKey)
-- Display if self:hasDate and self.format ~= 'hide' then span:wikitext(self:makeDisplay) if not self.isWrapping then span:css('white-space', 'nowrap') end end
return tostring(root)end
---------------------------------------------------------------------------------- Exports--------------------------------------------------------------------------------
local p =
function p._exportClasses return end
function p._main(args) local success, ret = pcall(function local dts = Dts.new(args) return tostring(dts) end) if success then return ret else ret = string.format('Error in : %s', ret ) if mw.title.getCurrentTitle.namespace
function p.main(frame) local args = require('Module:Arguments').getArgs(frame,) return p._main(args)end
return p