Template:UI/datetime
Render a UTC SMW datetime in the wiki's local timezone with the TZ
abbreviation, wrapped in an HTML5 <time> element so a
small Common.js gadget can upgrade the rendered text to the visitor's
MediaWiki timezone/date preferences client-side (see "Client-side
enhancement" below).
Single source of truth for date/time display across the schema. Anywhere
SMW projects ?Has start date#ISO et al, this template owns
the rendering. Direct Error: Invalid time. in row/card templates
displays the raw UTC value (events entered as "10 AM" in the editor's
local timezone get stored as UTC and surface as wrong-time-in-wrong-zone),
which is the bug this template exists to fix.
Server-side: wiki's $wgLocaltimezone via #timel
We use Error: Invalid time., which:
- Parses the iso with an explicit
Zsuffix so PHP's
strtotime anchors it as UTC (no ambiguity about server TZ for input).
- Formats in the wiki's configured
$wgLocaltimezonerather
than UTC. So17:00:00Zrenders as10:00 AMon a wiki configured for America/Los_Angeles (PDT), or19:00on a wiki configured for Europe/Paris, etc.
- Returns the right TZ abbreviation via
Error: Invalid time.
(e.g. "PDT", "PST", "CEST", "UTC"), DST handled automatically by PHP using the OS tzdata.
This replaces an earlier hand-rolled US-DST formula and a manual unix-
shift. The earlier comment in
Template:Category/Workshop itinerary/local-time warning against
Error: Invalid time. still applies — server-TZ-dependent
formatting is the bug; here we explicitly opt into the wiki's local
timezone via #timel, which is the intent.
Wiki admin requirement: set $wgLocaltimezone in
LocalSettings.php to the wiki's canonical timezone (e.g.
'America/Los_Angeles' for the Miniscope public wiki). If
left at the MediaWiki default (UTC), the server fallback will render in
UTC — visitors with the gadget installed still get their preference,
but anonymous/no-JS visitors will see UTC times.
Client-side enhancement (MediaWiki user preferences)
Each rendered datetime is wrapped in
<time datetime="<iso>Z" class="wiki-localtime" data-format="<format>"><server fallback></time>.
A small Common.js snippet (see #Common.js gadget below) finds these
elements at page-load and rewrites textContent based on the
viewer's preferences, in this priority order:
- Logged-in MediaWiki user with a timezone set in Special:Preferences →
use that timezone (parsed from mw.user.options.get('timecorrection'),
which encodes the pref as "ZoneInfo|<offset>|<Zone/Name>" or
"Offset|<minutes>" or "System|<minutes>").
- Otherwise → browser timezone via
Intl.DateTimeFormat
default (no timeZone option set).
Date format honors mw.user.options.get('date') when set
(e.g. mdy, dmy, ymd,
ISO 8601); otherwise falls back to the browser locale's
default.
When JS is disabled or the gadget isn't installed, the server fallback (wiki TZ) stands.
Common.js gadget
Paste into MediaWiki:Common.js on the deployed wiki:
// Upgrade SchemaSync server-rendered timestamps to the visitor's
// preferences. Targets <time class="wiki-localtime"> written by
// Template:UI/datetime. Honors mw.user.options for timezone and date
// format; falls back to the browser locale.
$(function () {
// Resolve the visitor's preferred IANA TZ name. mw.user.options has
// 'timecorrection' as one of three encodings:
// "ZoneInfo|<minutes>|<Zone/Name>" → use the zone name
// "Offset|<minutes>" → fixed offset (rare; we can't
// pass arbitrary offsets to
// Intl.DateTimeFormat, so fall
// through to browser default)
// "System|<minutes>" → wiki default; fall through
function resolveTimeZone() {
var tc = mw.user.options.get('timecorrection');
if (!tc) return undefined;
var parts = tc.split('|');
if (parts[0] === 'ZoneInfo' && parts[2]) return parts[2];
return undefined;
}
// Resolve date format prefs to Intl options. MediaWiki's 'date'
// pref is a slug ('mdy', 'dmy', 'ymd', 'ISO 8601', 'default') —
// map the common ones; let the browser locale handle 'default'.
function applyDatePref(opts, pref) {
switch (pref) {
case 'mdy':
return $.extend(opts, { month: 'short', day: 'numeric', year: 'numeric' });
case 'dmy':
return $.extend(opts, { day: 'numeric', month: 'short', year: 'numeric' });
case 'ymd':
case 'ISO 8601':
return $.extend(opts, { year: 'numeric', month: '2-digit', day: '2-digit' });
default:
return opts;
}
}
var tz = resolveTimeZone();
var datePref = mw.user.options.get('date');
document.querySelectorAll('time.wiki-localtime').forEach(function (el) {
var iso = el.getAttribute('datetime');
if (!iso) return;
var d = new Date(iso);
if (isNaN(d.getTime())) return;
var fmt = el.getAttribute('data-format') || 'long_datetime';
var opts;
switch (fmt) {
case 'time':
opts = { hour: 'numeric', minute: '2-digit', timeZoneName: 'short' };
break;
case 'long_datetime':
default:
opts = applyDatePref(
{ month: 'short', day: 'numeric', year: 'numeric',
hour: 'numeric', minute: '2-digit', timeZoneName: 'short' },
datePref
);
}
if (tz) opts.timeZone = tz;
try {
el.textContent = d.toLocaleString([], opts);
el.title = d.toLocaleString([], $.extend({ dateStyle: 'full', timeStyle: 'long' }, tz ? { timeZone: tz } : {}));
} catch (e) { /* leave server fallback */ }
});
});
Date-only formats deliberately skip TZ conversion
For month_day, month_day_year, and
month_year, this template emits plain text with no
<time> wrapper. Rationale: an all-day event stored as
2026-07-12T00:00:00 means "July 12 in the event's local
sense" — converting midnight UTC into any non-UTC zone would roll the
date back to July 11, which is wrong. Workshop and release dates fall
into this bucket. The gadget therefore doesn't touch date-only renders,
and the server output is correct as-is.
Parameters
iso— UTC ISO datetime from SMW (e.g.
2026-07-12T17:00:00), no Z suffix. Required; empty
renders nothing.
format— one of:time— e.g. "10:00 AM PDT"long_datetime— e.g. "Jul 12, 2026 · 10:00 AM PDT"
(default)
month_day— "Jul 12" (no TZ shift, no wrapper)month_day_year— "Jul 12, 2026" (no TZ shift, no
wrapper)
month_year— "July 2026" (no TZ shift, no wrapper)