Модуль:Dates
Этот модуль содержит функции для работы с датами.
Тестирование конвертаций
эдер- {{#invoke:Dates|parseISO8601|1380-09-08T00:00:00Z}} → -18596908800
- {{#invoke:Dates|parseISO8601|1700-03-11T00:00:00Z}} → -8514374400
- {{#invoke:Dates|parseISO8601|1799-06-06T00:00:00Z}} → -5382720000
- {{#invoke:Dates|parseISO8601|1800-03-12T00:00:00Z}} → -5358614400
- {{#invoke:Dates|parseISO8601|1825-12-26T00:00:00Z}} → -4544726400
- {{#invoke:Dates|parseISO8601|1837-02-10T00:00:00Z}} → -4193596800
- {{#invoke:Dates|parseISO8601|1900-03-13T00:00:00Z}} → -2202854400
- {{#invoke:Dates|parseISO8601|2900-03-13T00:00:00Z}} → 29354140800
- {{#invoke:Dates|formatWiki|-18596908800}} → 1380 сентябрьның 8
- {{#invoke:Dates|formatWiki|-8514374400}} → 29 февральдың (11 марттың) 1700
- {{#invoke:Dates|formatWiki|-5382720000}} → 26 майның (6 июньнуң) 1799
- {{#invoke:Dates|formatWiki|-5358614400}} → 29 февральдың (12 марттың) 1800
- {{#invoke:Dates|formatWiki|-5358614400}} → 14 (26) декабрьның 1825
- {{#invoke:Dates|formatWiki|-4193596800}} → 29 январьның (10 февральдың) 1837
- {{#invoke:Dates|formatWiki|-2202854400}} → 29 февральдың (13 марттың) 1900
--[[
В это модуле собраны функции, связанные с работой с датами.
]]
local monthg = {'январьның', 'февральдың', 'марттың', 'апрельдиң', 'майның', 'июньнуң',
'июльдуң', 'августтуң', 'сентябрьның', 'октябрьның', 'ноябрьның', 'декабрьның', 'бир айның', 'ийи айның', 'үш айның', 'дөрт айның', 'беш айның', 'алды айның',
'чеди айның', 'сес айның', "тос айның", 'он айның', 'он бир айның', 'он ийи айның',
'январь', 'февраль', 'март', 'апрель', 'май', 'июнь',
'июль', 'август', 'сентябрь', 'октябрь', 'ноябрь', 'декабрь'}
local monthd = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
local function DecodeDate(d)-- Ч, М, Г, СЧ, СМ, СГ, хвост
--дата: "%-?%d+"=год, "%d+%.%d+"=число месяца, "%d+%.%d+%.%-?%d+"=ЧМГ,
-- потом в скобках м.б. переопределено для старого стиля начиная с числа
local nd=d:match("^[%d.-]*");
local od=d:match("^[%d.-]*%s*%(%s*([%d.-]*)%s*%)");
local tail = d:match("^[%d.-]+%s*%(%s*[%d.-]+%s*%)%s*(%S.*)") or d:match("^[%d.-]+%s*([^%s%d].*)");
if nd:match('^%-?%d+$' ) then
return nil, nil, tonumber(nd), nil, nil, od and tonumber(od:match("%-?%d+$")),tail
else
local j,m,y=nd:match("^(%d+)%.(%d+)%.?(%-?%d*)");
if j then
if od then
local oj, om, oy = od:match("^(%d+)%.?(%d*)%.?(%-?%d*)");
return j and tonumber(j),
m and tonumber(m),
y>'' and tonumber(y) or nil,
oj and tonumber(oj),
om>'' and tonumber(om) or nil,
oy>'' and tonumber(oy) or nil,
tail
end
return j and tonumber(j), m and tonumber(m), y>'' and tonumber(y) or nil, nil, nil, nil, tail
else return nil
end
end
end
local function Diffy(d1,m1,y1,d0,m0,y0)--аналог Персона/Дата/Прошло лет
return y1-y0 - ( y1*y0<=0 and 1 or 0 ) - ( (m1<m0 or m1==m0 and d1<d0) and 1 or 0 )
end
local function Year0(y,t)-- аналог Год0
if y>0 then return table.concat{
'[[', tostring(y), ' чыл|', t and tostring(y)..' '..t or tostring(y), ']]'
} else return table.concat{
'[[', tostring(-y), ' б.э.ч. чыл|',
t and tostring(-y)..' '..t or tostring(-y),
' б. э. ч.]]'
}
end
end
local function FormDate(j,m,y,oj,om,oy,mo)-- ~ Персона/Дата/Logic 4
if j then
if not m then return "''меге формат''" end
if y then return
string.format(
'<span style="white-space:nowrap;">%s<span style="display:none">(<span class="%s">%04i-%02i-%02i</span>)</span></span>',
table.concat(
oj and (
om and (
oy and {-- ДД ММММ ГГГГ ([[ДД ММММ]] [[ГГГГ]])
oj,' ',monthg[om],' ',oy,
'</span> <span style="white-space:nowrap;">([[',
j, ' ', monthg[m],']] ',Year0(y),')'
} or {-- ДД ММММ ([[ДД ММММ]]) [[ГГГГ]]
oj,' ',monthg[om],' ([[',j,' ',monthg[m],']]) ',Year0(y)
}
) or {-- ДД [[ДД ММММ|(ДД) ММММ]] [[ГГГГ]]
oj,' [[',j,' ',monthg[m],'|','(',j,') ',monthg[m],']] ',Year0(y)
}
) or {'[[',j,' ',monthg[m],']] ',Year0(y)}
),--/table.concat
({['Рождения']='bday',['Смерти']='dday'})[mo] or '',
y,m,j
)--/string.format
else return
'<span style="white-space:nowrap;">' .. table.concat(
oj and (
om and {-- ДД ММММ ([[ДД ММММ]])
oj,' ',monthg[om],' ([[',j,' ',monthg[m],']])</span>'
} or {-- ДД [[ДД ММММ|(ДД) ММММ]]
oj,' [[',j,' ',monthg[m],'|','(',j,') ',monthg[m],']]</span>'
}
) or {'[[',j,' ',monthg[m],']]</span>'}
)
end
else
return y and string.format(
'<span style="white-space:nowrap;">%s<span style="display:none;">(<span class="bday">%04i</span>)</span></span>',
Year0(y,'год'),y) or "''формат неверен''"
end
end
local function GetDate(D)--dd.mm.-?yyyy или -?yyyy-mm-dd в три переменных d,m,y
local d,m,y = d:match('^%s*(%d%d?)[/.]([01]?%d)[/.](%-?%d+)')
if not d then y,m,d = D:match('^%s*(%-?%d+)[-\\]0*(1?%d)[-\\]0*(%d+)') end
return tonumber(d),tonumber(m),tonumber(y)
end
local function Cmp(a,b)--Сравнивает две даты, результат соответственно -1, 0 или 1
local d1,m1,y1 = GetDate(a)
local d2,m2,y2 = GetDate(b)
return d1 and d2 and (--nil, если формат не опознан
y1==y2 and (
m1==m2 and (
d1==d2 and 0 or d1<d2 and -1 or 1
) or m1<m2 and -1 or 1
) or y1<y2 and -1 or 1
)
end
function Yyyymmdd(r)--Переводит русскую дату в YYYY,MM,DD
local y, m, d = mw.ustring.match(r, "^%s*(%d+)%s+([а-яА-ЯөңүӨҢҮ ]+)%s+(%d%d?)")
if not m then return nil end
m = mw.ustring.lower(m)
--тупо перебор
for i = 1, 36 do
if m == monthg[i] then
M = i%12
break
end
end
if not M then
return nil
end
return tonumber(y), M, tonumber(d)
end
local p = {}
p = {
ifdate=function(f)-- Для шаблона "Если дата", имитирует старое поведение
-- Аргументы передаются шаблону
return f:getParent().args[ mw.ustring.match(frame.args[1],"^[ %d.%-−%()]*$") and 2 or 3 ]
end;
DecodeDate=DecodeDate;Diffy=Diffy;Year0=Year0;GetDate=GetDate;Cmp=Cmp;
Yyyymmdd=Yyyymmdd;
diffy=function(f)-- принимает параметры #invoke в виде двух строк-дат
local d1,m1,y1=DecodeDate(f.args[1]);
local d0,m0,y0=DecodeDate(f.args[2])
return Diffy(d1,m1,y1,d0,m0,y0)
end;
monthg=function(f) return monthg[ f.args[1] or f:getParent().args[1] ] end;--realmonth
persdate=function(f)-- Для шаблона Персона/Дата;{{#invoke:dates|persdate|nocat={{NAMESPACE}}}}
local frame=f:getParent();
local catpref,mo,d,d2={['Рождения']='Төрүттүнген',['Смерти']='Мөчээн'}, frame.args[1],frame.args[2],frame.args[3]
local cat, j,m,y,oj,om,oy,tail, j2,m2,y2, age = ''
if d then
j,m,y,oj,om,oy,tail=DecodeDate(d:gsub('−','-'));
if not (j or y) then
return (frame.args.nocat and d or d..'[[Category:Википедия:Статьи с ручной викификацией дат в карточке]]')
end
end;
if d2 then
j2,m2,y2 = DecodeDate(d2:gsub('−','-'));
end;
return table.concat{
FormDate(j,m,y,oj,om,oy,mo),
( (frame.args['nopersoncat'] or '')~='' or (f.args['nocat'] or '')~='' ) and '' or table.concat{
'[[Category:Алфавиттээн кижилер]]',
j and string.format('[[Аңгылал:%s %i %s]]',catpref[mo],j,monthg[m]) or '',
y and string.format('[[Аңгылал:%s %s]]',catpref[mo],y,Year0(y,'году')) or ''
},--/table.concat внутр.
(function(F)--возраст
if not F then return '' end;
local n=F();
return n and string.format(" (%i %s)%s",
n,
mw.getLanguage('ru'):plural(n,'хар','харлыг','хар'),
n>150 and '[[Category:Википедия:Амгы узун назылыг кижилер]]' or ''
) or ''
end)( ({
['Рождения']=function()
local now=os.date('*t');
if (not d2 or d2=='') and j and m and y then
return Diffy(now.day,now.month,now.year,j,m,y)
end
end,
['Смерти']=function()
return j and m and y and j2 and m2 and y2 and Diffy(j,m,y,j2,m2,y2);
end,
})[mo] ),--конец вызова функции возраста
tail or '',
cat
}--/table.concat внеш.
end;
formdate=function(f) -- Формирует дату по 3--6 параметрам #invoke или шаблона
--не использовать с пустыми аргументами
if (f.args[1] or '')~='' and (f.args[2] or '')~='' or (f.args[3] or '')~='' then
return FormDate(f.args[1],f.args[2],f.args[3],f.args[4],f.args[5],f.args[6],f.args['m'])
else
local tf=f:getParent();
return FormDate(tf.args[1],tf.args[2],tf.args[3],tf.args[4],tf.args[5],tf.args[6],tf.args['m'])
end
end;
cmp=function(f)--Сравнивает две даты, результат соответственно -1, 0 или 1
return Cmp(f.args[1],f.args[2])
end;
G2J=function(f)--перевод григорианских дат в юлианские, возврат DD.MM.YYYY
--Не знает про 15 октября 1582 года, не работает до нашей эры и после ???99 года
--Если есть второй аргумент, преобразует только ДО этой даты включительно
--Если есть третий аргумент, результат форматирует под Персона/Дата
local d,m,y=GetDate(f.args[1])
if f.args[2] and Cmp(f.args[1],f.args[2])==1 then
return string.format("%i.%i.%i",d,m,y)
end
local shift=math.floor(y/100)-math.floor(y/400)-2
if d-shift>0 then
return f.args[3] and string.format("%i.%i.%i (%i)",d,m,y,d-shift)
or string.format("%i.%i.%i",d-shift,m,y)
else
if m==1 then
return f.args[3]
and string.format("%i.1.%i (%i.12.%i)",d,y,31+d-shift,y-1)
or string.format("%i.12.%i",31+d-shift,y-1)
elseif m==3 then
return f.args[3] and string.format("%i.3.%i (%i.2)", d,y,
(y%4==0 and 29 or 28)+d-shift-(y%100==0 and y%400~=0 and 1 or 0)
)
or string.format("%i.2.%i",
(y%4==0 and 29 or 28)+d-shift-(y%100==0 and y%400~=0 and 1 or 0)
,y)
else
return f.args[3] and string.format(
"%i.%i.%i (%i.%i)", d,m,y, monthd[m-1]+d-shift,m-1
)
or string.format("%i.%i.%i",monthd[m-1]+d-shift,m-1,y)
end
end
end;
yyyymmdd = function(f)
local date, hourmin = f.args[1]
if mw.ustring.match(date, "^%s*%d+\-%d+\-%d+") then
return date
end
hourmin = mw.ustring.match(date, "%s+%d+:%d+$")
local y, m, d = Yyyymmdd(date)
if not y then
return '<span class="error">Алдаг: ай-хүннүң формады меге.</span>'
end
return string.format('%4i-%02i-%02i', y, m, d) .. (hourmin or '')
end
}
function table.val_to_str ( v )
if "string" == type( v ) then
v = string.gsub( v, "\n", "\\n" )
if string.match( string.gsub(v,"[^'\"]",""), '^"+$' ) then
return "'" .. v .. "'"
end
return '"' .. string.gsub(v,'"', '\\"' ) .. '"'
else
return "table" == type( v ) and table.tostring( v ) or
tostring( v )
end
end
function table.key_to_str ( k )
if "string" == type( k ) and string.match( k, "^[_%a][_%a%d]*$" ) then
return k
else
return "[" .. table.val_to_str( k ) .. "]"
end
end
function table.tostring( tbl )
local result, done = {}, {}
for k, v in ipairs( tbl ) do
table.insert( result, table.val_to_str( v ) )
done[ k ] = true
end
for k, v in pairs( tbl ) do
if not done[ k ] then
table.insert( result,
table.key_to_str( k ) .. "=" .. table.val_to_str( v ) )
end
end
return "{" .. table.concat( result, "," ) .. "}"
end
function parseISO8601Date(str)
local pattern = "(%-?%d+)%-(%d+)%-(%d+)T"
local Y, M, D = mw.ustring.match( str, pattern )
return tonumber(Y), tonumber(M), tonumber(D)
end
function parseISO8601Time(str)
local pattern = "T(%d+):(%d+):(%d+)%Z"
local H, M, S = mw.ustring.match( str, pattern)
return tonumber(H), tonumber(M), tonumber(S)
end
function parseISO8601Offset(str)
if str:sub(-1)=="Z" then return 0,0 end -- ends with Z, Zulu time
-- matches ±hh:mm, ±hhmm or ±hh; else returns nils
local pattern = "([-+])(%d%d):?(%d?%d?)$"
local sign, oh, om = mw.ustring.match( str, pattern)
sign, oh, om = sign or "+", oh or "00", om or "00"
return tonumber(sign .. oh), tonumber(sign .. om)
end
function p.parseISO8601(str)
if 'table'==type(str) then
if str.args and str.args[1] then
str = '' .. str.args[1]
else
return 'unknown argument type: ' .. type( str ) .. ': ' .. table.tostring( str )
end
end
local Y,M,D = parseISO8601Date(str)
local h,m,s = parseISO8601Time(str)
local oh,om = parseISO8601Offset(str)
return tonumber(os.time({year=Y, month=M, day=D, hour=(h+oh), min=(m+om), sec=s}))
end
local g2uBoundary1 = p.parseISO8601('1582-10-15T00:00:00Z')
local g2uBoundary2 = p.parseISO8601('1700-03-12T00:00:00Z')
local g2uBoundary3 = p.parseISO8601('1800-03-13T00:00:00Z')
local g2uBoundary4 = p.parseISO8601('1900-03-14T00:00:00Z')
local g2uBoundary5 = p.parseISO8601('1918-01-26T00:00:00Z') -- декрет Ленина
-- Передаваемое время обязано быть по Григорианскому календарю (новому стилю)
function p.formatWiki( time, infocardClass, categoryNamePrefix )
if 'table'==type( time ) then
if time.args and time.args[1] then
time = tonumber( time.args[1] )
else
return 'unknown argument type: ' .. type( time ) .. ': ' .. table.tostring( time )
end
end
local t = os.date("*t", time)
if time < g2uBoundary1 then
-- выводим просто юлианский календарь. Задавать тут григорианский некорректно
return p.formatWikiImpl( t, t, infocardClass, categoryNamePrefix )
end
-- Специальные даты
if t.year == 1700 and t.month == 3 and t.day == 11 then
return p.formatWikiImpl( {year=1700, month=2, day=29}, t, infocardClass, categoryNamePrefix)
end
if t.year == 1800 and t.month == 3 and t.day == 12 then
return p.formatWikiImpl( {year=1800, month=2, day=29}, t, infocardClass, categoryNamePrefix )
end
if t.year == 1900 and t.month == 3 and t.day == 13 then
return p.formatWikiImpl( {year=1900, month=2, day=29}, t, infocardClass, categoryNamePrefix )
end
if g2uBoundary1 <= time and time < g2uBoundary2 then
return p.formatWikiImpl( os.date("*t", time - 10 * 24 * 60 * 60), t, infocardClass, categoryNamePrefix )
end
if g2uBoundary2 <= time and time < g2uBoundary3 then
return p.formatWikiImpl( os.date("*t", time - 11 * 24 * 60 * 60), t, infocardClass, categoryNamePrefix )
end
if g2uBoundary3 <= time and time < g2uBoundary4 then
return p.formatWikiImpl( os.date("*t", time - 12 * 24 * 60 * 60), t, infocardClass, categoryNamePrefix )
end
if g2uBoundary4 <= time and time < g2uBoundary5 then
return p.formatWikiImpl( os.date("*t", time - 13 * 24 * 60 * 60), t, infocardClass, categoryNamePrefix )
end
--только Григорианский календарь
return p.formatWikiImpl( t, t, infocardClass, categoryNamePrefix )
end
function ternary ( cond , T , F )
if cond then return T else return F end
end
local nominativeMonthes = {'январь', 'февраль', 'март', 'апрель', 'май', 'июнь',
'июль', 'август', 'сентябрь', 'октябрь', 'ноябрь', 'декабрь'}
local genitivusMonthes = {'январьның', 'февральдың', 'марттың', 'апрельдиң', 'майның', 'июньнуң',
'июльдуң', 'августтуң', 'сентябрьның', 'октябрьның', 'ноябрьның', 'декабрьның'}
function nominativeYear( year )
if ( year >= 0 ) then
return '[[' .. year .. ' чыл|' .. year .. ']]'
else
return '[[' .. ( 0 - year ) .. ' чыл б. э. ч.|' .. ( 0 - year ) .. ' б. э. ч.]]'
end
end
function inYear( year )
if ( year >= 0 ) then
return '' .. year .. ' чылда'
else
return '' .. 'б.э. чедир ' .. ( 0 - year) .. ' чылда'
end
end
function p.formatWikiImpl( t1, t2, infocardClass, categoryNamePrefix )
local nd = t2.day;
local nm = t2.month;
local ny = t2.year;
local od = ternary ( t1.day ~= t2.day , t1.day, nil );
local om = ternary ( t1.month ~= t2.month , t1.month, nil );
local oy = ternary ( t1.year ~= t2.year , t1.year, nil );
local JulianComment = function(s)
return tostring(mw.html.create("abbr")
:attr("title","по юлианскому календарю")
:wikitext(s)
:done())
end
local template =
(nd ~= nil and "1" or "") .. (nm ~= nil and "2" or "") .. (ny ~= nil and "3" or "") ..
(od ~= nil and "4" or "") .. (om ~= nil and "5" or "") .. (oy ~= nil and "6" or "")
local datePart = '<span style="white-space:nowrap;">'
if (template == "12") then
datePart = datePart .. string.format( "[[%s %d]]",
nd, genitivusMonthes[nm] )
elseif (template == "23") then
datePart = datePart .. string.format( "%s [[%s]]",
nominativeYear( ny ), nominativeMonthes[nm] )
elseif (template == "3") then
datePart = datePart .. nominativeYear( ny )
elseif (template == "123") then
datePart = datePart .. string.format( "%s [[%s %d]]",
nominativeYear( ny ), genitivusMonthes[nm], nd )
elseif (template == "124") then
datePart = datePart .. JulianComment(string.format( "%d", od )
).. string.format( " [[%s %d|%s (%d)]]",
genitivusMonthes[nm], nd, genitivusMonthes[nm], nd )
elseif (template == "1234") then
datePart = datePart .. JulianComment(string.format( "%d", od )
).. string.format( " [[%d %s|(%d) %s]] %s",
nd, genitivusMonthes[nm], nd, genitivusMonthes[nm], nominativeYear( ny ) )
elseif (template == "1245") then
datePart = datePart .. JulianComment(string.format( "%d %s", od, genitivusMonthes[om] )
).. string.format(" ([[%d %s]])", nd, genitivusMonthes[nm] )
elseif (template == "12345") then
datePart = datePart .. JulianComment(string.format( "%d %s", od, genitivusMonthes[om] )
).. string.format(" ([[%d %s]]) %s", nd, genitivusMonthes[nm], nominativeYear( ny ) )
elseif (template == "123456") then
datePart = datePart .. JulianComment(string.format( "%d %s %d", od, genitivusMonthes[om], oy ))
.. '</span> <span style="white-space:nowrap;">'
.. string.format(' ([[%d %s]] %s)', nd, genitivusMonthes[nm], nominativeYear( ny ) )
else
datePart = datePart .. 'формат неверен'
end
datePart = datePart .. '</span>'
local infocardTemplate =
(nd ~= nil and "1" or "") .. (nm ~= nil and "2" or "") .. (ny ~= nil and "3" or "")
if infocardClass then
if (infocardTemplate == "123") then
datePart = datePart .. string.format('<span style="display:none">(<span class="%s">%04d-%02d-%02d</span>)</span>', infocardClass , ny , nm , nd )
elseif (infocardTemplate == "23") then
datePart = datePart .. string.format('<span style="display:none">(<span class="%s">%04d-%02d</span>)</span>', infocardClass , ny , nm )
elseif (infocardTemplate == "3") then
datePart = datePart .. string.format('<span style="display:none;">(<span class="%s">%04d</span>)</span>', infocardClass , ny )
end
end
if categoryNamePrefix then
if ( nd ~= nil and nm ~= nil) then
datePart = datePart .. '[[Аңгылал:' .. genitivusMonthes[nm] .. ' ' .. nd .. ' ' .. categoryNamePrefix .. ']]'
end
if ( ny ~= nil) then
datePart = datePart .. '[[Аңгылал:' .. inYear( ny ) .. ' ' .. categoryNamePrefix .. ']]'
end
end
return datePart
end
return p