Module:Timeline
require('Module:No globals')
 
 local yesno = require('Module:Yesno')
 local navbox = require('Module:Navbox')._navbox
 local getArgs = require('Module:Arguments').getArgs
 local p = {}
 
 -- Add a blank table cell
 local function addBlank(args, row, prev, current)
 	if row and prev &lt; current then
 		if yesno(args.decades) == false then
 			row:tag('td')
 				:addClass('timeline-blank')
 				:cssText(args.blankstyle)
 				:attr('colspan', current - prev)
 		-- Divide the cell up every decade if showing decades at the top
 		else
 			local year = prev
 			
 			while year &lt; current do
 				local dur = math.min(10 - year % 10, current - year)
 				
 				row:tag('td')
 					:addClass('timeline-blank')
 					:cssText(args.blankstyle)
 					:attr('colspan', dur)
 				
 				year = year + dur
 			end
 		end
 	end
 end
 
 -- Get timeline entries, start years, and end years
 local function timelineInfo(args)
 	local info = {
 		startYear = math.huge,
 		startYears = {},
 		endYear = 0,
 		endYears = {},
 		entries = {}
 	}
 	
 	for k, _ in pairs(args) do
 		if type(k) == 'string' then
 			local num = k:match('^%a+(%d+)$')
 
 			if num then
 				table.insert(info.entries, tonumber(num))
 			end
 		end
 	end
 	
 	table.sort(info.entries)
 	
 	for i, num in ipairs(info.entries) do 
 		if args['item' .. i] then
 			if not args['date' .. i] then
 				error('item' .. i .. ' requires a corresponding ' .. 'date' .. i, 0)
 			end
 			
 			local dates = mw.text.split(args['date' .. i], '-', true)
 			local startYear = tonumber(dates[1])
 			local endYear = tonumber(dates[2]) or tonumber(os.date('%Y')) + 1
 			
 			if not startYear then
 				error('date' .. i .. ' contains an invalid timerange', 0)
 			end
 			
 			info.startYear = math.min(info.startYear, startYear)
 			info.endYear = math.max(info.endYear, endYear)
 			info.startYears[i] = startYear
 			info.endYears[i] = endYear
 		end
 	end
 	
 	if args.startoffset then
 		info.startYear = info.startYear - tonumber(args.startoffset)
 	end
 	
 	if args.startyear then
 		info.startYear = math.min(info.startYear, tonumber(args.startyear))
 	end
 	
 	if args.endoffset then
 		info.endYear = info.endYear + tonumber(args.endoffset)
 	end
 	
 	if args.endyear then
 		info.endYear = math.max(info.endYear, tonumber(args.endyear))
 	end
 	
 	return info
 end
 
 -- Render the date rows
 local function renderDates(args, tbl, info)
 	local showDecades = yesno(args.decades)
 	local labelRow = nil
 	
 	if args.label then
 		labelRow = mw.html.create('th')
 			:attr('scope', 'col')
 			:addClass('navbox-group timeline-label')
 			:cssText(args.labelstyle)
 			:attr('rowspan', showDecades ~= false and '2' or '1')
 			:wikitext(args.label or '')
 	end
 	
 	-- Render the decades row
 	if showDecades ~= false then
 		local decadeRow = tbl:tag('tr')
 		local year = info.startYear
 		
 		decadeRow:node(labelRow)
 		
 		while year &lt; info.endYear do
 			local dur = math.min(10 - year % 10, info.endYear - year)
 			
 			decadeRow:tag('th')
 				:attr('scope', 'col')
 				:addClass('timeline-decade')
 				:cssText(args.datestyle)
 				:cssText(args.decadestyle)
 				:attr('colspan', dur)
 				:wikitext(math.floor(year / 10) .. '0s')
 			
 			year = year + dur
 		end
 	end
 
 	-- Render the years row
 	local yearRow = tbl:tag('tr')
 	local width = 100 / (info.endYear - info.startYear)
 	
 	if showDecades == false then
 		yearRow:node(labelRow)
 	end
 	
 	for i = info.startYear, info.endYear - 1 do
 		
 		yearRow:tag('th')
 			:attr('scope', 'col')
 			:addClass('timeline-year')
 			:cssText(args.datestyle)
 			:cssText(args.yearstyle)
 			:cssText('width:' .. width .. '%')
 			:wikitext(showDecades == false and i or i % 10)
 	end
 end
 
 -- Render the timeline itself
 local function renderTimeline(args, tbl, info)
 	local row = nil
 	local prev = info.startYear
 	local prevItem = nil
 	local prevLabel = nil
 	local labelSpan = 0
 	
 	for i, num in ipairs(info.entries) do
 		if args['row' .. i] or row == nil then
 			addBlank(args, row, prev, info.endYear)
 			
 			row = tbl:tag('tr')
 			prev = info.startYear
 			
 			if labelSpan &lt;= 0 and args.label then
 				labelSpan = tonumber(args['span' .. i]) or 1
 				
 				prevLabel = row:tag('th')
 					:attr('scope', 'row')
 					:attr('rowspan', labelSpan)
 					:addClass('navbox-group timeline-label')
 					:cssText(args.labelstyle)
 					:cssText(args['labelstyle' .. i] or '')
 					:wikitext(args['row' .. i])
 			end
 			
 			labelSpan = labelSpan - 1
 		end
 		
 		if args['item' .. i] then
 			local content = args['item' .. i] 
 			local startYear = info.startYears[i]
 			local endYear = info.endYears[i]
 			
 			addBlank(args, row, prev, startYear)
 			
 			-- Shrink previous item so new item can start at the start year
 			if prevItem and prev &gt; startYear then
 				prevItem:attr('colspan', prevItem:getAttr('colspan') - prev + startYear);
 			end
 			
 			prevItem = row:tag('td')
 				:addClass('timeline-item')
 				:cssText(args.itemstyle)
 				:cssText(args['style' .. i] or '')
 				:attr('colspan', endYear - startYear)
 				:wikitext(content)
 			
 			prev = endYear
 		end
 	end
 	
 	-- Remove any extra rowspan from the label
 	if prevLabel and labelSpan &gt; 0 then
 		prevLabel:attr('rowspan', prevLabel:getAttr('rowspan') - labelSpan);
 	end
 	
 	addBlank(args, row, prev, info.endYear)
 end
 
 function p.main(frame)
 	local args = getArgs(frame, {
 		removeBlanks = false,
 		wrappers = 'Template:Timeline'
 	})
 	local targs = {
 		listpadding = '0'
 	}
 	-- Arguments to passthrough to navbox
 	local passthrough = {
 		'name', 'title', 'above', 'below', 'state', 'navbar', 'border', 1,
 		'image', 'imageleft', 'style', 'bodystyle', 'style', 'bodystyle',
 		'basestyle', 'titlestyle', 'abovestyle', 'belowstyle', 'imagestyle',
 		'imageleftstyle', 'titleclass', 'aboveclass', 'bodyclass',
 		'belowclass', 'imageclass'
 	}
 	local info = timelineInfo(args)
 	local tbl = mw.html.create('table'):addClass('timeline-table')
 	
 	renderDates(args, tbl, info)
 	renderTimeline(args, tbl, info)
 	
 	if yesno(args.footer) then
 		renderDates(args, tbl, info)
 	end
 	
 	for _, name in ipairs(passthrough) do 
 		targs[name] = args[name]
 	end
 	
 	targs.list1 = frame:extensionTag{
 		name = 'templatestyles',
 		args = {
 			src = 'Template:Timeline/style.css'
 		}
 	} .. tostring(tbl)
 	
 	return navbox(targs)
 end