Модуль:Сартаваньне датаў у табліцы
Дакумэнтацыю да гэтага модуля можна стварыць у Модуль:Сартаваньне датаў у табліцы/Дакумэнтацыя
local yesno = require('Модуль:ТакНе')
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 = {
"студзень",
"люты",
"сакавік",
"красавік",
"травень",
"чэрвень",
"ліпень",
"жнівень",
"верасень",
"кастрычнік",
"лістапад",
"сьнежань"
}
Dts.monthsAbbr = {
"сту",
"лют",
"сак",
"кра",
"тра",
"чэр",
"ліп",
"жні",
"вер",
"кас",
"ліс",
"сьн"
}
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['вера'] = 9 -- Хай "вера" будзе верасьнем
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)
-- Разабраць парамэтры даты.
-- На гэтым кроку таксама занатуем, дата ў фармаце DMY ці YMD,
-- і ці назва месяцу скарочаная.
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
-- Пры няслушных значэньнях вяртаем памылку
if self.year then
if self.year == 0 then
error('год ня можа быць нулявым', 0)
elseif self.year < -MAX_YEAR then
error(string.format(
'год ня можа быць меншы за %s',
lang:formatNum(-MAX_YEAR)
), 0)
elseif self.year > MAX_YEAR then
error(string.format(
'год ня можа быць большы за %s',
lang:formatNum(MAX_YEAR)
), 0)
elseif math.floor(self.year) ~= self.year then
error('год мусіць быць цэлым лікам', 0)
end
end
if self.month and (
self.month < 1
or self.month > 12
or math.floor(self.month) ~= self.month
) then
error('месяц мусіць быць цэлым лікам ад 1 да 12', 0)
end
if self.day and (
self.day < 1
or self.day > 31
or math.floor(self.day) ~= self.day
) then
error('дзень мусіць быць цэлым лікам ад 1 да 31', 0)
end
-- Задаць спосаб вываду месяцу, т. б. выводзіць
-- „студзень“ ці „сту“.
if args.abbr then
self.isAbbreviated = args.abbr == 'on' or yesno(args.abbr) or false
else
self.isAbbreviated = self.isAbbreviated or false
end
-- Задаць радок фармату
if args.format then
self.format = args.format
else
self.format = self.format or 'dmy'
end
if not Dts.formats[self.format] then
error(string.format(
"'%s' — хібны фармат",
tostring(self.format)
), 0)
end
-- Задаць дадатковы ключ. У канцы ключа сартаваньня дадаецца дадатковы,
-- каб карыстальнікі маглі самастойна адрозьніць аднолькавыя даты.
if args.addkey then
self.addkey = tonumber(args.addkey)
if not self.addkey or
self.addkey < 0 or
self.addkey > 9999 or
math.floor(self.addkey) ~= self.addkey
then
error("парамэтар 'addkey' мусіць быць цэлым лікам ад 0 да 9999", 0)
end
end
-- Задаць, абгортваць паказаную дату ці не.
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
-- Знайсьці нумар месяцу па ягонай назьве і ўсталяваць сьцяг
-- isAbbreviated у патрэбнае значэньне.
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
end
end
return nil
end
-- Разьбірае асобныя парамэтры для году, месяцу, дню і эпохі.
function Dts:parseDateParts(year, month, day, bc)
if year then
self.year = tonumber(year)
if not self.year then
error(string.format(
"'%s' — няслушны год",
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' — няслушны месяц",
tostring(month)
), 0)
end
end
if day then
self.day = tonumber(day)
if not self.day then
error(string.format(
"'%s' — няслушны дзень",
tostring(day)
), 0)
end
end
if bc then
local bcLower = type(bc) == 'string' and bc:lower()
if bcLower == 'да н. э.' or bcLower == 'да н. хр.' then
if self.year and self.year > 0 then
self.year = -self.year
end
elseif bcLower ~= 'н. э.' and bcLower ~= 'па н. хр.' then
error(string.format(
"'%s' — няслушная эра (магчымыя 'да н. э.', 'да Н. Хр.', 'н. э.' або 'па Н. Хр.')",
tostring(bc)
), 0)
end
end
end
-- Гэты мэтад разьбірае радок даты. Хоць і благая альтэрнатыва да
-- mw.language:formatDate, але ў выніку зь ім лягчэй разьбіраць дату, чым
-- карыстацца mw.language:formatDate, а пасьля спрабаваць здагадацца,
-- ці месяц у скарочанай форме, і ці фармат даты DMY або MDY.
function Dts:parseDate(date)
-- Агульнае паведамленьне пра памылку.
local function dateError()
error(string.format(
"'%s' — няслушная дата",
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
-- Сьпярша апрацоўвае даты толькі з гадоў, бо ў іх прысутнічаюць злучкі,
-- а пасьля трэба разьдзяліць радок па нялітарных сымбалях, у тым ліку
-- пераносах. Акрамя таго, няма патрэбы абмяжоўваць гады 3—4 разрадамі,
-- бо іх асобных ня зблытаць з днём ці нумарам месяцу.
self.year = tonumber(date)
if self.year then
return
end
-- Разьдзяліць радок па нялітарных сымбалях.
date = tostring(date)
local parts = mw.text.split(date, '%W+')
local nParts = #parts
if parts[1] == '' or parts[nParts] == '' or nParts > 3 then
-- Мы разьбіраем максымум тры элемэнты, таму калі іх болей, тады
-- вяртаем памылку. Калі першы ці апошні элемэнты пустыя, тады
-- пачатак ці канец радку быў нялітарным сымбалем, што мы таксама
-- ўважаем памылкай.
dateError()
elseif nParts < 1 then
-- Калі маем менш за адзін элемэнт, то штосьці пайшло абсалютна
-- ня так.
error(string.format(
"пры разборы даты '%s' адбылася нечаканая памылка",
date
), 0)
end
if nParts == 1 then
-- Гэта можа быць як назва месяцу, так і год.
self.month = self:parseMonthName(parts[1])
if not self.month then
self.year = parseYear(parts[1])
if not self.year then
dateError()
end
end
elseif nParts == 2 then
-- Можа быць адным з гэтых фарматаў:
-- DD Месяц
-- Месяц DD
-- Месяц YYYY
-- YYYY-MM
self.month = self:parseMonthName(parts[1])
if self.month then
-- Гэта або Месяц DD, або Месяц YYYY.
self.year = parseYear(parts[2])
if not self.year then
-- Гэта Месяц DD.
self.format = 'mdy'
self.day = parseDayOrMonth(parts[2])
if not self.day then
dateError()
end
end
else
self.month = self:parseMonthName(parts[2])
if self.month then
-- Гэта DD Месяц.
self.format = 'dmy'
self.day = parseDayOrMonth(parts[1])
if not self.day then
dateError()
end
else
-- Гэта YYYY-MM.
self.year = parseYear(parts[1])
self.month = parseDayOrMonth(parts[2])
if not self.year or not self.month then
dateError()
end
end
end
elseif nParts == 3 then
-- Можа быць адным з гэтых фарматаў:
-- DD Месяц YYYY
-- Месяц DD, YYYY
-- YYYY-MM-DD
-- DD-MM-YYYY
self.month = self:parseMonthName(parts[1])
if self.month then
-- Гэта Месяц DD, YYYY.
self.format = 'mdy'
self.day = parseDayOrMonth(parts[2])
self.year = parseYear(parts[3])
if not self.day or not self.year then
dateError()
end
else
self.day = parseDayOrMonth(parts[1])
if self.day then
self.month = self:parseMonthName(parts[2])
if self.month then
-- Гэта DD Месяц YYYY.
self.format = 'dmy'
self.year = parseYear(parts[3])
if not self.year then
dateError()
end
else
-- Гэта Месяц DD-MM-YYYY.
self.format = 'dmy'
self.month = parseDayOrMonth(parts[2])
self.year = parseYear(parts[3])
if not self.month or not self.year then
dateError()
end
end
else
-- Гэта YYYY-MM-DD
self.year = parseYear(parts[1])
self.month = parseDayOrMonth(parts[2])
self.day = parseDayOrMonth(parts[3])
if not self.year or not self.month or not self.day then
dateError()
end
end
end
end
end
function Dts:makeSortKey()
local year, month, day
local nYearDigits = N_YEAR_DIGITS
if self:hasDate() then
year = self.year or os.date("*t").year
if year < 0 then
year = -MAX_YEAR - 1 - year
nYearDigits = nYearDigits + 1 -- Для знаку мінусу
end
month = self.month or 1
day = self.day or 1
else
-- Пустыя ўключэньні {{Сартаваньне датаў у табліцы}} павінны
-- адсартавацца апошнімі.
year = MAX_YEAR
month = 99
day = 99
end
return string.format(
'%0' .. nYearDigits .. 'd-%02d-%02d-%04d',
year, month, day, self.addkey or 0
)
end
function Dts:getMonthName()
if not self.month then
return ''
end
if self.isAbbreviated then
return self.monthsAbbr[self.month]
else
return self.months[self.month]
end
end
function Dts:makeDisplay()
if self.format == 'hide' then
return ''
end
local hasYear = self.year and self.format:find('y')
local hasMonth = self.month and self.format:find('m')
local hasDay = self.day and self.format:find('d')
local isMonthFirst = self.format:find('md')
local ret = {}
if hasDay and hasMonth and isMonthFirst then
ret[#ret + 1] = self:getMonthName()
ret[#ret + 1] = ' '
ret[#ret + 1] = self.day
if hasYear then
ret[#ret + 1] = ','
end
elseif hasDay and hasMonth then
ret[#ret + 1] = self.day
ret[#ret + 1] = ' '
ret[#ret + 1] = self:getMonthName()
elseif hasDay then
ret[#ret + 1] = self.day
elseif hasMonth then
ret[#ret + 1] = self:getMonthName()
end
if hasYear then
if hasDay or hasMonth then
ret[#ret + 1] = ' '
end
local displayYear = math.abs(self.year)
if displayYear > 9999 then
displayYear = lang:formatNum(displayYear)
else
displayYear = tostring(displayYear)
end
ret[#ret + 1] = displayYear
if self.year < 0 then
ret[#ret + 1] = ' да н. э.'
end
end
return table.concat(ret)
end
function Dts:__tostring()
local root = mw.html.create()
local span = root:tag('span')
:attr('data-sort-value', self:makeSortKey())
-- Вывад
if self:hasDate() and self.format ~= 'hide' then
span:wikitext(self:makeDisplay())
if not self.isWrapping then
span:css('white-space', 'nowrap')
end
end
return tostring(root)
end
--------------------------------------------------------------------------------
-- Экспарт
--------------------------------------------------------------------------------
local p = {}
function p._exportClasses()
return {
Dts = Dts
}
end
function p._main(args)
local success, ret = pcall(function ()
local dts = Dts.new(args)
return tostring(dts)
end)
if success then
return ret
else
ret = string.format(
'<strong class="error">Памылка ў шаблёне [[Шаблён:Сартаваньне датаў у табліцы|Сартаваньне датаў у табліцы]]: %s</strong>',
ret
)
if mw.title.getCurrentTitle().namespace == 0 then
-- Катэгорыя толькі ў асноўнай прасторы
ret = ret .. '[[Катэгорыя:Вікіпэдыя:Старонкі з памылкамі ў парамэтрах шаблёнаў]]'
end
return ret
end
end
function p.main(frame)
local args = require('Модуль:Аргумэнты').getArgs(frame, {
wrappers = 'Шаблён:Сартаваньне датаў у табліцы',
})
return p._main(args)
end
return p