Модуль:Dates

Для документации этого модуля может быть создана страница Модуль:Dates/doc

--[[ В это модуле собраны функции, связанные с работой с датами.]]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    endendlocal 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 )endlocal function Year0(y,t)-- аналог Год0    if y>0 then return table.concat{        '[[', tostring(y), ' и|', t and tostring(y)..'&nbsp;'..t or tostring(y), ']]'    } else return table.concat{        '[[', tostring(-y), ' и до н. э.|',         t and tostring(-y)..'&nbsp;'..t or tostring(-y),        '&nbsp;до&nbsp;н.&nbsp;э.]]'    }    endendlocal 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,'&nbsp;',monthg[om],'&nbsp;',oy,                            '</span> <span style="white-space:nowrap;">([[',                            j, ' ', monthg[m],']] ',Year0(y),')'                        } or {-- ДД ММММ ([[ДД ММММ]]) [[ГГГГ]]                            oj,'&nbsp;',monthg[om],' ([[',j,'&nbsp;',monthg[m],']]) ',Year0(y)                        }                    ) or {-- ДД [[ДД ММММ|(ДД) ММММ]] [[ГГГГ]]                        oj,'&nbsp;[[',j,' ',monthg[m],'|','(',j,')&nbsp;',monthg[m],']] ',Year0(y)                    }                ) or {'[[',j,'&nbsp;',monthg[m],']]&nbsp;',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,'&nbsp;',monthg[om],' ([[',j,'&nbsp;',monthg[m],']])</span>'                    } or {-- ДД [[ДД ММММ|(ДД) ММММ]]                        oj,'&nbsp;[[',j,' ',monthg[m],'|','(',j,')&nbsp;',monthg[m],']]</span>'                    }                ) or {'[[',j,'&nbsp;',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 "''формат неверен''"    endendlocal 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)endlocal 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    )endlocal function Yyyymmdd(r)--Переводит русскую дату в YYYY,MM,DD    local d,m,y,M=mw.ustring.match(r, "^%s*(%d%d?)%s+([а-яА-Я]+)%s+(%d+)")    if not m then return nil end    m=mw.ustring.lower(m)    for i=1,12 do if m==monthg[i] then M=i;break end end--тупо перебор    if not M then return nil end    return tonumber(y),M,tonumber(d)endlocal 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;--realmonthpersdate=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('[[Category:%s %i %s]]',catpref[mo],j,monthg[m]) or '',         y and string.format('[[Category:%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'])    endend;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    endend;-- Переводит русскую дату в YYYY-MM-DD. Возвращает входное значение, если дата уже в этом формате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)    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 )  endendfunction 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 ) .. "]"  endendfunction 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, "," ) .. "}"endfunction parseISO8601Date(str)local pattern = "(%-?%d+)%-(%d+)%-(%d+)T"local Y, M, D = mw.ustring.match( str, pattern )return tonumber(Y), tonumber(M), tonumber(D)endfunction 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)endfunction 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)endfunction p.parseISO8601(str)if 'table'==type(str) thenif str.args and str.args[1] thenstr = '' .. str.args[1]elsereturn 'unknown argument type: ' .. type( str ) .. ': ' .. table.tostring( str )endendlocal 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}))endlocal 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 ) thenif time.args and time.args[1] thentime = tonumber( time.args[1] )elsereturn 'unknown argument type: ' .. type( time ) .. ': ' .. table.tostring( time )endendlocal 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 thenreturn p.formatWikiImpl( {year=1700, month=2, day=29}, t, infocardClass, categoryNamePrefix)endif t.year == 1800 and t.month == 3 and t.day == 12 thenreturn p.formatWikiImpl( {year=1800, month=2, day=29}, t, infocardClass, categoryNamePrefix )endif t.year == 1900 and t.month == 3 and t.day == 13 thenreturn p.formatWikiImpl( {year=1900, month=2, day=29}, t, infocardClass, categoryNamePrefix )endif g2uBoundary1 <= time and time < g2uBoundary2 thenreturn p.formatWikiImpl( os.date("*t", time - 10 * 24 * 60 * 60), t, infocardClass, categoryNamePrefix )endif g2uBoundary2 <= time and time < g2uBoundary3 thenreturn p.formatWikiImpl( os.date("*t", time - 11 * 24 * 60 * 60), t, infocardClass, categoryNamePrefix )endif g2uBoundary3 <= time and time < g2uBoundary4 thenreturn p.formatWikiImpl( os.date("*t", time - 12 * 24 * 60 * 60), t, infocardClass, categoryNamePrefix )endif g2uBoundary4 <= time and time < g2uBoundary5 thenreturn p.formatWikiImpl( os.date("*t", time - 13 * 24 * 60 * 60), t, infocardClass, categoryNamePrefix )end--только Григорианский календарьreturn p.formatWikiImpl( t, t, infocardClass, categoryNamePrefix )endfunction ternary ( cond , T , F )    if cond then return T else return F endendlocal nominativeMonthes = {'январь', 'февраль', 'март', 'апрель', 'май', 'июнь',    'июль', 'август', 'сентябрь', 'октябрь', 'ноябрь', 'декабрь'}local genitivusMonthes = {'январьын', 'февральын', 'мартын', 'апрельын', 'майын', 'июньын',    'июльын', 'августын', 'сентябрьын', 'октябрьын', 'ноябрьын', 'декабрьын'}function nominativeYear( year )    if ( year >= 0 ) then        return '[[' .. year .. ' и|' .. year .. ' и]]'    else        return '[[' .. ( 0 - year ) .. ' и Х.ш.я.|' .. ( 0 - year ) .. ' и Х.ш.я.]]'    endendfunction genitivusYear( year )    if ( year >= 0 ) then        return '[[' .. year .. ' и|' .. year .. ' ин]]'    else        return '[[' .. ( 0 - year ) .. ' и Х.ш.я.|' .. ( 0 - year ) .. ' ин Х.ш.я.]]'    endendfunction inYear( year )    if ( year >= 0 ) then        return '' .. year .. ' ин'    else        return '' .. ( 0 - year) .. ' ин Х.ш.я.'    endendfunction 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( "[[%d %s]]",        nd, genitivusMonthes[nm] )    elseif (template == "23") then        datePart = datePart .. string.format( "%s %s",        nominativeMonthes[nm], nominativeYear( ny ) )    elseif (template == "3") then        datePart = datePart .. nominativeYear( ny )    elseif (template == "123") then        datePart = datePart .. string.format( " %s [[%d %s]]",                                        genitivusYear( ny ), nd, genitivusMonthes[nm])    elseif (template == "124") then        datePart = datePart .. JulianComment(string.format( "%d", od )).. string.format( " [[%d %s|(%d) %s]]",                                        nd, nominativeMonthes[nm], nd, nominativeMonthes[nm] )    elseif (template == "1234") then        datePart = datePart .. JulianComment(string.format( "%d", od )).. string.format( " [[%d %s|(%d) %s]] %s",                                        nd, nominativeMonthes[nm], nd, nominativeMonthes[nm], nominativeYear( ny ) )    elseif (template == "1245") then        datePart = datePart .. JulianComment(string.format( "%d %s", od, nominativeMonthes[om] )).. string.format(" ([[%d %s]])", nd, nominativeMonthes[nm] )    elseif (template == "12345") then        datePart = datePart .. JulianComment(string.format( "%d %s", od, nominativeMonthes[om] )).. string.format(" ([[%d %s]]) %s", nd, nominativeMonthes[nm], nominativeYear( ny ) )    elseif (template == "123456") then        datePart = datePart .. JulianComment(string.format( "%d %d %s", oy, od, genitivusMonthes[om] )).. '</span> <span style="white-space:nowrap;">'.. string.format(' %s ([[%d %s]])', genitivusYear( ny ), nd, genitivusMonthes[nm] )    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 )    endend     if categoryNamePrefix then        if ( nd ~= nil and nm ~= nil) then            datePart = datePart .. '[[Category:' .. nd .. '-шӹ ' .. genitivusMonthes[nm] .. ' ' .. categoryNamePrefix .. ']]'        end        if ( ny ~= nil) then            datePart = datePart .. '[[Category:' .. inYear( ny ) ..  ' ' .. categoryNamePrefix .. ']]'        end    end     return datePartendreturn p