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
		retmonth:lower() = i > 12 and i - 12 or i
	end
	return ret
end
function Dts._viMonthNameAlt(n)
	return Dts._viAltMonthn and Dts._viAltMonthn 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 args2 or args3 or args4 then
		self:parseDateParts(args1, args2, args3, args4)
	elseif args1 then
		self:parseDate(args1)
	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.formatsself.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.monthSearchs
	if month then
		return month
	else
		month = Dts.monthSearchAbbrs
		if month then
			self.isAbbreviated = true
			return month
		else
			month = Dts.viMonthSearchDts._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 parts1 ==  or partsnParts ==  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 .. 'Thể loại:Lỗi bản mẫu Dts'
		end
		return ret
	end
end
function p.main(frame)
	local args = require('Mô đun:Arguments').getArgs(frame)
	return p._main(args)
end
return p