Mô đun:Sandbox/NhacNy2412/Date Table Sorting

local yesno = require('Module:Yesno')
local lang = mw.language.getContentLanguage()
local N_YEAR_DIGITS = 12
local MAX_YEAR = 10^N_YEAR_DIGITS - 1

--------------------------------------------------------------------------------
-- 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"
}

Dts.monthsVi = {
"tháng 1",
"tháng 2",
"tháng 3",
"tháng 4",
"tháng 5",
"tháng 6",
"tháng 7",
"tháng 8",
"tháng 9",
"tháng 10",
"tháng 11",
"tháng 12"
}

Dts.monthsViAbbr = {
"Th1",
"Th2",
"Th3",
"Th4",
"Th5",
"Th6",
"Th7",
"Th8",
"Th9",
"Th10",
"Th11",
"Th12"
}

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)
Dts.monthSearchAbbr['sept'] = 9 -- Allow "Sept" to match September
Dts.monthViSearch = Dts._makeMonthSearch(Dts.monthsVi)
Dts.monthViSearchAbbr = Dts._makeMonthSearch(Dts.monthsViAbbr)

Dts.formats = {
dmy = true,
mdy = true,
dm = true,
md = true,
my = true,
y = true,
m = true,
d = true,
hide = true
}

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 == 0 then
error('years cannot be zero', 0)
elseif self.year MAX_YEAR then
error(string.format(
'years cannot be greater than %s',
lang:formatNum(MAX_YEAR)
), 0)
elseif math.floor(self.year) ~= self.year then
error('years must be an integer', 0)
end
end
if self.month and (
self.month 12
or math.floor(self.month) ~= self.month
) then
error('months must be an integer between 1 and 12', 0)
end
if self.day and (
self.day 31
or math.floor(self.day) ~= self.day
) then
error('days must be an integer between 1 and 31', 0)
end

-- Set month abbreviation behaviour, i.e. whether we are outputting
-- "January" or "Jan".
if args.abbr then
self.isAbbreviated = args.abbr == 'on' or yesno(args.abbr) or false
else
self.isAbbreviated = self.isAbbreviated or false
end

-- 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 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 'off' or yesno(args.nowrap) false

return self
end

function Dts:hasDate()
return (self.year or self.month or self.day) ~= nil
end

-- 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
else
month = Dts.monthViSearch[s]
if month then
return month
else
month = Dts.monthViSearchAbbr[s]
if month then
self.isAbbreviated = true
return month
end
end
end
end
return nil
end

-- 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' then
self.month = self:parseMonthName(month)
end
if not self.month then
error(string.format(
"'%s' is not a valid month",
tostring(month)
), 0)
end
end
if day then
self.day = tonumber(day)
if not self.day then
error(string.format(
"'%s' is not a valid day",
tostring(day)
), 0)
end
end
if bc then
local bcLower = type(bc) 'string' and bc:lower()
if bcLower 'bc' or 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
end
end

-- 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 parts[nParts] 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 9999 then
displayYear = lang:formatNum(displayYear)
else
displayYear = tostring(displayYear)
end
ret[ret + 1] = displayYear
if self.year Error in Template:Date table sorting: %s',
ret
)
if mw.title.getCurrentTitle().namespace == 0 then
-- Only categorise in the main namespace
ret = ret ..
end
return ret
end
end

function p.main(frame)
local args = require('Module:Arguments').getArgs(frame, {
wrappers = 'Template:Date table sorting',
})
return p._main(args)
end

return p