Module:Events calendar

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 dateFilter(data, t1, t2) filtered_data = {} for _,value in pairs(data) do	   if (value.dtstart >= t1 and value.dtstart <= t2) or (value.dtend >= t1 and value.dtend <= t2) or (value.dtstart < t1 and value.dtend > t2) then 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		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 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 .. " "		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.title .. "" end end local date = os.date("*t", event.dtstart) content = content .. "'''" .. event.location[#(event.location)] .. " " .. string.format("%02d:%02d", date.hour, date.min) .. "''' " .. title .. "  "		if not short then content = content .. " " .. 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 = text .. "," .. array[i] 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 ) 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 end 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) else content = content .. "\r\nURL:https://meta.wikimedia.org/wiki/" .. mw.text.encode(value.link) end end content = content .. "\r\nDTSTART:" .. os.date("%Y%m%dT%H%M%SZ", value.dtstart) .. "\r\nDTEND:" .. os.date("%Y%m%dT%H%M%SZ", 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.title .. "" else title = "" .. value.title .. "" end end local properties = { title = title, description = " " .. lang:formatDate( "l d F - H:i", os.date("%Y%m%d%H%M00", value.dtstart), false) .. " " .. 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 == nil and "en" or args.lang local year = args.year == nil and os.date("%Y") or args.year local month = args.month == nil and os.date("%m") or args.month local locations = (args.locations == nil or args.locations == "") and {} or mw.text.split( args.locations, ",", true ) local tags = (args.tags == nil or args.tags == "") and {} or mw.text.split( args.tags, ",", true ) 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