Module:Calendar

From Angelina Jordan Wiki

Documentation for this module may be created at Module:Calendar/doc

-- Float control:
--    Default: float:right; clear:right; margin-left:0.2em;
---   Add nofloat=1 → removes float entirely.
---   Add float=left or other value to choose side.
--- Example: {{#invoke:Calendar|month |year=2025 |month=9 |float=left }}

local p = {}

local function trim(s)
	if not s then return nil end
	return (tostring(s):gsub("^%s*(.-)%s*$", "%1"))
end

local function escAttr(s)
	if not s then return "" end
	return tostring(s):gsub("&","&amp;"):gsub('"',"&quot;"):gsub("<","&lt;"):gsub(">","&gt;")
end

local function extractDay(dateStr)
	if not dateStr then return nil end
	local y, m, d = tostring(dateStr):match("^(%d%d%d%d)%-(%d%d)%-(%d%d)")
	if d then return tonumber(d) end
	return nil
end

local monthNames = {
	"January", "February", "March", "April", "May", "June",
	"July", "August", "September", "October", "November", "December"
}

function p.month(frame)
	local args = frame.args or (frame.getParent and frame:getParent().args) or {}
	local year = tonumber(trim(args.year))
	local month = tonumber(trim(args.month))
	if not year or not month then
		return "Error: module requires numeric |year= and |month=."
	end
	if month < 1 or month > 12 then
		return "Error: |month must be between 1 and 12."
	end

	-- defaults from Cargo table definition
	local tableName = trim(args.table) or "Dates"
	local dateField = trim(args.datefield) or "date"
	local typeField = trim(args.typefield) or "type"
	local linkField = trim(args.linkfield) or "link"
	local pageField = trim(args.pagefield) or "page"

	local filterType = trim(args.type) or "da"

	local daysInMonth = tonumber(os.date("*t", os.time{year=year, month=month+1, day=0}).day)
	local firstWday   = tonumber(os.date("*t", os.time{year=year, month=month, day=1}).wday)

	local nofloat = args.nofloat and trim(args.nofloat) ~= ""
	local floatDir = trim(args.float) or "right"
	local style
	if nofloat then
		style = "font-size:80%; text-align:center;"
	else
		style = string.format("font-size:80%%; text-align:center; float:%s; clear:right; margin-left:0.2em;", floatDir)
	end

	local out = {}
	local events = {}

	-- Cargo query
	if mw and mw.ext and mw.ext.cargo and mw.ext.cargo.query then
		local startDate = string.format("%04d-%02d-01", year, month)
		local endDate   = string.format("%04d-%02d-%02d", year, month, daysInMonth)
		local where = string.format("%s >= '%s' AND %s <= '%s' AND %s = '%s'",
			dateField, startDate, dateField, endDate, typeField, filterType)
		local fields = string.format("%s,%s,%s,%s", dateField, typeField, linkField, pageField)
		local ok, results = pcall(mw.ext.cargo.query, tableName, fields, { where = where, limit = 1000 })
		if ok and results then
			for _, row in ipairs(results) do
				local dateVal = row[dateField]
				local linkVal = row[linkField] or row[pageField]
				local di = extractDay(dateVal)
				if di and linkVal then
					events[di] = events[di] or {}
					table.insert(events[di], tostring(linkVal))
				end
			end
		end
	end

	-- Build table
	table.insert(out, string.format('{| class="wikitable" style="%s"', style))
	table.insert(out, '|-')
	table.insert(out, string.format('! colspan="7" | %s %d', monthNames[month], year))
	table.insert(out, '|-')
	table.insert(out, '!Su !!Mo !!Tu !!We !!Th !!Fr !!Sa')

	local day = 1
	while day <= daysInMonth do
		local cells = {}
		for col = 1, 7 do
			local cellContent, cell
			if day == 1 and col < firstWday then
				cellContent = " "
			elseif day <= daysInMonth then
				if events[day] and #events[day] > 0 then
					local firstLink = events[day][1]
					cellContent = string.format('[[%s|\'\'\'%d\'\'\']]', firstLink, day)
				else
					cellContent = tostring(day)
				end
				day = day + 1
			else
				cellContent = " "
			end

			-- weekend background (Sunday=1, Saturday=7)
			if col == 1 or col == 7 then
				cell = string.format('style="background-color:#eee;" | %s', cellContent)
			else
				cell = cellContent
			end
			table.insert(cells, cell)
		end
		table.insert(out, "|-")
		table.insert(out, "| " .. table.concat(cells, " || "))
	end

	table.insert(out, "|}")
	return table.concat(out, "\n")
end

return p