Module:Calendar widget
Appearance
{{Module rating }}
Usage
{{#invoke:Calendar widget|calendar}}
Lua error in package.lua at line 80: module 'Module:No globals' not found.
Parameters
Calendar parameters
|year=
– specifies the year to be used when creating a monthly or yearly calendar; Gregorian calendar only; minimum 1583 for yearly calendar; minimum October 1582 for stand-alone month calendar; when omitted or out of range, uses current year|month=
– specifies the month to be used when creating an stand-alone month calendar in the year specified by|year=
; accepts a variety values:- numbers
1
to12
– defaults to current month when month number is out of range - month names (
January
,March
, etc) – defaults to current month when month name is not recognized - keywords:
current
– display the current monthlast
– display the month that occurs before the current monthnext
– display the month that occurs after the current month
- numbers
|cols=
– yearly calendars only; number of columnsn
to be used for calendar rendering; default is4
; values ofn
less than 1 or greater than 12 ignored|iso=
– accepts the single valueyes
; calendar renders in ISO week format (Monday through Sunday); not needed if|iso_wk=
set|iso_wk=
– accepts the single valueyes
; calendar renders in ISO week format (Monday through Sunday) with ISO week number in the left column; setting|iso_wk=yes
automatically sets|iso=yes
Styling parameters
|float=
– position the rendered calendar; default position is at the left page margin:center
– middle of the pageright
– at the right page margin
|hide_year=
– accepts the single valueyes
; suppresses display of year in calendar headers; alias|show_year=off
|show_today=
– accepts the single valueyes
; highlights the current date in the current-month calendar|today_color=
– set the highlight color used by|show-today=
; alias:|today_colour=
|title_color=
– set background color for the month title bar; overrides|color=
; alias:|title_colour=
|week_color=
– set background color for the day-abbreviations title bar; overrides|color=
; alias:|week_colour=
|color=
– shorthand for both|title_color=
and|week_color=
;|color=
yields to|title_color=
and|week_color=
; alias:|colour=
|wknum_color=
– set background color for ISO week numbers; alias:|wknum_colour=
Linking parameters
|lk=
– various date component linking options:d
– link days in calendar to calendar day of calendar month –[[April 9]]
m
– link month in calendar header to month article –[[April]] 2019
y
– link year in calendar header to year article –April [[2025]]
dm
– link to days and monthdy
– link to days and yearmy
– link to month and yearyes
– individually link all date componentsm&y
– stand-alone month calendars only; link month and year together as a single composite link –[[April 2025]]
dm&y
– stand-alone month calendars only; link to days and composite month/year
Link prefixes and suffixes
These parameters require |lk=
:
|lk_pref=
– prefix for all day, month, and year links enabled by|lk=
; yields to specific|lk_pref_x=
parameters|lk_suff=
– suffix for all day, month, and year links enabled by|lk=
; yields to specific|lk_suff_x=
parameters
These parameters automatically set |lk=
to the appropriate value; override values assigned to |lk_pref=
and |lk_suff=
:
|lk_pref_d=
– prefix for day links|lk_pref_m=
– prefix for month and composite month/year links|lk_pref_y=
– prefix for year links|lk_suff_d=
– suffix for day links|lk_suff_m=
– suffix for month links and composite month/year links|lk_suff_y=
– suffix for year links
For stand-alone month calendars only, links to previous- and next-month targets:
|prevnext=
– accepts the single valueyes
; adds generic << and >> links to month header linked to the preceding and next month articles; automatically set if any of the following parameters are set:|lk_pref_mprev=
– prefix for previous-month link|lk_suff_mprev=
– suffix for previous-month link|lk_pref_mnext=
– prefix for next-month link|lk_suff_mnext=
– suffix for next-month link
Examples
if the current page and section is [[An example page#May]]
and there is a May calendar there, to offer links to the previous month (April) and next month (June) sections set:
|k_pref_mprev=#
– creates link to[[An example page#April]]
|k_pref_mnext=#
– creates link to[[An example page#June]]
if the current page is a subpage [[An example page/May]]
and there is a May calendar there, to offer links to the previous month and next month subpages set:
|k_pref_mprev=../
– creates link to[[An example page/April]]
|k_pref_mnext=../
– creates link to[[An example page/June]]
- in
../
,..
is the parent ([[An example page]]
) and/
is the required path separator; see Uniform Resource Identifier
- in
--[[
Module to create Calendar widget
--]]
require('Module:No globals');
local getArgs = require ('Module:Arguments').getArgs;
local lang_obj = mw.language.getContentLanguage();
local daysinmonth = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
local dayname = {'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'}
local dayabbr = {}
for i, v in ipairs(dayname) do
dayabbr[i] = v:sub(1, 2)
end
local iso_dayname = {'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'}
local iso_dayabbr = {}
for i, v in ipairs(iso_dayname) do
iso_dayabbr[i] = v:sub(1, 2)
end
local monthname = {}
local monthabbr = {}
if 0 == #monthname then
for m = 1, 12 do
monthname[m] = lang_obj:formatDate ("F", '2019-' .. m); -- table of long month names
monthabbr[m] = lang_obj:formatDate ("M", '2019-' .. m); -- table of abbreviated month names
end
end
local function is_leap(year)
return '1' == lang_obj:formatDate ('L', tostring(year));
end
--[[
returns 1 to 7; 1 == Sunday; 1 == Monday when iso true
TODO: error check inputs
--]]
local function day_of_week (year, month, day, iso)
return
iso and lang_obj:formatDate ('N', year .. '-' .. month .. '-' .. day) or -- ISO: 1 = monday
lang_obj:formatDate ('w', year .. '-' .. month .. '-' .. day) + 1; -- 1 = sunday
end
-- year and month both numeric:
local function monthstart(year, month, iso)
return day_of_week(year, month, 1, iso)
end
--[[--------------------------< I S _ S E T >------------------------------------------------------------------
Returns true if argument is set; false otherwise. Argument is 'set' when it exists (not nil) or when it is not an empty string.
]]
local function is_set( var )
return not (var == nil or var == '');
end
--[=[-------------------------< M A K E _ W I K I L I N K >----------------------------------------------------
Makes a wikilink; when both link and display text is provided, returns a wikilink in the form [[L|D]]; if only
link is provided, returns a wikilink in the form [[L]]; if neither are provided or link is omitted, returns an
empty string.
]=]
local function make_wikilink (link, display)
if is_set (link) then
if is_set (display) then
return table.concat ({'[[', link, '|', display, ']]'});
else
return table.concat ({'[[', link, ']]'});
end
else
return '';
end
end
--[[--------------------------< G E T _ D I S P L A Y _ Y E A R >----------------------------------------------
returns year from props with wikilink, prefix, suffix ...
TODO: this will be a lot similar to get_display_month(), get_display_day() so generalize this? maybe not...
]]
local function get_display_year (props)
local year_text = props.year;
if props.lk_y then
year_text = make_wikilink (props.year);
end
return year_text
end
--[[--------------------------< G E T _ D I S P L A Y _ M O N T H >--------------------------------------------
returns month from arguments or props with wikilink, prefix, suffix ...
]]
local function get_display_month (mnum, props)
local month_text = mnum or props.month_num;
month_text = monthname[month_text];
if props.lk_m then
month_text = make_wikilink (month_text);
end
return month_text;
end
--[[--------------------------< G E T _ D I S P L A Y _ D A Y >------------------------------------------------
returns day with wikilink (month and day), link prefix, link suffix ... (text doesn't get prefix / suffix)
]]
local function get_display_day (day_text, mnum, props)
if props.lk_d then
day_text = make_wikilink (monthname[mnum] .. ' ' .. day_text, day_text);
end
return day_text;
end
--[[--------------------------< R E P E A T _ T A G S >--------------------------------------------------------
create <tag> ... </tag>... string to be included into another tag as :wikitext(...)
items is a table of items, each of which will be wrapped in <tag>...</tag>
options is a table of optional class, css, and attribute settings for these tags
options.attr is a table of attribute / value pairs: {['attribute'] = 'value', ...}
options.css is a table of attribute / value pairs: {['attribute'] = 'value', ...}
]]
local function repeat_tags (tag, items, options)
local tags = {}; -- table of <tag>...</tag> tag strings
local opt_attr = options.attr or {}; -- if options not supplied, use empty table
local opt_css = options.css or {};
for i, item in ipairs (items) do
local repeat_tag = mw.html.create (tag); -- new td object
repeat_tag
:addClass (options.class)
:attr (opt_attr)
:css (opt_css)
:wikitext (item) -- get a calendar for month number mnum
:done() -- close <td>
table.insert (tags, tostring (repeat_tag)); -- make a string of this object
end
return table.concat (tags); -- concatenate them all together
end
--[[--------------------------< G E T _ R O W _ D A T E S >----------------------------------------------------
gets a week (row) of calendar dates each in its own <td>...</td>; inserts iso week number <td> tag ahead of column 1
when props.iso_wk true.
]]
local function get_row_dates (firstday, mnum, row, props)
local options = {['class']='mcal'}; -- table of otions for these td tags
local td_items = {}; -- table of <td>...</td> tag strings
local result = {};
local hilite;
for col = 1, 7 do
local dom = 7 * (row-1) + col + 1 - firstday -- calculate day of month for row/col position
if props.iso_wk and 1 == col then -- when column 1, insert iso week number <td> ahead of first 'dom'
local iso_wk = lang_obj:formatDate ('W', props.year .. '-' .. mnum .. '-' .. ((1 > dom) and 1 or dom));
table.insert (result, repeat_tags ('td', {iso_wk}, {['class']='mcal_iso'}));
end
if dom < 1 or dom > daysinmonth[mnum] then
dom = " " -- before or after month, blank cell
else
dom = get_display_day (dom, mnum, props); -- make wikilinks from month and day if required
end
if props.today then -- highlight today's date when displayed
if (props.year == props.this_ynum) and (mnum == props.this_mnum) and (dom == props.this_dnum) then
hilite = col;
end
end
table.insert (td_items, dom);
end
for i, td_item in ipairs (td_items) do
if i == hilite then
table.insert (result, repeat_tags ('td', {td_item}, {['class']='mcal', ['css'] = {['background-color'] = '#cfc'}}));
else
table.insert (result, repeat_tags ('td', {td_item}, options));
end
end
-- table.insert (result, repeat_tags ('td', td_items, options)); -- get the get the <td> tags for the dates part of the calendar
return table.concat (result);
end
--[[--------------------------< G E T _ W E E K _ D A Y _ H D R >----------------------------------------------
create header row of day-of-week abbreviations with title attributes
]]
local function get_week_day_hdr (props)
local headers = {};
if props.iso_wk then
table.insert (headers, repeat_tags ('th', {'Wk'}, {['class']='mcal', ['attr']={['title'] = 'ISO week number'}})); -- iso week header
for i, abbr in ipairs (iso_dayabbr) do
table.insert (headers, repeat_tags ('th', {iso_dayabbr[i]}, {['class']='mcal', ['attr']={['title'] = iso_dayname[i]}}));
end
else
for i, abbr in ipairs (dayabbr) do
table.insert (headers, repeat_tags ('th', {dayabbr[i]}, {['class']='mcal', ['attr']={['title'] = dayname[i]}}));
end
end
return table.concat (headers);
end
--[[--------------------------< G E T _ M O N T H _ H D R >----------------------------------------------------
create main header row for month calendars, with or without year and with or without previous / next links
]]
local function get_month_hdr (mnum, props)
local result = {};
local prev = '';
local next = '';
local hdr_year = '';
local col_span = (props.iso_wk and 8) or 7; -- assume no prev/next
if props.show_year then
hdr_year = get_display_year (props); -- if to be shown, add wikilink, etc when required
end
if props.prevnext then
prev = make_wikilink (monthname[(0 < mnum-1) and mnum-1 or 12], '<<');
next = make_wikilink (monthname[(13 > mnum+1) and mnum+1 or 1], '>>');
table.insert (result, repeat_tags ('td', {prev}, {}));
col_span = col_span - 2; -- narrow the month year <th>
end
table.insert (result, repeat_tags ('th', {get_display_month (mnum, props) .. ' ' .. hdr_year}, {['class']='mcal', ['attr']={['colspan']=col_span}}));
if props.prevnext then
table.insert (result, repeat_tags ('td', {next}, {}));
end
return table.concat (result);
end
--[[--------------------------< D I S P L A Y M O N T H >------------------------------------------------------
generate the html to display a month calendar
]]
local function display_month (mnum, props)
if props.leap then daysinmonth[2] = 29 end
local firstday = day_of_week (props.year, mnum, 1, props.iso); -- get first day number of the first day of the month; 1 == Sunday
local table_css = {};
if props.m_center then
table_css = { -- TODO: make a separate class in styles.css?
['clear'] = 'both',
['margin-left'] = 'auto',
['margin-right'] = 'auto',
}
end
if props.month_num then -- month_num only set when doing individual month calendars
table_css.border = '1px solid grey'; -- put this is styles.css as a separate class?
end
local month_cal = mw.html.create ('table');
month_cal
:addClass ('mcal' .. (props.m_float_r and ' floatright' or '')) -- float table right; leading space required to separate classes
:css (table_css)
:tag ('tr') -- for month name header
:addClass ('mcalhdr')
:wikitext (get_month_hdr (mnum, props))
:done() -- close <tr>
:tag ('tr') -- for weekday header
:addClass ('mcalhdr')
:wikitext (get_week_day_hdr (props))
:done() -- close <tr>
local numrows = math.ceil ((firstday + daysinmonth[mnum] - 1) / 7); -- calculate number of rows needed for this calendar
for row = 1, numrows do
month_cal
:tag ('tr') -- for this week
:addClass ('mcal')
:wikitext (get_row_dates (firstday, mnum, row, props)); -- get dates for this week
end
month_cal:done() -- close <table>
mw.log (tostring (month_cal))
return tostring (month_cal)
end
--[[--------------------------< G E T _ R O W _ C A L E N D A R S >--------------------------------------------
create <td> ... </td>... string to be included into <tr>...</tr> as :wikitext(...)
]]
local function get_row_calendars (cols, row_num, props)
local mnum; -- month number
local options = {['class']='ycal'}; -- table of otions for these td tags
local td_items = {}; -- table of <td>...</td> tag strings
for col_num = 1, cols do
mnum = cols * (row_num - 1) + col_num -- calculate month number from row and column values
if mnum < 13 then -- some sort of error return if ever 13+?
table.insert (td_items, display_month (mnum, props)); -- get a calendar for month number mnum
end
end
return repeat_tags ('td', td_items, options)
end
--[[--------------------------< D I S P L A Y _ Y E A R >------------------------------------------------------
create a twelve-month calendar; default is 4 columns × 3 rows
]]
local function display_year(props)
local year = props.year
local rows = props.rows
local cols = props.cols or 4
local mnum;
if rows then
cols = math.ceil(12 / rows)
else
rows = math.ceil(12 / cols)
end
local year_cal = mw.html.create('table');
year_cal
:addClass ('ycal' .. (props.y_float_r and ' floatright' or '')) -- float table right; leading space required to separate classes
:css (props.y_center and {['clear'] = 'both', ['margin-left'] = 'auto', ['margin-right'] = 'auto'} or {}) -- centers table; TODO: add to styles.css?
:tag ('tr')
:addClass ('ycalhdr')
:tag ('th')
:addClass ('ycal')
:attr ('colspan', cols)
:wikitext (props.title or year)
:done() -- close <th>
:done() -- close <tr>
for row_num = 1, rows do
year_cal
:tag('tr')
:addClass ('ycal')
:wikitext(get_row_calendars (cols, row_num, props)) -- get calendars for this row each wrapped in <td>...</td> tags as wikitext for this <tr>...</tr>
end
year_cal:done() -- close <table>
--mw.log (tostring (year_cal))
return tostring (year_cal)
end
--[[--------------------------< _ C A L E N D A R >------------------------------------------------------------
]]
local function _calendar (props)
if props.month_num then
props.show_year = true; -- show year in individual month calendars
return display_month (props.month_num, props);
else
return display_year (props)
end
end
--------------------------------------------------
--[[
Sakamoto's method: ISO date
returns day name or nil if bad arguments
iso here refers to the yyyy-mm-dd date format, not to iso day of week where 1 = monday
--]]
local function dayofweek (frame)
local isodate = mw.text.trim(frame.args[1] or "")
local y, m, d = isodate:match("(%d+)%p(%d+)%p(%d+)")
local dow = day_of_week(y, m, d)
if not dow then return "" end
return dayname[dow]
end
--[[
isleap returns "leap" if passed a leap year
otherwise returns nothing
]]
local function isleap (frame)
if is_leap(frame.args[1]) then return "leap" end
return ""
end
--[[
Main entry point for widget
--]]
local function calendar(frame)
local args=getArgs (frame);
local cal_props = {}; -- separate calendar properties table to preserve arguments as originally provided
local this_year_num = tonumber (lang_obj:formatDate ('Y'));
local this_month_num = tonumber (lang_obj:formatDate ('n'));
cal_props.this_ynum = this_year_num; -- for highlighting 'today' in a calendar display
cal_props.this_mnum = this_month_num;
cal_props.this_dnum = tonumber (lang_obj:formatDate ('j'));
cal_props.year = args.year and tonumber(args.year) or this_year_num;
cal_props.leap = is_leap (cal_props.year)
cal_props.title = args.title; -- year calendar title
if args.month then
local mnum = tonumber(args.month)
if not mnum then -- month provided as some sort of text string
if args.month == "current" then
cal_props.month_num = this_month_num
cal_props.year = this_year_num
elseif args.month == "last" then
mnum = this_month_num - 1
if mnum == 0 then
cal_props.month_num = 12 -- december last year
cal_props.year = this_year_num - 1 -- last year
else
cal_props.month_num = mnum; -- previous month
end
elseif args.month == "next" then
mnum = this_month_num + 1
if mnum == 13 then
cal_props.month_num = 1 -- january next year
cal_props.year = this_year_num + 1 -- next year
else
cal_props.month_num = mnum; -- next month
end
else
local good
good, cal_props.month_num = pcall (lang_obj.formatDate, lang_obj, 'n', args.month);
if not good then
cal_props.month_num = this_month_num
else
cal_props.month_num = tonumber (cal_props.month_num)
end
end
else
cal_props.month_num = (13 > mnum and 0 < mnum) and mnum or this_month_num; -- month provided as a number
end
cal_props.prevnext = 'yes' == (args.prevnext and args.prevnext:lower()); -- show previous / next links in month header only in single month calendars
cal_props.m_center = 'center' == (args.float and args.float:lower()) -- month calendar positions; default is left
cal_props.m_float_r = 'right' == (args.float and args.float:lower())
end
cal_props.iso_wk = 'yes' == (args['iso-wk'] and args['iso-wk']:lower()); -- show iso format with week numbers when true
cal_props.iso = 'yes' == (args.iso and args.iso:lower()) or cal_props.iso_wk; -- iso format without week number unless cal_props.iso_wk true; always true when cal_props.iso_wk true
args.lk = args.lk and args.lk:lower();
if args.lk and ({['yes']=1, ['dm']=1, ['my']=1, ['dy']=1, ['d']=1, ['m']=1, ['y']=1})[args.lk] then -- if valid keywords
if 'yes' == args.lk then -- all date components are linked
cal_props.lk_d = true;
cal_props.lk_m = true;
cal_props.lk_y = true;
else
cal_props.lk_d = 'd' == args.lk:match ('d'); -- decode the keywords to know which components are to be linked
cal_props.lk_m = 'm' == args.lk:match ('m');
cal_props.lk_y = 'y' == args.lk:match ('y');
end
end
if not (cal_props.m_center or cal_props.m_float_r) then -- these may aleady be set for month only calendar display
cal_props.y_center = 'center' == (args.float and args.float:lower());
cal_props.y_float_r = 'right' == (args.float and args.float:lower());
end
cal_props.today = 'yes' == (args['show-today'] and args['show-today']:lower()); -- highlight today's date in calendars where it is displayed
-- TODO: add all other args{} from template or invoke to cal_props{} modified as appropriate
return _calendar (cal_props);
end
--[[--------------------------< E X P O R T E D F U N C T I O N S >------------------------------------------
]]
return {
calendar = calendar,
dayofweek = dayofweek,
isleap = isleap,
}