Module:Deployment schedule
Appearance
The deployment schedule module provides functions for formatting and organizing the deployment calendar.
formatTable
Produces the wrapper around each row in the table. You only need one of these per calendar. It accepts one option, hidedate
which if set to true will display all dates as only the day of the week.
row
Workhorse of the calendar. Accepts options:
when
Start date and time of the deployment. Expressed as some timestamp like2014-05-12 8:00 PDT
length
Length of deployment window in hours. Default is 2.window
Name of the window, usually the name of the component being modified (e.g. parsoid).who
Who is performing the deployment. Consider using the {{ircnick}} template.what
What is being deployed.
Example
{{#invoke:Deployment schedule|formatTable|hidedate=false| {{#invoke:Deployment schedule|row |when=2014-05-12 8:00 PDT |length=1 |window=[[SWAT deploys|SWAT]] <br/><small>'''(Max 8 patches)'''</small> |who=Nik (manybubbles), Brad (anomie) |what= ''Tomasz (twkozlowski)'' * [config] Enable Extension:NewUserMessage on the Ukrainian Wikipedia {{gerrit|132747}} ''Timo (Krinkle)'' * [wmf3] VisualEditor i18n fix backport {{gerrit|132770}}; cite case sensitivity backport {{gerrit|132773}} * [wmf4] VisualEditor i18n fix backport {{gerrit|132769}}; cite case sensitivity backport {{gerrit|132772}} ''Requesting developer'' (''irc handle'') * ''Gerrit link to backport or config change'' }} }}
Result
Time | Component | Deployer | Changes |
---|---|---|---|
Monday, May 12 | |||
– UTC # – PDT |
SWAT (Max 8 patches) |
Nik (manybubbles), Brad (anomie) | Tomasz (twkozlowski)
Timo (Krinkle)
Requesting developer (irc handle)
|
local p = {}
local timezones = {
-- Add timezone offsets here!
Z = 0,
UTC = 0,
PST = -8,
PDT = -7,
SF = 'America/Los_Angeles',
DE = 'Europe/Berlin',
}
local timezones2 = {
SF = 'America/Los_Angeles',
DE = 'Europe/Berlin',
}
function getSanFranciscoTimezone( time )
-- mw.getContentLanguage():formatDate() will handle input with tzdata
-- timezones, outputting the corresponding time in UTC. So we pass it input
-- that is manually offset for PST and PDT and see which one matches UTC.
local utc = os.date( '!%FT%TZ', time )
local pst = mw.getContentLanguage():formatDate( 'Y-m-d\\TH:i:s\\Z', os.date( '!%FT%T America/Los_Angeles', time + timezones.PST * 3600 ) )
local pdt = mw.getContentLanguage():formatDate( 'Y-m-d\\TH:i:s\\Z', os.date( '!%FT%T America/Los_Angeles', time + timezones.PDT * 3600 ) )
if utc == pst then
return 'PST'
elseif utc == pdt then
return 'PDT'
else
mw.logObject( { UTC = utc, PST = pst, PDT = pdt }, 'Cannot determine SF time' )
return 'PST'
end
end
function trim( str )
-- Trims a string because wikitech apparently doesn't have this library...
str = string.gsub( str, "%s+$", "" )
str = string.gsub( str, "^%s+", "" )
return str
end
-- Re-usable version of stable attributes on the item element (style agnostic)
-- * 'id' for anchors
-- * 'utcstart' for start datetime in UTC (ISO format)
-- * 'utcend' for end datetime in UTC (ISO format)
function getStableItemAttribs(args)
local start = args['when']
local seconds = ( tonumber( args['length'] ) or 2 ) * 60 * 60
if start == nil or start == '' then
-- Fallback
return { id = "", utcstart = "", utcend = "" }
end
year, month, day, hour, minute, tz_raw = string.match( start, '^(%d+)-(%d+)-(%d+) (%d+):(%d+) ([A-Z]+)$' )
tz = timezones2[tz_raw] or tz_raw
local formatted = string.format( '%d-%d-%dT%d:%d:00 %s',
year or 1980,
month or 0,
day or 0,
hour or 0,
minute or 42,
tz
)
utc = tonumber( mw.getContentLanguage():formatDate( 'U', formatted ) )
local id = string.format( 'deploycal-item-%s', os.date( "!%Y%m%dT%H%M", utc ) )
local utcstart = os.date( "!%Y-%m-%dT%H:%M+00:00", utc )
local utcend = os.date( "!%Y-%m-%dT%H:%M+00:00", utc + seconds )
return { id = id, utcstart = utcstart, utcend = utcend }
end
function p.item_stable_attribs( frame )
local parentFrame = frame:getParent()
local stable = getStableItemAttribs( parentFrame.args )
return string.format(
'id="%s" data-utcstart="%s" data-utcend="%s"',
stable.id, stable.utcstart, stable.utcend
)
end
function p.getJSON( frame )
local parentFrame = frame:getParent()
local tmp = {}
for k, v in pairs(parentFrame.args) do
tmp[k] = mw.text.nowiki( mw.ustring.gsub( v, '"', "'" ) )
end
local stable = getStableItemAttribs( parentFrame.args )
tmp['id'] = stable.id
return mw.text.jsonEncode( tmp )
end
-- Date formatting logic for the schedule-deployment.toolforge.org link
-- in [[Template:Deployment_calendar_event_card]]
-- Returns a Unix timestamp string or the empty string
function p.unixTimestamp( frame )
local start = frame.args['start']
if start == nil or start == '' then
return ''
end
year, month, day, hour, minute, tz_raw = string.match( start, '^(%d+)-(%d+)-(%d+) (%d+):(%d+) ([A-Z]+)$' )
tz = timezones2[tz_raw] or tz_raw
local formatted = string.format( '%d-%d-%dT%d:%d:00 %s',
year or 1980,
month or 0,
day or 0,
hour or 0,
minute or 42,
tz
)
return mw.getContentLanguage():formatDate( 'U', formatted )
end
-- Date fomatting logic for [[Template:Deployment_calendar_event_card]]
function p.when( frame )
local utc = p.unixTimestamp( frame )
local seconds = ( tonumber( frame.args['hours'] ) or 2 ) * 60 * 60
if utc == '' then
return "No time given"
end
utc = tonumber(utc)
sftz = getSanFranciscoTimezone( utc )
tzoffset = timezones[sftz]
sflocal = utc + ( tzoffset * 60 * 60 )
-- Determine if the SF day is different from the UTC day
if os.date( "!%A", utc ) ~= os.date( "!%A", sflocal ) then
sfdatestr = os.date( "!<small>(%a)</small> ", sflocal )
else
sfdatestr = ""
end
return string.format( '<span class="deploycal-time-utc">%s–%s UTC [[#deploycal-item-%s|#]]</span>' ..
' <br/> ' ..
'<span class="deploycal-time-sf">%s %s–%s %s</span>',
'<time class="deploycal-item-utcstart deploycal-starttime" datetime="' .. os.date( "!%Y-%m-%dT%H:%M+00:00", utc ) .. '">' ..
os.date( "!%H:%M", utc ) .. '</time>',
'<time class="deploycal-item-utcend deploycal-endtime" datetime="' .. os.date( "!%Y-%m-%dT%H:%M+00:00", utc + seconds ) .. '">' ..
os.date( "!%H:%M", utc + seconds ) .. "</time>",
os.date( "!%Y%m%dT%H%M", utc ),
sfdatestr,
'<time class="deploycal-starttime" datetime="' .. os.date( "!%Y-%m-%dT%H:%M+" .. tzoffset .. ":00", sflocal ) .. '">' ..
os.date( "!%H:%M", sflocal ) .. "</time>",
'<time class="deploycal-endtime" datetime="' .. os.date( "!%Y-%m-%dT%H:%M+" .. tzoffset .. ":00", sflocal + seconds ) .. '">' ..
os.date( "!%H:%M", sflocal + seconds ) .. "</time>",
sftz
)
end
function p.header( frame )
-- In my young and dumb days, this created a header for formatTable.
-- We still need it because the archive page calls it (though it no longer needs to)
end
function p.footer( frame )
-- In my young and dumb days, this created a footer for formatTable.
-- We still need it because the archive page calls it (though it no longer needs to)
end
-- Code for the older table-based layout.
-- Kept to help render archived deployment calendars.
-- (TODO: Consider substituting them at some point if possible.)
function p.row( frame )
-- Produces a raw row to be reconsumed by this script for sorting
-- Expects:
--- when, string, like YYYY-MM-DD HH:mm XXX where XXX is a valid timezone listed in timezones
--- length, number, of hours the deploy will take
--- window, string, what deployment window this is (e.g. SWAT, parsoid, MediaWiki)
--- who, string, who is performing the deploy
--- what, string, what will be deployed
--
-- Outputs: UTC Timestamp,Length,Who,What
--- commas in strings are replaced with ,
local when = frame.args['when']
local length = ( tonumber( frame.args['length'] ) or 2 ) * 60 * 60
local window = frame.args['window'] or "N/A"
local who = frame.args['who'] or "Anonymous Cowards (must provide 'who=')"
local what = frame.args['what'] or "Nothing? Then why is this an entry!? (must provide 'what=')"
who = string.gsub( who, ",", "," )
what = string.gsub( what, ",", "," )
if when == nil then
utc_time = 0
what = "No time given (must provide 'when' and 'tz', e.g.: 'when=YYYY-MM-DD HH:mm XXX'"
else
year, month, day, hour, minute, tz = string.match( when, '^(%d+)-(%d+)-(%d+) (%d+):(%d+) ([A-Z]+)$' )
tz = timezones[tz]
if tz == nil then
utc_time = 0
what = "Unknown timezone in 'when' string! -- add it to the module :)"
elseif type( tz ) == 'string' then
local when2 = string.format( '%d-%d-%dT%d:%d:00 %s',
year or 1980,
month or 1,
day or 1,
hour or 1,
minute or 1,
tz
)
utc_time = mw.getContentLanguage():formatDate( 'U', when2 )
else
ts = os.time({
year = year or 1980,
month = month or 1,
day = day or 1,
hour = hour or 1,
min = minute or 1
})
utc_time = ts - ( tz * 60 * 60 )
end
end
return string.format( "%s,%s,%s,%s,%s", utc_time, length, window, who, what )
end
function p.formatTable( frame )
-- Formats a bunch of rows from p.row()
local rows = {}
local row
local count = 1
local retval = {}
local sflocal
local sfdatestr
local showdate = not ( frame.args['hidedate'] == 'true' )
local tzoffset = 0
local cutcday
local sftz = "No data"
-- Load and sort the arguments
row = frame.args[count]
while not ( row == nil ) do
utc, length, window, who, what = string.match( row, '^(.*),(.*),(.*),(.*),(.*)$' )
utc = tonumber( trim ( utc ) )
length = tonumber( trim( length ) )
who = trim( who )
what = trim( what )
table.insert( rows, { utc=utc, length=length, window=window, who=who, what=what } )
count = count + 1
row = frame.args[count]
end
table.sort( rows, function( a, b ) return a.utc < b.utc end )
-- For each entry create the final row
for count = 1, #rows do
local datestr
utc = rows[count].utc
length = rows[count].length
window = rows[count].window
who = rows[count].who
what = rows[count].what
if ( cutcday ~= os.date( '%x', utc ) ) then
-- Create a new date header in the table
table.insert( retval, "|-\n| colspan='4' class='deploycal-dayrow' | \n==== " .. os.date( "!%A, %B %d", utc ) .. " ====" )
cutcday = os.date( '%x', utc )
end
sftz = getSanFranciscoTimezone( utc )
tzoffset = timezones[sftz]
sflocal = utc + ( tzoffset * 60 * 60 )
-- Determine if the SF day is different from the UTC day
if os.date( "!%A", utc ) ~= os.date( "!%A", sflocal ) then
sfdatestr = os.date( "!<small>(%a)</small> ", sflocal )
else
sfdatestr = ""
end
table.insert( retval, string.format( [=[ |- id="deploycal-item-%s" class="deploycal-item"
| <span class="deploycal-time-utc">%s–%s UTC [[#deploycal-item-%s|#]]</span> <br/> <span class="deploycal-time-sf">%s %s–%s %s</span>
| <span class="deploycal-window">%s</span>
| %s
| %s
]=],
os.date( "!%Y%m%dT%H%M", utc ),
'<time class="deploycal-starttime" datetime="' .. os.date( "!%Y-%m-%dT%H:%M+00:00", utc ) .. '">' ..
os.date( "!%H:%M", utc ) .. '</time>',
'<time class="deploycal-endtime" datetime="' .. os.date( "!%Y-%m-%dT%H:%M+00:00", utc + length ) .. '">' ..
os.date( "!%H:%M", utc + length ) .. "</time>",
os.date( "!%Y%m%dT%H%M", utc ),
sfdatestr,
'<time class="deploycal-starttime" datetime="' .. os.date( "!%Y-%m-%dT%H:%M+" .. tzoffset .. ":00", sflocal ) .. '">' ..
os.date( "!%H:%M", sflocal ) .. "</time>",
'<time class="deploycal-endtime" datetime="' .. os.date( "!%Y-%m-%dT%H:%M+" .. tzoffset .. ":00", sflocal + length ) .. '">' ..
os.date( "!%H:%M", sflocal + length ) .. "</time>",
sftz,
window,
who,
what
))
end
return string.format( [[
{| class='wikitable deploycal'
!Time
!Component
!Deployer
!Changes
]], sftz ) .. table.concat( retval, "\n" ) .. "\n|}"
end
return p