Module:Date table sorting
Appearance
![]() | This Lua module is used on approximately 44,000 pages and changes may be widely noticed. Test changes in the module's /sandbox or /testcases subpages, or in your own module sandbox. Consider discussing changes on the talk page before implementing them. |
This module implements {{Date table sorting}}. Please see the template page for documentation.
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
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
if (self.year==0) then --not valid. placeholder for no-year
self.year = nil
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