Difference between revisions of "Module:Dts"
From Timelines
(try using formatDate for parsing) |
(detect invalid numbers) |
||
Line 138: | Line 138: | ||
if self.day == 1 and not stringHasNumber(args[1], 1) then | if self.day == 1 and not stringHasNumber(args[1], 1) then | ||
self.day = nil | self.day = nil | ||
+ | end | ||
+ | |||
+ | -- Detect abbreviated month names | ||
+ | if self.month then | ||
+ | local dateLower = args[1]:lower() | ||
+ | if not dateLower:find(Dts.months[self.month]:lower()) and | ||
+ | dateLower:find(Dts.monthsAbbr[self.month]:lower()) | ||
+ | then | ||
+ | self.abbr = true | ||
+ | end | ||
end | end | ||
else | else | ||
Line 156: | Line 166: | ||
end | end | ||
− | if | + | -- Raise an error on invalid values |
− | self.year = | + | if self.year then |
+ | if self.year == 0 then | ||
+ | error('years cannot be zero', 3) | ||
+ | elseif math.floor(self.year) ~= self.year then | ||
+ | error('years must be an integer', 3) | ||
+ | end | ||
end | end | ||
+ | if self.month and ( | ||
+ | self.month < 1 | ||
+ | or self.month > 12 | ||
+ | or math.floor(self.month) ~= self.month | ||
+ | ) then | ||
+ | error('months must be an integer between 1 and 12', 3) | ||
+ | end | ||
+ | if self.day and ( | ||
+ | self.day < 1 | ||
+ | or self.day > 31 | ||
+ | or math.floor(self.month) ~= self.month | ||
+ | ) then | ||
+ | error('months must be an integer between 1 and 31', 3) | ||
+ | end | ||
+ | |||
return self | return self | ||
end | end |
Revision as of 19:48, 1 July 2015
Documentation for this module may be created at Module:Dts/doc
local lang = mw.language.getContentLanguage() -------------------------------------------------------------------------------- -- Dts class -------------------------------------------------------------------------------- local Dts = {} Dts.__index = Dts Dts.months = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" } Dts.monthsAbbr = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" } function Dts._makeMonthSearch(t) local ret = {} for i, month in ipairs(t) do ret[month:lower()] = i end return ret end Dts.monthSearch = Dts._makeMonthSearch(Dts.months) Dts.monthSearchAbbr = Dts._makeMonthSearch(Dts.monthsAbbr) function Dts.new(args) local self = setmetatable({}, Dts) self.format = args.format or "mdy" self.abbr = false -- Default if args[2] or args[3] or args[4] then -- YMD parameters are specified individually. if args[1] then self.year = tonumber(args[1]) if not self.year then error(string.format( "'%s' is not a valid year", tostring(args[1]) ), 3) end end if args[2] then if tonumber(args[2]) then self.month = tonumber(args[2]) elseif type(args[2]) == 'string' then local lower = args[2]:lower() self.month = Dts.monthSearch[lower] if not self.month then self.month = Dts.monthSearchAbbr[lower] if self.month then self.abbr = true end end end if not self.month then error(string.format( "'%s' is not a valid month", tostring(args[2]) ), 3) end end if args[3] then self.day = tonumber(args[3]) if not self.day then error(string.format( "'%s' is not a valid day", tostring(args[3]) ), 3) end end if args[4] then local bc = type(args[4]) == 'string' and args[4]:lower() if bc == 'bc' or bc == 'bce' then if self.year and self.year > 0 then self.year = -self.year end elseif bc ~= 'ad' or bc ~= 'ce' then error(string.format( "'%s' is not a valid era code (expected 'BC', 'BCE', 'AD' or 'CE')", tostring(args[4]) ), 3) end end elseif args[1] then -- args[1] is the entire date that we need to parse. args[1] = tostring(args[1]) local date = Dts.formatDate('Y-m-d', args[1]) if date then self.year, self.month, self.day = date:match('^(%d%d%d%d)%-(%d%d)%-(%d%d)$') self.year = tonumber(self.year) self.month = tonumber(self.month) self.day = tonumber(self.day) -- Try to detect whether the values have been normalised, e.g. the -- user specified "February 2012" but formatDate added the day for -- us, making 2012-02-01. local function stringHasNumber(s, num) num = tostring(num) return s:find('%D0*' .. num .. '%D') or s:find('^0*' .. num .. '%D') or s:find('%D0*' .. num .. '$') or s:find('^0*' .. num .. '$') end local currentYear = os.date('*t').year if self.year == currentYear and not stringHasNumber(args[1], currentYear) then self.year = nil end if self.month == 1 and not args[1]:lower():find('jan') and not stringHasNumber(args[1], 1) then self.month = nil end if self.day == 1 and not stringHasNumber(args[1], 1) then self.day = nil end -- Detect abbreviated month names if self.month then local dateLower = args[1]:lower() if not dateLower:find(Dts.months[self.month]:lower()) and dateLower:find(Dts.monthsAbbr[self.month]:lower()) then self.abbr = true end end else error(string.format( "'%s' is an invalid date", tostring(args[1]) ), 3) end else error('no date parameters detected', 3) end -- Whether to output abbreviated month names if args.abbr then self.abbr = args.abbr == 'on' else self.abbr = self.abbr or false end -- Raise an error on invalid values if self.year then if self.year == 0 then error('years cannot be zero', 3) elseif math.floor(self.year) ~= self.year then error('years must be an integer', 3) end end if self.month and ( self.month < 1 or self.month > 12 or math.floor(self.month) ~= self.month ) then error('months must be an integer between 1 and 12', 3) end if self.day and ( self.day < 1 or self.day > 31 or math.floor(self.month) ~= self.month ) then error('months must be an integer between 1 and 31', 3) end return self end function Dts.formatDate(format, timestamp) local success, ret = pcall(lang.formatDate, lang, format, timestamp) if success then return ret end end function Dts:setmonth(raw) if not raw then self.month = nil return false end local numbermonth = tonumber(raw) if numbermonth and numbermonth > 0 and numbermonth < 13 then self.month = numbermonth return true end for i, mon in pairs(self.monthsSearch) do if string.find(string.lower(raw),mon) then self.month=i if string.find(string.lower(raw),string.lower(self.months[i])) then self.abbr=false else self.abbr=true end return true end end return false end function Dts:annonval(val, dayfirst) local numberval if val then numberval = tonumber(mw.text.trim(val,"%s%t,")) end if (not val) or (type(val)=="table") or (mw.text.trim(val)=="") then numberval = 0 end if not numberval then if mw.text.trim(string.lower(val)) == "bc" then if (not self.year) then self.year = self.day self.day = nil end if self.year then self.year = 0 - self.year end else if self:setmonth(val,dayfirst) and dayfirst and self.year and (not self.day) and (self.year > 0) and (self.year<31) then self.day = self.year self.year = nil self.format = "dmy" end end return end if self.month and (not self.day) and (numberval < 32) and (numberval > 0) then self.day = numberval return end if self.year and (not self.month) then self.month = numberval return end if (not self.year) then self.year = numberval return end end function Dts:monthName() if (not self.month) or (self.month < 0) or (self.month > 12) then return "" end if self.abbr then return self.monthsAbr[self.month] else return self.months[self.month] end end function Dts:makeSortKey() local year = self.year or os.date("*t").year year = year > 0 and year or -10000 - year return string.format( "%05d-%02d-%02d-%02d%02d", year, self.month or 1, self.day or 1, 0, 0 ) end function Dts:makeDisplay() local ret = {} if self.day then if self.format == "mdy" then ret[#ret + 1] = self:monthName() ret[#ret + 1] = ' ' ret[#ret + 1] = self.day if self.year then ret[#ret + 1] = ',' end else ret[#ret + 1] = self.day ret[#ret + 1] = ' ' ret[#ret + 1] = self:monthName() end elseif self.month then ret[#ret + 1] = self:monthName() end if self.year then if self.month then ret[#ret + 1] = ' ' end ret[#ret + 1] = math.abs(self.year) if self.year < 0 then ret[#ret + 1] = ' BC' end end return table.concat(ret) end function Dts:__tostring() local root = mw.html.create() root :tag('span') :addClass('sortkey') :css('display', 'none') :css('speak', 'none') :wikitext(self:makeSortKey()) :done() :tag('span') :css('white-space', 'nowrap') :wikitext(self:makeDisplay()) return tostring(root) end -------------------------------------------------------------------------------- -- Exports -------------------------------------------------------------------------------- local p = {} function p._main(args) local dts = Dts.new(args) return tostring(dts) end function p.main(frame) local args = getArgs(frame, { wrappers = 'Template:Dts', removeBlanks = false }) return p._main(args) end return p