Модуль:Календарь
| ||
---|---|---|
Тематические статьи | ||
Техническая справка | ||
Тэги MediaWiki: | ||
Общие правила | ||
Модуль для календарных расчётов.
век
- Функция для расчёта века, которому принадлежит год
десятилетие
- Функция для расчёта десятилетия, которому принадлежит год
Навигатор
2024
2010-е
XX век
753 до н. э.
VIII век до н. э.
--[[
Dependencies
--]]
local locale = require 'Модуль:Локаль'
local roman, nonArabic, NAG = locale.roman, locale.nonArabic, locale.nonArabicGrammar
local function deromanise (roman)
return nonArabic (roman, 'roman')
end -- local function deromanise (roman)
local floor = math.floor
local m = {}
local CalendarData = {
defaultMethod = 2, -- default method of Easter date calculation when Easter type is not given
defaultFormat = "d.m.Y", -- default date output format
noFormat = "none", -- prevent from final date formatting
defaultOffset = 0, -- the Easter date
minimumOffset = -63, -- Septuagesima
maximumOffset = 68, -- Feast of the Sacred Heart
epochs = {
[1] = {
API = 'год' -- year name.
},
[10] = {
suffix = '-е',
API = 'десятилетие' -- decade name.
},
[100] = {
suffix = ' век',
style = 'roman',
from1 = true,
API = 'век'
},
[1000] = {
suffix = ' тысячелетие',
from1 = true,
API = 'тысячелетие'
}
}, -- epochs
BC = 'до н. э.',
-- API:
-- Easter:
apiEaster = 'пасхалия', -- public function name
argYear = 1, -- index or name of the argument with year
argEasterMethod = 'пасхалия', -- index or name of the argument with calculation method
argEasterOffset = 'день', -- index or name of the argument with offset in days relative to the calculated Easter Sunday
argEasterFormat = 'формат', -- index or name of the argument with date output format (#time style)
-- Decades and centuries:
argYearOffset = 2,
argYearNoEra = 'без эры',
apiNavigator = 'годы',
-- Navigator:
argEpoch = 1,
-- errors
errorMissingYear = 'Не указан год',
errorMissingEpoch = 'Не указана эпоха',
errorInvalidYear = "Аргумент «год» неверен: '%s'",
errorInvalidEpoch = "Аргумент «эпоха» неверен: '%s'",
errorNo0thYear = "Нулевого года не бывает",
errorInvalidOffset = "Аргумент «дни» ошибочен: '%s'",
errorInvalidMethod = "Аргумент «пасхалия» ошибочен: '%s'",
errorYearOutOfRange = "Пасха рассчитывается только с 326 по 4099 годы; указан год %d",
errorIncorrectMethod = "Западная и православная пасхалии отделяются от юлианской только с 1583; указан год %d",
errorUnknownMethod = "Неизвестный метод: %d",
errorInvalidYearOffset = "Аргумент «сдвиг» неверен: '%s'",
methods = {
["юлианская"] = 1,
["восточная"] = 2,
["православная"] = 2, -- alias for Eastern
["западная"] = 3,
["римская"] = 3, -- alias for Roman
},
relativeDates = {
["Семидесятница"] = -63,
["Sexagesima"] = -56,
["Масленица"] = -55,
["Fat Thursday"] = -52,
["Прощёное воскресенье"] = -49,
["Чистый понедельник"] = -48,
["Пепельная среда"] = -46,
["Вербное воскресенье"] = -7,
["Чистый четверг"] = -3,
["Великий четверг"] = -3,
["Страстная пятница"] = -2,
["Великая суббота"] = -1,
["Пасха"] = 0,
["Пасхальный понедельник"] = 1,
["Вознесение"] = 39,
["Отдание Пасхи"] = 39,
["Пятидесятница"] = 49,
["Corpus Christi"] = 60,
},
}
local function formatCalendarError (message, ...)
if select('#', ... ) > 0 then
message = string.format(message, ...)
end
return "<span class=\"error\">" .. message .. "</span>"
end
local sub, gsub, len = mw.ustring.sub, mw.ustring.gsub, mw.ustring.len
local function loadYear (year)
if not year then
return false, formatCalendarError (CalendarData.errorMissingYear)
end
-- Process BC:
year = gsub (year, '%s', '')
if sub (year, -6) == gsub (CalendarData.BC, '%s', '') then
year = '-' .. sub (year, 1, len (year) - 6)
end
local result = tonumber(year)
if not result or math.floor(result) ~= result then
return false, formatCalendarError (CalendarData.errorInvalidYear, year)
end
if result == 0 then
return false, formatCalendarError (CalendarData.errorNo0thYear, year)
end
return true, result
end
local function loadEasterMethod(method, year)
local result = CalendarData.defaultMethod
if method then
result = CalendarData.methods[method]
if not result then
return false, formatCalendarError (CalendarData.errorInvalidMethod, method)
end
end
if year < 1583 then
result = 1
end
return true, result
end
local function loadEasterOffset(day)
if not day then
return true, ""
end
local data = CalendarData.relativeDates
local offset = tonumber(day)
if not offset then
offset = data[day]
end
if not offset or offset ~= math.floor(offset) or offset < CalendarData.minimumOffset or offset > CalendarData.maximumOffset then
return false, formatCalendarError (CalendarData.errorInvalidOffset, day)
end
if offset < -1 then
return true, string.format(" %d days", offset)
elseif offset == -1 then
return true, " -1 day"
elseif offset == 0 then
return true, ""
elseif offset == 1 then
return true, " +1 day"
else -- if offset > 1 then
return true, string.format(" +%d days", offset)
end
end
local function loadEasterFormat(fmt)
if fmt == CalendarData.noFormat then
return true, nil
elseif not fmt then
return true, CalendarData.defaultFormat
else
return true, fmt
end
end
--[[
PURPOSE: This function returns Easter Sunday day and month
for a specified year and method.
INPUTS: Year - Any year between 326 and 4099.
Method - 1 = the original calculation based on the
Julian calendar
2 = the original calculation, with the
Julian date converted to the
equivalent Gregorian calendar
3 = the revised calculation based on the
Gregorian calendar
OUTPUTS: None.
RETURNS: 0, error message - Error; invalid arguments
month, day - month and day of the Sunday
NOTES:
The code is translated from DN OSP 6.4.0 sources.
The roots of the code might be found in
http://www.gmarts.org/index.php?go=415
ORIGINAL NOTES:
This algorithm is an arithmetic interpretation
of the 3 step Easter Dating Method developed
by Ron Mallen 1985, as a vast improvement on
the method described in the Common Prayer Book
Published Australian Almanac 1988
Refer to this publication, or the Canberra Library
for a clear understanding of the method used
Because this algorithm is a direct translation of
the official tables, it can be easily proved to be
100% correct
It's free! Please do not modify code or comments!
]]
local function calculateEasterDate(year, method)
if year < 326 or year > 4099 then
-- Easter dates are valid for years between 326 and 4099
return 0, formatCalendarError (CalendarData.errorYearOutOfRange, year)
end
if year < 1583 and method ~= 1 then
-- Western or Orthodox Easter is valid since 1583
return 0, formatCalendarError (CalendarData.errorIncorrectMethod, year)
end
-- intermediate result
local firstDig = math.floor(year / 100)
local remain19 = year % 19
local temp = 0
-- table A to E results
local tA = 0
local tB = 0
local tC = 0
local tD = 0
local tE = 0
-- Easter Sunday day
local d = 0
if method == 1 or method == 2 then
-- calculate PFM date
tA = ((225 - 11 * remain19) % 30) + 21
-- find the next Sunday
tB = (tA - 19) % 7
tC = (40 - firstDig) % 7
temp = year % 100
tD = (temp + math.floor(temp / 4)) % 7
tE = ((20 - tB - tC - tD) % 7) + 1
d = tA + tE
if method == 2 then
-- convert Julian to Gregorian date
-- 10 days were skipped in the Gregorian calendar from 5-14 Oct 1582
temp = 10
-- only 1 in every 4 century years are leap years in the Gregorian
-- calendar (every century is a leap year in the Julian calendar)
if year > 1600 then
temp = temp + firstDig - 16 - math.floor((firstDig - 16) / 4)
end
d = d + temp
end
elseif method == 3 then
-- calculate PFM date
temp = math.floor((firstDig - 15) / 2) + 202 - 11 * remain19
if firstDig > 26 then
temp = temp - 1
end
if firstDig > 38 then
temp = temp - 1
end
if firstDig == 21 or firstDig == 24 or firstDig == 25 or firstDig == 33 or firstDig == 36 or firstDig == 37 then
temp = temp - 1
end
temp = temp % 30
tA = temp + 21
if temp == 29 then
tA = tA - 1
end
if temp == 28 and remain19 > 10 then
tA = tA - 1
end
-- find the next Sunday
tB = (tA - 19) % 7
tC = (40 - firstDig) % 4
if tC == 3 then
tC = tC + 1
end
if tC > 1 then
tC = tC + 1
end
temp = year % 100
tD = (temp + math.floor(temp / 4)) % 7
tE = ((20 - tB - tC - tD) % 7) + 1
d = tA + tE
else
-- Unknown method
return 0, formatEeasteError(CalendarData.errorUnknownMethod, method)
end
if d > 61 then
-- when the oryginal calculation os converted to the Gregorian
-- calendar, Easter Sunday can occur in May
return 5, d - 61
elseif d > 31 then
return 4, d - 31
else
return 3, d
end
end
local function Easter (args)
local ok
local year
ok, year = loadYear (args[CalendarData.argYear])
if not ok then
return year
end
local method
ok, method = loadEasterMethod (args[CalendarData.argEasterMethod], year)
if not ok then
return method
end
local offset
ok, offset = loadEasterOffset (args[CalendarData.argEasterOffset])
if not ok then
return offset
end
local format
ok, format = loadEasterFormat (args[CalendarData.argEasterFormat])
if not ok then
return format
end
local month, day = calculateEasterDate (year, method)
if month == 0 then
return day
end
local result = string.format("%04d-%02d-%02d%s", year, month, day, offset)
if format then
result = mw.language.getContentLanguage():formatDate(format, result)
end
return result
end
-- Общий синтаксис, с "пасхалия":
m [CalendarData.apiEaster] = function (frame)
return Easter (frame.args)
end
-- Сокращённый синтаксис, без "пасхалия":
for holiday, _ in pairs (CalendarData.relativeDates) do
m [holiday] = function (frame)
local args = frame.args
args [CalendarData.argEasterOffset] = holiday
return Easter (args)
end
end
--[[
Period names:
--]]
local function loadYearOffset (offset)
if not offset then
return true, 0
end
local result = tonumber (offset)
if not result or math.floor (result) ~= result then
return false, formatCalendarError (CalendarData.errorInvalidYearOffset, offset)
end
return true, result
end -- local function loadYearOffset (offset)
local function boundaries (year, length)
local digit = year > 0 and 1 or 0
local start = (year - digit) - (year - digit) % length + digit
return start, start + length - 1
end -- local function boundaries (year, length)
m.b = boundaries
local function number (year, length, from1, style)
local serialiser = style and locale [style] or tostring
local no = from1 and floor (year / length) + 1 or year - year % length
return serialiser (no)
end -- local function number (year, length, from1, style)
m.nn = number
local abs = math.abs
local function year2epoch (year, length, suffix, era)
local start, finish = boundaries (year, length)
local name = abs (length > 1 and (year > 0 and start - 1 or finish + 1) or start)
local epoch = CalendarData.epochs [length]
return number (name, length, epoch.from1, epoch.style)
.. (suffix and epoch.suffix or '')
.. (era and year < 0 and ' ' .. CalendarData.BC or '')
end -- local function year2epoch (year, length, era)
local function wrapEpoch (func)
return function (frame)
local args = frame.args
local ok, year, offset, era
ok, year = loadYear (args [CalendarData.argYear])
if not ok then
return year
end
ok, offset = loadYearOffset (args [CalendarData.argYearOffset])
if not ok then
return offset
end
era = not (args [CalendarData.argYearNoEra] and args [CalendarData.argYearNoEra] ~= '')
return func (year + offset, era)
end
end -- local function wrapEpoch (func)
for length, epoch in pairs (CalendarData.epochs) do
m [epoch.API] = wrapEpoch (function (year, era)
return year2epoch (year, length, epoch.suffix, era)
end)
end
m.d = function (year, era)
return year2epoch (year, 10, true, era)
end
-- An iterator over epochs from greater to lesser:
local function epochs (reverse)
local lengths = {}
for length, _ in pairs (CalendarData.epochs) do
lengths [#lengths + 1] = length
end
table.sort (lengths, reverse and function (a, b) return a > b end or nil)
local key = 0
return function ()
key = key + 1
if key <= #lengths then
return lengths [key], CalendarData.epochs [lengths [key]]
end
end
end -- local function epochs (reverse)
-- Parse epoch:
local lpeg = lpeg
local P, C, Cc, R = lpeg.P, lpeg.C, lpeg.Cc, lpeg.R
local arabic = C ((P'-' ^ -1 * R'09' ^ 1)--[[ / tonumber -- for some reason, inverts the sign]])
local function grammar (length, style, suffix, from1, BC)
return Cc (length) * Cc (from1)
* (style and NAG (style) or arabic) * (suffix or '')
* (P' ' ^ 0 * P (BC) * Cc (true)) ^ -1 * -1
end -- local function grammar (length, style, suffix, from1, BC)
local function epoch (str)
local choice = P(false)
for length, epoch in epochs (true) do
choice = choice + grammar (length, epoch.style, epoch.suffix, epoch.from1, CalendarData.BC)
end
local length, from1, number, BC = choice:match (str)
local year = (tonumber (number) * (from1 and length or 1) + (from1 and -1 or length > 1 and 1 or 0))
* (BC and -1 or 1)
local start, finish = boundaries (year, length)
return length, start, finish, CalendarData.epochs [length].API
end -- local function epoch (str)
m.e = epoch
local function ribbon (start, finish, highlighted, period)
local tabs = {}
for current = start, finish, period do
local epoch = year2epoch (current, period, true, true)
local epoch_short = year2epoch (current, period, false, false)
tabs [#tabs + 1] = '<div'
.. (current == highlighted
and ' class="current">[[' .. epoch
or '>[[' .. epoch .. '|' .. epoch_short
)
.. ']]</div>'
end
return '\n<div class="row">' .. table.concat (tabs) .. '</div>'
end -- local function ribbon (year, period, onesuffix)
m.r = ribbon
local function implement_navigator (length, start)
local rows = ''
local this = start
for size, epoch in epochs (false) do
if size >= length or size * 10 == length then
local start, finish = boundaries (this, size * 10)
rows = ribbon (start, finish, size >= length and this or 0, size) .. rows
this = start
end
end
return '<div class="tabbed">' .. rows .. '</div>'
end -- local function implement_navigator (length, start)
m.i = implement_navigator
local function navigator (epoch_name)
local length, start, finish = epoch (epoch_name)
return implement_navigator (length, start)
end -- local function navigator (epoch_name)
m.n = navigator
local function loadEpoch (epoch_name)
if not epoch then
return false, formatCalendarError (CalendarData.errorMissingEpoch)
end
local length, start, finish = epoch (epoch_name)
if not length then
return false, formatCalendarError (CalendarData.errorInvalidEpoch)
end
return true, length, start, finish
end -- local function loadEpoch (epoch_name)
m [CalendarData.apiNavigator] = function (frame)
local args = frame.args
local ok, length, start, finish = loadEpoch (args [CalendarData.argEpoch])
if not ok then
return length
end
return implement_navigator (length, start)
end -- m [CalendarData.apiNavigator] = function (frame)
return m