Mô đun:Date table sorting

local yesno = require('Mô đun: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 = {
"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",
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
}

Dts.monthsAbbr = {
"Th1",
"Th2",
"Th3",
"Th4",
"Th5",
"Th6",
"Th7",
"Th8",
"Th9",
"Th10",
"Th11",
"Th12",
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec"
}

Dts.viMonths = {
"một",
"hai",
"ba",
"tư",
"năm",
"sáu",
"bảy",
"tám",
"chín",
"mười",
"mười một",
"mười hai"
}

Dts._viAltMonth = {
["giêng"] = "hai",
["bốn"] = "tư"
}

function Dts._makeMonthSearch(t)
local ret = {}
for i, month in ipairs(t) do
ret[month:lower()] = i > 12 and i - 12 or i
end
return ret
end

function Dts._viMonthNameAlt(n)
return Dts._viAltMonth[n] and Dts._viAltMonth[n] or n
end

Dts.monthSearch = Dts._makeMonthSearch(Dts.months)
Dts.monthSearchAbbr = Dts._makeMonthSearch(Dts.monthsAbbr)
Dts.monthSearchAbbr['sept'] = 9 -- Allow "Sept" to match September
Dts.viMonthSearch = Dts._makeMonthSearch(Dts.viMonths)

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

-- Check for deprecated parameters.
if args.link then
self.hasDeprecatedParameters = true
end

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.viMonthSearch[Dts._viMonthNameAlt(s)]
if month then
return month
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' or bcLower 'tcn' then
if self.year and self.year > 0 then
self.year = -self.year
end
elseif bcLower ~= 'ad' and bcLower ~= 'ce' and bclower ~= 'scn' then
error(string.format(
"'%s' is not a valid era code (expected 'BC', 'BCE', 'AD', 'CE', 'TCN' and 'SCN')",
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 > 6 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 Lỗi trong Bản mẫu:Dts: %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('Mô đun:Arguments').getArgs(frame)
return p._main(args)
end

return p