Module:Events calendar
File:.svg | {{Module rating}} "" is invalid or not specified. |
Usage
edit{{#invoke:Events calendar|function_name}}
local p = {}
local lang = nil
local function getDaysInMonth(month, year)
local days_in_month = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
local d = days_in_month[month]
-- check for leap year
if month == 2 then
if math.mod(year,4) == 0 then
if math.mod(year,100) == 0 then
if math.mod(year,400) == 0 then
d = 29
end
else
d = 29
end
end
end
return d
end
local function getFirstTimestamp(month, year, extended)
first_timestamp = os.time({ year = year, month = month, day = 1, hour = 0, minut = 0 })
if not extended then
return first_timestamp
end
first_day = (os.date("*t", first_timestamp).wday - 2)%7
return first_timestamp - (first_day * 86400)
end
local function getLastTimestamp(month, year, extended)
last_timestamp = os.time({ year = year, month = month, day = getDaysInMonth(tonumber(month), tonumber(year)), hour = 23, minut = 59 })
if not extended then
return last_timestamp
end
last_day = (os.date("*t", last_timestamp).wday - 2)%7
return last_timestamp + ((6-last_day) * 86400)
end
local function getFirstDayTimestamp(year, month, day)
return os.time({ year = year, month = month, day = day, hour = 0 })
end
local function getLastDayTimestamp(year, month, day)
return os.time({ year = year, month = month, day = day, hour = 23, min = 59, sec = 59 })
end
local function rruleToInterval(rrule)
local days = 10000
if rrule.freq == 'weekly' then
days = 7
end
local interval = rrule.interval or 1
return days * interval * 86400
end
local function currentDatesByRrule(dtstart, dtend, rrule, t1)
local dtuntil = rrule['until'] or 2000000000 -- ~2033
if dtuntil < t1 then
return dtstart, dtend
end
local interval = rruleToInterval(rrule)
local duration = dtend - dtstart
while dtstart < t1 and dtstart + interval <= dtuntil do
dtstart = dtstart + interval
end
dtend = dtstart + duration
return dtstart, dtend
end
local function dateFilter(data, t1, t2)
filtered_data = {}
for _,value in pairs(data) do
local dtstart = value.dtstart
local dtend = value.dtend
if value.rrule then
dtstart, dtend = currentDatesByRrule(dtstart, dtend, value.rrule, t1)
end
if (dtstart >= t1 and dtstart <= t2)
or (dtend >= t1 and dtend <= t2)
or (dtstart < t1 and dtend > t2)
then
if dtstart < t1 then
dtstart = t1
end
value.dtstart = dtstart
value.dtend = dtend
table.insert(filtered_data, value)
end
end
return filtered_data
end
local function tagFilter(data, searched_tags)
if #searched_tags == 0 then
return data
end
filtered_data = {}
for _,value in pairs(data) do
if value.tags ~= nil then
for _,tag in pairs(value.tags) do
local break_out = false
for _,searched_tag in pairs(searched_tags) do
if tag == searched_tag then
table.insert(filtered_data, value)
break_out = true
break
end
end
if break_out then break end
end
end
end
return filtered_data
end
local function locationFilter(data, searched_locations)
if #searched_locations == 0 then
return data
end
filtered_data = {}
for _,value in pairs(data) do
for _,location in pairs(value.location) do
local break_out = false
for _,searched_location in pairs(searched_locations) do
if location == searched_location then
table.insert(filtered_data, value)
break_out = true
break
end
end
if break_out then break end
end
end
return filtered_data
end
local function geolocFilter(data)
filtered_data = {}
for _,value in pairs(data) do
if value.geoloc.lat ~= nil and value.geoloc.lng ~= nil then
table.insert(filtered_data, value)
end
end
return filtered_data
end
local function formatEvents(data, short)
local content = ""
for _,event in pairs(data) do
if content ~= "" then
content = content .. "<br>"
end
local title = event.title
if event.link ~= nil and event.link ~= "" then
if mw.ustring.find(event.link, 'https?://') == 1 then
title = "[" .. event.link .. " " .. event.title .. "]"
else
title = "[[" .. event.link .. "|" .. event.title .. "]]"
end
end
local langs = ''
if event.langs ~= nil and #event.langs then
for _, lang in ipairs( event.langs ) do
langs = langs .. '<span style="display:inline-block; border:1px solid #72777d; border-radius:2px; padding:1px 2px; font-variant:small-caps; line-height:1em">' .. lang .. '</span> '
end
end
local date = os.date("*t", event.dtstart)
content = content .. "'''" .. event.location[#(event.location)] .. " " .. string.format("%02d:%02d", date.hour, date.min) .. "''' " .. langs .. title .. " <span style='display: none;' class='ec-pencil' id='" .. mw.text.split(event.id, '@', true)[1] .. "'>[[File:Blue pencil.svg|12px|link=]]</span>"
if not short then
content = content .. "<br>" .. event.description
end
end
return content
end
local function arrayToText(array)
if #array == 0 then
return ""
end
local text = array[1]
for i = 2, #array do
text = array[i] .. "," .. text
end
return text
end
function p.ical(frame, year, month, locations, tags)
local args = frame:getParent().args
local today = os.date("*t")
local timestamp = getFirstTimestamp(month, year, false)
local last_timestamp = timestamp + 300000000
local data = mw.text.jsonDecode(frame:expandTemplate{ title = ':Events calendar/events.json', args = {} })
data = dateFilter(data, timestamp, last_timestamp)
data = tagFilter(data, tags)
data = locationFilter(data, locations)
local content = "BEGIN:VCALENDAR"
.. "\r\nVERSION:2.0"
.. "\r\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN"
.. "\r\nX-WR-CALNAME:" .. ( args.title == nil and "Wikimedia events" or args.title )
.. "\r\nREFRESH-INTERVAL;VALUE=DURATION:P1H"
.. "\r\nX-PUBLISHED-TTL:PT1H"
for _,value in pairs(data) do
content = content .. "\r\nBEGIN:VEVENT"
.. "\r\nCREATED:" .. os.date("%Y%m%dT%H%M%SZ", value.dtcreated)
.. "\r\nLAST-MODIFIED:" .. os.date("%Y%m%dT%H%M%SZ", value.dtmodified)
.. "\r\nDTSTAMP:" .. os.date("%Y%m%dT%H%M%SZ", value.dtmodified)
.. "\r\nUID:" .. value.id
.. "\r\nSUMMARY:" .. value.title
if value.description ~= nil then
content = content .. "\r\nDESCRIPTION:" .. value.description:gsub("\n", ", ")
end
content = content .. " " .. mw.text.encode(value.link:gsub(" ", "_"))
if value.tags ~= nil then
content = content .. "\r\nCATEGORIES:" .. arrayToText(value.tags)
end
if value.location ~= nil then
content = content .. "\r\nLOCATION:" .. arrayToText(value.location)
end
if value.link ~= nil then
if mw.ustring.find(value.link, 'https?://') == 1 then
content = content .. "\r\nURL:" .. mw.text.encode(value.link:gsub(" ", "_"))
else
content = content .. "\r\nURL:https://meta.wikimedia.org/wiki/" .. mw.text.encode(value.link:gsub(" ", "_"))
end
end
if value.rrule ~= nil then
local rrule = {}
for key, value in pairs( value.rrule ) do
if key == 'until' then
value = os.date( "%Y%m%dT%H%M%S", value )
end
table.insert( rrule, string.upper( key ) .. '=' .. string.upper( value ) )
end
content = content .. "\r\nRRULE:" .. table.concat( rrule, ';' )
end
content = content .. "\r\nDTSTART:" .. os.date("%Y%m%dT%H%M%S", value.dtstart)
.. "\r\nDTEND:" .. os.date("%Y%m%dT%H%M%S", value.dtend)
.. "\r\nEND:VEVENT"
end
content = content .. "\r\nEND:VCALENDAR"
return content
end
function p.grid(frame, year, month, locations, tags)
local args = frame:getParent().args
local today = os.date("*t")
local timestamp = getFirstTimestamp(month, year, true)
local last_timestamp = getLastTimestamp(month, year, true)
local data = mw.text.jsonDecode(frame:expandTemplate{ title = ':Events calendar/events.json', args = {} })
data = dateFilter(data, timestamp, last_timestamp)
data = tagFilter(data, tags)
data = locationFilter(data, locations)
local grid_content = ""
while timestamp < last_timestamp do
local line_content = ""
for i=1,7 do
local current_date = os.date("*t", timestamp)
local events = formatEvents( dateFilter(data, getFirstDayTimestamp(current_date.year, current_date.month, current_date.day), getLastDayTimestamp(current_date.year, current_date.month, current_date.day)), true )
local is_today = current_date.month == today.month and current_date.day == today.day and "yes" or ""
local is_grey = current_date.month ~= tonumber(month) and "yes" or ""
line_content = line_content .. frame:expandTemplate{ title = 'Events calendar/Table-cell grid', args = { day = current_date.day, event = events, grey = is_grey, today = is_today } }
timestamp = timestamp + 86400
end
grid_content = grid_content .. frame:expandTemplate{ title = 'Events calendar/Table-line grid', args = {cells = line_content} }
end
return frame:expandTemplate{ title = 'Events calendar/Table grid', args = {lines = grid_content, monday=lang:formatDate( "l", "20010101000000", false), tuesday=lang:formatDate( "l", "20010102000000", false), wednesday=lang:formatDate( "l", "20010103000000", false), thursday=lang:formatDate( "l", "20010104000000", false), friday=lang:formatDate( "l", "20010105000000", false), saturday=lang:formatDate( "l", "20010106000000", false), sunday=lang:formatDate( "l", "20010107000000", false)} }
end
function p.list(frame, year, month, locations, tags)
local args = frame:getParent().args
local today = os.date("*t")
local timestamp = getFirstTimestamp(month, year, false)
local last_timestamp = getLastTimestamp(month, year, false)
local data = mw.text.jsonDecode(frame:expandTemplate{ title = ':Events calendar/events.json', args = {} })
data = dateFilter(data, timestamp, last_timestamp)
data = tagFilter(data, tags)
data = locationFilter(data, locations)
local list_content = ""
while timestamp < last_timestamp do
local current_date = os.date("*t", timestamp)
local events = dateFilter(data, getFirstDayTimestamp(current_date.year, current_date.month, current_date.day), getLastDayTimestamp(current_date.year, current_date.month, current_date.day))
if #events ~= 0 then
local is_today = current_date.month == today.month and current_date.day == today.day and "yes" or ""
list_content = list_content .. frame:expandTemplate{ title = 'Events calendar/Table-line list', args = { day = lang:formatDate( "l d", os.date("%Y%m%d000000", timestamp), false), events = formatEvents(events, false), today = is_today } }
end
timestamp = timestamp + 86400
end
return frame:expandTemplate{ title = 'Events calendar/Table list', args = {lines = list_content} }
end
function p.map(frame, year, month, locations, tags)
local args = frame:getParent().args
local today = os.date("*t")
local data = mw.text.jsonDecode(frame:expandTemplate{ title = ':Events calendar/events.json', args = {} })
data = dateFilter(data, getFirstTimestamp(month, year, false), getLastTimestamp(month, year, false))
data = tagFilter(data, tags)
data = locationFilter(data, locations)
data = geolocFilter(data)
if #data == 0 then
return frame:extensionTag("mapframe", "", {width = "980", height = "500", align = "center", frameless = "", zoom = 2, latitude = 32 , longitude = 5 })
end
local geojson = {
type = "FeatureCollection",
features = {}
}
local first_timestamp_of_today = getFirstDayTimestamp(today.year, today.month, today.day)
local last_timestamp_of_today = getLastDayTimestamp(today.year, today.month, today.day)
for _,value in pairs(data) do
local title = value.title
if value.link ~= nil and value.link ~= "" then
if mw.ustring.find(value.link, 'https?://') == 1 then
title = "[[" .. value.link .. "|" .. value.title .. "]]"
else
title = "[[" .. value.link .. "|" .. value.title .. "]]"
end
end
local properties = {
title = title,
description = "<small>" .. lang:formatDate( "l d F - H:i", os.date("%Y%m%d%H%M00", value.dtstart), false) .. "</small><br/>" .. value.description,
}
properties["marker-color"] = "#e23131"
if value.dtend < first_timestamp_of_today then
properties["marker-color"] = "#555555"
elseif value.dtstart > last_timestamp_of_today then
properties["marker-color"] = "#6d9ce8"
end
table.insert(geojson.features, {
type = "Feature",
properties = properties,
geometry = {
type = "Point",
coordinates = {
tonumber(value.geoloc.lng),
tonumber(value.geoloc.lat),
}
}
})
end
return frame:extensionTag("mapframe", mw.text.jsonEncode(geojson), {width = "980", height = "500", align = "center", frameless = ""})
end
function p.calendar(frame)
local args = frame:getParent().args
local display = args.display
local lang_code = args.lang or frame:preprocess( '{{PAGELANGUAGE}}' )
local year = args.year or os.date("%Y")
local month = args.month or os.date("%m")
local locations = (args.locations and args.locations ~= "") and mw.text.split( args.locations, ",", true ) or {}
local tags = (args.tags and args.tags ~= "") and mw.text.split( args.tags, ",", true ) or {}
lang = mw.language.new( lang_code )
local content = ""
if display == "ical" then
return p.ical(frame, year, month, locations, tags)
elseif display == "list" then
content = p.list(frame, year, month, locations, tags)
elseif display == "map" then
content = p.map(frame, year, month, locations, tags)
else
display = "grid"
content = p.grid(frame, year, month, locations, tags)
end
local container_args = {}
container_args["class"] = "events-calendar"
container_args["data-display"] = display
container_args["data-lang"] = lang_code
container_args["data-year"] = year
container_args["data-month"] = month
container_args["data-locations"] = #locations == 0 and "" or args.locations
container_args["data-tags"] = #tags == 0 and "" or args.tags
return frame:extensionTag('div', frame:expandTemplate{ title = 'Events calendar/Header', args = {month = lang:formatDate( "F", year .. string.format("%02d", tonumber(month)) .. "01000000", false ), year = year} } .. content .. frame:expandTemplate{ title = 'Events calendar/Button', args = {} }, container_args)
end
return p