User:Fullstop/autodate.js
Appearance
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump. This code will be executed when previewing this page. |
![]() | Documentation for this user script can be added at User:Fullstop/autodate. |
//
// 1. add
// importScript('User:Fullstop/autodate.js');
// to your User:yourname/monobook.js if you want to use this.
// 2. wrap your dates like this: {{date| <your date> }}
//
/* --------------------------------------------------------------
* wpAutoDate() is a client-side processor for converting
* appropriately-tagged dates into a user-preference based format.
* ---------------------------------------------------------------
*
* Function summary:
* 1. This function obviates the need to [[wiki-link]] dates in order
* for them to be to be reformatted according to date format
* preferences.
* 2. It can obtain the date-format preference from the browser, and
* thus also works for anon users, or when an editor has not set
* a date-format preference.
* 3. It can supplement a normal [[wikilink]]ed date too. Just span it
* as appropriate (see next section).
* 4. It is a client-side solution for bug #4582
*
* How it works:
* The function works by reformatting dates inside <span>s that have
* class="wpAutoDate". When tagged in this fashion, the contents of
* the spans are identifiable as dates and reformatted as appropriate.
*
* Although the <span>s can be added manually, the {{date}} template
* on en.wiki is also set up to do the necessary tagging.
* {{date|...somedate...}} also pre-formats dates as 'dmy' (aka "RFC 2822")
* dates. That 'dmy' format is then what a user will see when he/she
* does not have Javascript enabled (or is not running the script).
*
* How the preferred date-format is inferred:
* 1. The function first looks for a global variable named
* 'wgUserDateFormat' provided by the server. Like the other
* 'wgUserXXX' variables, this is not set if not-logged-in.
* 2. Failing option #1, and if logged in, the function will infer
* the date pref from other information provided by the server.
* This information is however only available in normal "view"
* mode, so autodate will cache it in a cookie (expires at end
* of session) so that it is available in edit preview too.
* 3. When options #1 and #2 are not available, the function
* will attempt to infer the date format from the browser's
* locale.
* 4. Failing option #3, the pref will be inferred from the
* timezone.
* NB: As of January 2008, the server does not yet provide
* 'wgUserDateFormat' (yet). Recognized values would be the
* mediawiki ones: "dmy", "mdy", "ymd" or "ISO 8601".
*
* Supported formats:
* * This function supports all the date formats settable in userprefs
* and/or that are recognized by /phase3/includes/DateFormatter.php.
* These include:
* . dmy: e.g. 30 January 2008 or 30 January 2008 BC
* . mdy: e.g. January 30, 2008 or January 30, 2008 BC
* . ymd: e.g. 2008 January 30 or 2008 BC January 30
* . ISO 8601 subset: e.g. 2008-01-30 or -2008-01-30 or 0000-01-30
* . dm: e.g. 30 January
* . md: e.g. January 30
* * Because time/timezone need to be reformatted when converting to/from
* ISO dates, this function supports time/timezone conversion for all
* formats as well. So for example,
* . 2008-01-28 T 22:26:23+02:00
* . 2008-01-28T22:26:23.95Z
* . 28 January 2008 22:26 UTC+02:00
* . January 28, 2008 22:26:23 (UTC)
* . January 28, 2008 22:26:23 GMT-12
* . 28 January 22:26:23 UTC+1400
* * And since wiki-style timestamps are just dates with the time in
* the "wrong" place, this function will also recognize a
* wiki-style timestamp. For example,
* . 22:26, 28 January 2008 (UTC)
* * Because weekday names are emitted by en:{{FULLDATE}}, and because
* leading weekday names were no significant effort to add support for,
* this function can handle those as well. With that, this function
* can also deal with anything that en:{{FULLDATE}} can emit.
* So for example,
* . Monday, January 28, 2008
* . Mon, 28 Jan 2008 22:26:23 +0000
* * In addition, this function can deal with
* . Every imaginable combination of commas/periods/whitepace.
* . BCE, AD, and CE in addition the standard BC.
*
* Date formatting notes:
* 1. ISO 8601 defines hyphen and colon separators as optional. In
* this function, as also in DateFormatter.php, they are mandatory.
* 2. Following DateFormatter.php's example, when the date format
* preference is ISO 8601 and the source date has no year
* component, the date will be emitted as 'md', i.e. "May, 5".
* Per ISO 8601:2003, negative year values denote a BC year. This
* is supported in both input and output.
* 3. ISO 8601 (subset) YYYY-MM-DD dates (with or without optional time
* and timezone information) in the span's title= attribute are
* compatible with the hCalendar microformat. Auto-reformattable
* dates can thus co-exist with hCalendar dates, and for conversion
* the date in the title= attribute will have priority over the
* contents of the span.
* 4. Era signifiers (BCE/BC/CE/AD) are normalized. The first date
* with/requiring a signifier determines the suffix for the rest.
* 5. Prefix AD (e.g. 'AD 24') is converted to postfix AD ('24 AD').
* 6. CE/AD is always appened to years in the range 1-99. Although
* this is only absolutely necessary for years in the range 1-31,
* it is the author's humble opinion that the other 2-digit dates
* benefit from an era specifier as well.
* 7. Abbreviated month names (Jan., Feb. etc) are preserved in their
* abbreviated form. Although MOS:DATE recommends full names, it
* also seems to suggest that there may to be uses for abbreviated
* forms.
* 8. Some common non-3-letter abbreviations such as "Thurs"
* or "Sept" are also accepted but normalized to 3-letter forms.
* 9. The reformatted dates will have non-breaking spaces between parts
* of a unit. So, "Saturday, February 16 2008 19:27:10.444 UTC+07:00"
* can wrap between "2008" and "19:27" but not anywhere else.
* 10. Ordinal suffixes (the 'st', 'nd' in 1st, 2nd, etc) are filtered
* out (per MOS:DATE). The code to emit these in the output (when
* present in the input) is disabled.
*
* Implementation notes:
* 1. The function regulates itself to avoid running for too long
* at a time. This is (in the main) an anti-abuse measure, and
* rather than set a hard limit, it times itself. The timeout is
* such (ca. 0.25 secs) that a page would have to have several
* hundred dates on it before this protection kicks in. The
* function then continuously "reschedules" itself (each time
* running ca. 0.25 sec) until processing completes.
* The downside of "throttling" is that the page gets redrawn in
* chunks, which can manifest itself as seemingly unmotivated
* text reflows, which are particularly noticeable when a table
* is redrawn. This functionality is optional but enabled
* by default.
* 2. The output format can be selected on a date-for-date basis. This
* functionality was originally to aid debugging, but may be
* generally useful as well. To override the default date format
* set the span's class to "autodate-xxx" where 'xxx' is one of:
* 'dmy', 'mdy', 'ymd' or 'iso'.
* 3. Error handling: Dates in an unsupported format, or that have
* other issues (ambiguous year/day values for example) will appear
* in red, with class="error", and with a title= attribute (then
* seen as a tooltip) that describes the problem.
*
* See also:
* * HTML 5's <time> tag
* http://www.whatwg.org/specs/web-apps/current-work/#the-time
* * FF 3's microformat API.
* http://developer.mozilla.org/en/docs/Using_microformats
*/
// --------------------
function wp_get_datefmt_pref()
{
// date format is selected as follows...
// 1. from a global 'wgUserDateFormat' if set.
// 2. from a cookie set on the last visit to an article
// in normal "view" (non-edit/non-preview) mode.
// 3. from the browser's (i.e. operating system's) locale.
// 4. from the timezone that the user is in
// always returns a datepref
var s, i, dt;
if (typeof(window.wgUserName) === 'string') { //null if not logged in
var df_list = "|mdy|dmy|ymd|ISO 8601|iso|"; //"ISO 8601" is "iso"
if (typeof(window.wgUserDateFormat) === 'string') { //server provided it?
if (df_list.indexOf('|' + window.wgUserDateFormat + '|') !== -1 ) {
//step #1 success!
return window.wgUserDateFormat.substr(0,3).toLowerCase();
}
//the indexOf would be -1 for unrecognized values (eg "default")
}
else { // wgUserDateFormat is not yet implemented
var cname = 'datefmt';
s = null;
if (typeof(window.wgAction) === 'string' &&
typeof(window.wgCanonicalSpecialPageName) !== 'string' &&
window.wgAction === 'view') {
ar = document.getElementById("lastmod");
if (ar) {
ar = ar.innerHTML;
if (/^[^\d]+\d{4}.\d\d.\d\d[^\d]+.*/.test(ar)) {
s = "iso";
} else if (/^[^\d]+\d{1,2}[^\d]{1,2}\d{4}[^\d]+.*/.test(ar)) {
s = "mdy";
} else if (/^[^\d]+\d{4}[^\d]+\d{1,2}[^\d]+.*/.test(ar)) {
s = "ymd";
} else {
s = ""; //"dmy" is the default, so fallthrough
} //and use locale/timezone
//update cookie if iso/mdy/ymd
//delete cookie if dmy
dt = ""; //expires at end of session
if (!s) //has already expired (i.e. delete)
dt = "; expires=Thu, 1 Jan 1970 00:00:01 GMT";
document.cookie = cname + '=' + s + '; path=/' + dt;
if (s) {
window.wgUserDateFormat = s; //cache it
return s;
}
// 's' is "" here
}
}
if (s === null && // not in "view"
typeof(document.cookie) === 'string') {
s = ';' + document.cookie + ';';
i = s.indexOf(';' + cname + '=');
if (i === -1)
i = s.indexOf(' ' + cname + '=');
if (i !== -1 && s.charAt(i + cname.length + 5) === ';' &&
df_list.indexOf('|' + s.substr(
(i + cname.length + 2), 3 ) + '|') !== -1) {
s = s.substr( (i + cname.length + 2), 3 );
window.wgUserDateFormat = s; //save it for next time
return s; // step #2 success!
}
} // if (typeof(document.cookie) === 'string')
} // wgUserDateFormat is not yet implemented
} // user is logged in (have wgUserName)
s = null;
dt = new Date(1999,11-1,22,0,0,0,0); //in dst
if (dt) {
s = dt.toLocaleString();
if ( s.indexOf('99-11-22') !== -1) {
s = 'iso'; // step #3 success
} else {
var y = s.indexOf('99');
var m = s.indexOf('11'); //can be -1!!
var d = s.indexOf('22');
s = null;
if (y != -1 && y < d)
s = 'ymd'; // step #3 success
else if (d != -1 && d < m)
s = 'dmy'; // step #3 success
else if (m != -1 && m < d)
s = 'mdy'; // step #3 success
else {
i = dt.getTimezoneOffset(); //note: 1999-11-22 is in dst
if (i >= ((2*60)+30) && i <= (10*60)) // step #4 success
s = 'mdy'; //azores+30 to hawaii (incl.)
//Australian territories begin at 1030
//The following regions will get the wrong defaults
// - French Polynesia (1000), should be dmy;
// - Am.Samoa & Midway (1100), should be mdy;
// - NZ far west territories (1000), should be dmy;
}
}
delete dt;
}
if (!s)
s = 'dmy'; //rest of the world - step #4 success
window.wgUserDateFormat = s; //save it until reload
return s;
}
// ---------------------
function _bad_date(elem, oldstr, whatsinv)
{
var i, lotsabugs = (/*@cc_on!@*/false);
if (!whatsinv)
whatsinv = '';
else
whatsinv = ' (' + whatsinv + '?)';
whatsinv = 'Error: malformed date' + whatsinv;
if (typeof(elem.wpAutoDate) === 'undefined')
elem.wpAutoDate = true; //flag as been there, done that
if (!lotsabugs && typeof(elem.hasAttribute) !== 'undefined') {
if (elem.childNodes.length === 1 && elem.childNodes[0].nodeType === 3) {
elem.childNodes[0].nodeValue = oldstr;
oldstr = null;
} else if (typeof(elem.textContent) != 'undefined') {
elem.textContent = oldstr;
oldstr = null;
} else if (typeof(elem.innerText) != 'undefined') {
elem.innerText = oldstr;
oldstr = null;
}
if (oldstr === null) { // changed text with grace
elem.setAttribute('title', whatsinv);
elem.setAttribute('style', 'color:rgb(255,0,0)'); //'#660033';
if (typeof(elem.className) !== 'string')
elem.className = "error";
else
elem.className += " error";
return;
} // changed text with grace
} // if (!lotsabugs && typeof(elem.hasAttribute) != 'undefined')
// have to do it the graceless way
oldstr = escape(oldstr);
oldstr = oldstr.replace('%20',' ').replace('%2C',',');
i = -3;
while (-1 !== (i = oldstr.indexOf('%', i+3)) )
oldstr = oldstr.substr(0,i) + '&#' +
parseInt(oldstr.substr(i+1,2),16) + ';' + oldstr.substr(i+3);
oldstr = '<span title="' + whatsinv.replace('\x22', '"') + '"' +
'class="error" style="color:red;">' + oldstr + '</span>';
elem.innerHTML = oldstr;
return;
}
/* --------------------- */
var _wpAutoDate_throttling = 1; // 1=enable, 0=disable throttling
function wpAutoDate()
{
/* English language names so that this works even if the
* browser's language is something other than English.
*/
var _mn_l = ["January","February","March","April","May","June","July",
"August","September","October","November","December"];
var _mn_s = ["Jan","Feb","Mar","Apr","May","Jun","Jul",
"Aug","Sep","Oct","Nov","Dec",
"Sept"]; //note "Sept" vs "Sep"
var _dn_l = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday",
"Saturday"];
var _dn_s = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat",
"Tues","Wednes","Thur","Thurs"]; //alt abbrev.
if (typeof(document.getElementById) === 'undefined')
return; // DOM not loaded yet
if (typeof(document.childNodes) === 'undefined')
return; // unsupported implementation
if (arguments.callee.last_idx) { // defined && != 0
//ensure that the function isn't called while a pass is already
//in progress (for example, invoked by multiple onload handlers)
if (arguments.length < 1)
return;
if (arguments.callee.last_idx !== parseInt(arguments[0],10))
return;
}
try {
var datefmt = null;
var running_all;
var with_xpath;
var ar, i, dlist;
var fn_gettext = function(elem) {
// caution: this is _not_ general purpose. It makes some
// assumptions that shouldn't generally be made.
// IE=625, O=305, FF=219, Webkit=141 (msecs per 500 node reads)
if (typeof(elem.textContent) !== 'undefined') { // DOM 3 standard
return elem.textContent;
} else if ((/*@cc_on!@*/true) // disabled for IE;
&& typeof(elem.innerText) !== 'undefined') {
//IE swallows \xA0 ( ) added through createTextNode
return elem.innerText;
} else if (typeof(elem.childNodes) !== 'undefined') {
return (function (_el) {
var _s = ''; var _cn = _el.childNodes; var _l = _cn.length;
for (var _i = 0; _i < _l; _i++) {
switch (_cn[_i].nodeType) {
case 1: _s += arguments.callee(_cn[_i]); break; //node
case 3: _s += _cn[_i].nodeValue; break; //text
}
}
return _s;
})(elem);
}
return '';
};
var fn_now = function() {
if (typeof(Date.now) !== 'function') {
var _dt = new Date();
var _t = _dt.getTime();
delete _dt; // some like this
_dt = null; // some like that
return _t;
}
return Date.now(); //ain't spidermonkey cool?
};
// NB: the settext operation is the single most
// expensive function in the nodewalk loop.
// caution: this is _not_ general purpose. It makes some
// assumptions that shouldn't generally be made.
var fn_settext = function(elem, s) {
if (elem.childNodes.length === 1 && elem.childNodes[0].nodeType===3)
elem.childNodes[0].nodeValue = s;
else if (typeof(elem.textContent) !== 'undefined')
elem.textContent = s;
else if (typeof(elem.innerText) !== 'undefined')
elem.innerText = s;
else
{
var _i = -4; var _s = escape(s);
_s = _s.replace('%20',' ').replace('%2C',',');
while ((_i = _s.indexOf('%', _i+4)) !== -1)
_s = _s.substr(0, _i) + '&#' + parseInt(_s.substr(_i+1,2),16)
+ ';' + _s.substr(_i+3);
elem.innerHTML = _s;
}
return;
};
// simultaneously...
// a) load the contents of the span,
// b) check for <a> linkage,
// c) check for nesting.
// This is faster than discrete steps. It is
// also a work around for an IE5 bug (*elem*.getbyTagName
// fails to find span's 'A' child nodes)
var fn_3in1 = function (_el, _arp) {
var _s = '';
var _cn = _el.childNodes;
var _l = _cn.length;
for (var _n = 0; _n < _l; _n++) {
switch (_cn[_n].nodeType) {
case 3: //text
//_s += _cn[_n].nodeValue;
_s = _s.concat(_cn[_n].nodeValue);
break;
case 1: { //node
//nb: 'i' is outside the namespace
if (_cn[_n].nodeName != 'A') {
_arp[1] = true;
} else {
_arp[0] = true;
}
_s = _s.concat(arguments.callee(_cn[_n], _arp));
break;
}
}
}
return _s;
};
// ++++++++ regularly scheduled programming ++++++
running_all = 0;
with_xpath = false;
if (typeof(arguments.callee.coll) === 'object') {
dlist = arguments.callee.coll;
datefmt = arguments.callee.last_fmt;
with_xpath = !!(document.evaluate);
} else if (document.evaluate) {
// rather like the shadow copy mechanism below, but
// leaving array generation to the xpath engine and
// pre-filtering on classname
dlist = document.getElementById('bodyContent');
if (dlist) {
// As of Jan 2008, webkit can't yet (3.04) do
// 'id("bodyContent")//span[contains(@class,"wpAutoDate")]'
dlist = document.evaluate('//span[contains(@class,"wpAutoDate")]',
dlist, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
//XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
if (dlist) {
if (dlist.snapshotLength) {
with_xpath = true;
datefmt = wp_get_datefmt_pref();
arguments.callee.coll = dlist;
arguments.callee.last_fmt = datefmt;
}
}
}
} else {
dlist = document.getElementById('bodyContent');
if (dlist) {
/*@cc_on @if (@_jscript_version <= 5.6)//IE5 or IE6 pre-KB942840
if (typeof(document.all) !== 'undefined') {
// workaround IE's crappy GC. Counter-productive for Opera.
// Note: this hack only works for *document*.all and directly
// (no referencing) on document.all. Referencing or filtering
// (i.e. dlist.all and/or .tags("SPAN")) renders the hack
// ineffective. Filtering is done separately in the inner loop.
running_all = document.all.length;
if (running_all <= (4096*3)) //4096 is the GC threshold, and
running_all = 0; // *3 assumes that at most 1/3rd are spans
}
@end @*/
if (!running_all) {
dlist = dlist.getElementsByTagName('span');
if (dlist) {
i = dlist.length;
if (!i) {
dlist = null;
} else if (i >= 100 &&
(typeof(arguments.callee.last_idx) !== 'number' ||
arguments.callee.last_idx === 0) ) {
// Shadow copy optimization for Opera and Webkit.
// Only marginal gains for Gecko/Spidermonkey.
ar = new Array(i);
while (i) {
i--;
ar[i] = dlist[i];
}
arguments.callee.coll = ar;
dlist = ar;
ar = null;
} // use shadow copy
} // have spans
} // not running_all
if (dlist) {
if (arguments.callee.last_fmt && arguments.callee.last_idx) {
datefmt = arguments.callee.last_fmt;
} else {
datefmt = wp_get_datefmt_pref();
arguments.callee.last_fmt = datefmt;
}
}
} //have bodyContent
} // not using shadow copy
if (datefmt) { // all is well
var iso = /^([+-]?\d{4})-(\d{2})-(\d{2})(.*)/;
var normalized_time =
/^T(\d\d):(\d\d)(:\d\d|)(\.\d+|)(Z\+\d\d:\d\d|Z\-\d\d:\d\d|Z|)$/;
var haslinks = /<a\s+/i;
var dclean = /[\s.,]+/g; //update regexen if you change this
var ce_mode;
var has_buggy_s;
var profcntr_1 = 0;
var profcntr_2 = 0;
var tslice;
var YMd, dMY, dM, MdY, Md;
var idx, s, dlist_len;
var start_time;
start_time = fn_now(); //also used as a "some number" value
//workaround for webkit and IE missing nbsp in \s
//\t\r\n also fail for elems created with createTextNode()
s = '\t\r\n\xA0\u00a0';
s = s.replace(/\s+/g, '');
has_buggy_s = (s.length != 0);
//alert("buggy?=" + has_buggy_s + ' "' + escape(s) + '"' );
(function () //pseudo "let"
{
// year expressions are intentionally \d+ and not \d{1,4}
// YMd/dMY are intentionally ambiguous for y <= 2 digits
var ymid = '(\\d+\\s*BCE?|'
+ '\\d+\\s*AD|'
+ 'AD\\s*\\d+|'
+ '\\d+\\s*CE|'
+ '\\d+)[\\s,\\.]+';
var yend = '(\\d+\\s*BCE?[\\s,\\.]+|\\d+\\s*BCE?$|'
+ '\\d+\\s*AD[\\s,\\.]+|\\d+\\s*AD$|'
+ 'AD\\s*\\d+[\\s,\\.]+|AD\\s*\\d+$|'
+ '\\d+\\s*CE[\\s,\\.]+|\\d+\\s*CE$|'
+ '\\d+[\\s,\\.]+|\\d+$)';
var mmid = '(' + _mn_l.join('|') + '|'
+ _mn_s.join('|') + ')[\\s,\\.]+';
var mend = '(' + _mn_l.join('[\\s,\\.]+|') + '[\\s,\\.]+|'
+ _mn_l.join('$|') + '$|'
+ _mn_s.join('[\\s,\\.]+|') + '[\\s,\\.]+|'
+ _mn_s.join('$|') + '$)';
var dmid = '(\\d{1,2})[\\s,\\.]+';
var dend = '(\\d{1,2}[\\s,\\.]+|\\d{1,2}$)';
var tail = '(.*)'; //[\\s,\\.]*(.*)';
s = '^('+_dn_l.join('[\\s,\\.]+|') + '[\\s,\\.]+|' +
_dn_s.join('[\\s,\\.]+|') + '[\\s,\\.]+|)';
YMd = new RegExp( s + ymid + mmid + dend + tail, 'i' );
dMY = new RegExp( s + dmid + mmid + yend + tail, 'i' );
MdY = new RegExp( s + mmid + dmid + yend + tail, 'i' );
Md = new RegExp( s + mmid + dend + tail, 'i' );
dM = new RegExp( s + dmid + mend + tail, 'i' );
delete ymid; delete yend;
delete mmid; delete mend;
delete dmid; delete dend;
delete tail;
})();
// +++++++++ walk the nodes ++++++++++
if (with_xpath)
dlist_len = dlist.snapshotLength;
else if (running_all) // collection of *all* elements
dlist_len = running_all; // may change inside the loop
else
dlist_len = dlist.length;
tslice = 10;
ce_mode = '';
idx = 0;
if (arguments.callee.last_idx) { //defined && != 0
if (arguments.callee.last_len === dlist_len)
idx = arguments.callee.last_idx;
ce_mode = arguments.callee.sav_ce_mode;
tslice = arguments.callee.sav_tslice;
}
else if (typeof(window._wpAutoDate_throttling) === 'number' ||
typeof(window._wpAutoDate_throttling) === 'boolean') {
if (!(window._wpAutoDate_throttling)) {
tslice = 0; //throttling is enabled unless explicitly disabled
}
}
for (idx = idx; idx < dlist_len; idx++) {
// other than temps ('ar', 'i' and 's') and 'ce_mode',
// all vars decl'd outside the loop are 'const' inside it
var weekday, timestr;
var out_datefmt;
var short_mname;
var ord_suffix;
var linkage_needed;
var orig_s;
var y, m, d;
var elem;
if (with_xpath) {
elem = dlist.snapshotItem(idx);
} else if (!running_all) {
elem = dlist[idx];
} else { // optimization around crappy GC
if (idx === (dlist_len-1)) { //length changes when
dlist_len = document.all.length; //inserting links
} //reading document.all.length is a very expensive operation
elem = document.all[idx];
if (typeof(elem.nodeName) !== 'string')
continue; //faster than 'if ("nodeName" in elem)'
if (elem.nodeName !== 'SPAN')
continue;
ar = elem;
while (ar && ar !== dlist)
ar = ar.parentNode;
if (ar !== dlist)
continue;
}
if (tslice && running_all && elem.wpAutoDate)
continue; //already done this
// ++++++++ match classname ++++++++
if (!elem.className)
continue; //no classname
s = ' ' + elem.className + ' ';
ar = ['wpAutoDate'];
y = false;
out_datefmt = datefmt;
for (d = (ar.length-1); d >= 0; d--) {
i = s.indexOf(' ' + ar[d] );
if (i === -1)
continue;
i += 1 + ar[d].length;
if (s.charAt(i) === ' ') {
y = true; continue;
}
if (d >= 2)
continue;
if (s.charAt(i) != '-')
continue;
if ((i + 4) >= s.length)
continue;
if (s.charAt(i + 4) != ' ')
continue;
if (("dmy|mdy|ymd|iso".indexOf(s.substr( i + 1, 3)) ) != -1)
out_datefmt = s.substr( i + 1, 3 );
y = true;
break;
}
if (!y) { //not in class
if (tslice && running_all)
elem.wpAutoDate = true; //flag the element as "done"
continue;
}
// ++++++++ throttling check ++++++++
// throttling
if (tslice
&& (profcntr_1 >= tslice) // not too often
&& ((dlist_len - idx) > 5) ){ // not about to finish
i = fn_now() - start_time;
profcntr_2++;
d = profcntr_1; //num conversions so far
m = 2; //num conversions till next timeout check
if (i > 225) {
if (d) {
m = (250 / (i / d)) >> 0; // ">>0" to truncate
if (m < 1)
m = 1;
}
tslice = m; //projected # of iters per 250ms
break; //NB: this is the only 'break' in this loop
}
if (d) {
m = (i / d); // msecs per conversion
if (m > 0)
m = ((250 - i) / m) >> 0;
if (m < 1) // # of conversions in remaining time
m = 1;
}
tslice = d + m;
}
// +++++ element is going to modified for sure +++++
profcntr_1++;
if (tslice && running_all)
elem.wpAutoDate = true; //flag the element as "done"
// +++++++ load the span's contents and flags ++++++++
ar = [false, false];
orig_s = fn_3in1(elem, ar); // ~25% of the time is spent here
linkage_needed = ar[0];
if (ar[1]) { //contains nested thingies other than <a>..</a>
_bad_date(elem, orig_s, "nested markup");
continue; //typically markup, <b> or <i> or something.
}
// ++++++++++ load from title= +++++++++++++
if (elem.hasAttribute) {
if (elem.hasAttribute('title')) // DOM core
s = elem.getAttribute('title');
else
s = '';
} else { // non-compliant browser
if (typeof(elem.title) == 'string')
s = elem.title;
else
s = '';
}
d = m = y = 0;
weekday = timestr = '';
short_mname = false;
ord_suffix = false;
if (s.length) { // got title=
orig_s = s; //contents of title=
if (has_buggy_s)
s = s.replace(/[\xA0\t\r\n\u00a0]/g, ' ');
//fallthrough to iso
} else { // ++++++++++ parse <span> contents ++++++++++
//
// no title= means no YYYY-MM-DD override, so
// use the span's contents.
//
s = orig_s; //contents of span
if (has_buggy_s)
s = s.replace(/[\xA0\t\r\n\u00a0]/g, ' ');
if (s.length) {
//normalize wikidate to dMY.
if (s.length >= 19 && s.charAt(2) == ':' &&
s.substr(s.length-5,5) == '(UTC)') {
s = s.replace(dclean, ' '); //works because no frac
s = s.substr(6, s.length-(6+5)) +
s.substr(0,6) + s.substr(s.length-5,5);
} else { //strip ordinal suffix
if ((i = s.indexOf('1st')) != -1) i++;
else if ((i = s.indexOf('2nd')) != -1) i++;
else if ((i = s.indexOf('3rd')) != -1) i++;
else if ((i = s.indexOf('th', 1)) <= 0) i = -1;
// ,1 disqualifies "thursday" (the only n with 'th')
else if (("0123456789".indexOf(s.charAt(i-1))) == -1)
i = -1;
if (i >= 1) {
if (s.length == (i+2) ||
/[\.,\s]/.test(s.charAt(i+2)) ) {
s = s.substr(0, i) + s.substr(i+2);
ord_suffix = true;
}
}
}
ar = s.match(dMY);
if (ar) {
d = 2; m = 3; y = 4;
} else if (!(!(ar = s.match(YMd)))) {
y = 2; m = 3; d = 4;
} else if (!(!(ar = s.match(MdY)))) {
m = 2; d = 3; y = 4;
} else if (!(!(ar = s.match(dM)))) {
d = 2; m = 3;
} else if (!(!(ar = s.match(Md)))) {
m = 2; d = 3;
}
if (d) { // got some match
//strip day-field whitespace first.
//the field is referred to in Y checking
ar[d] = ar[d].replace(dclean, '');
if (y) { // have a year
s = ar[y];
if (s.substr(0,2) === 'AD') //leading 'AD'
s = s.substr(2).replace(/^\s+/,'') + 'AD';
i = 0; // num digits
while (i < s.length &&
("0123456789".indexOf(s.charAt(i))) != -1) {
i++;
}
if (i === 0 || i > 4) {
_bad_date(elem,orig_s,"too many digits in year");
continue; // num is invalid
}
y = parseInt(s.substr(0,i), 10);
if (s.length > i) { //may have AD/BC/CE/BCE
s = s.substr(i).replace(dclean, '');
if (s.length) {
if (s === 'BCE' || s === 'BC') {
y = -y;
} else if (s !== 'AD' && s !== 'CE') {
_bad_date(elem,orig_s,"'"+s+"'");
continue; // num is invalid
}
if (ce_mode === '') { // mode not determined?
if (s.charAt(s.length-1) === 'E')
ce_mode = 'BCE'; //found 'bce' or 'ce'
else
ce_mode = 'BC';
}
i = 3; //fake the yearlen as >= 3 since
// day/year are not ambiguous when year
// has ad/ce (bc/bce) qualifier
}
}
if ((m === 3) // month-field between day and year
&& i < 3 && y > 0 && y <= 31) {
//(ymd or dmy) && yearlen < 3 && yearval <= 31
// => day and year fields are ambiguous
i = parseInt( ar[d], 10 );
if (i <= 0 || (i <= 31 && ar[d].length < 3)) {
// dayval <= 31 && daylen < 3 digits
s = "ambiguous year/day";
if (i <= 0)
s = "zero day";
_bad_date(elem, orig_s, s);
continue;
}
// year and day need to be swapped
ar[d] = y.toString(); //set new value for day
y = i; //previously loaded day val
}
if (y === 0) {
_bad_date(elem,orig_s,"zero year/day");
continue;
}
} // have a year field
if (ar[d].length > 2) { //day field > 2 digits?
// cosmic rays?
_bad_date(elem,orig_s,"too many digits in day");
continue;
}
d = parseInt( ar[d], 10 );
// convert month name to month #
s = ar[m].replace(dclean,'');
s = s.substr(0,1).toUpperCase() +
s.substr(1,s.length-1).toLowerCase();
m = 0; // assume error
for (i = 0; i < 12; i++) {
if (s === _mn_l[i]) {
short_mname = false;
m = i+1;
break;
} else if (s === _mn_s[i]) {
short_mname = true;
//short_mname is not set when long == short
m = i+1;
break;
}
}
if (m === 0) {
for (i = 12; i < _mn_s.length; i++) {
if (s !== _mn_s[i])
continue;
for (i = 0; i < 12; i++) {
if (s === _mn_l[i].substr(0, s.length)) {
short_mname = true;
m = i+1;
break;
}
}
break;
}
}
if (ar[1].length) { //weekday was provided
s = ar[1].replace(dclean,'');
s = s.substr(0,1).toUpperCase() +
s.substr(1,s.length-1).toLowerCase();
for (i = 0; i < 7; i++) {
if (s === _dn_l[i] || s === _dn_s[i])
break;
}
if (i >= 7) {
for (i == (_dn_s.length - 1); i >= 7; i--) {
if (s === _dn_s[i]) {
for (i = 0; i < 7; i++) {
if (s === _dn_l[i].substr(0, s.length) )
break;
}
break;
}
}
}
if (i < 7) {
if (short_mname && _dn_s[i] !== _dn_l[i])
weekday = _dn_s[i] + '., '
else
weekday = _dn_l[i] + ', '
}
}
//what follows comes last since it trashes 'ar'
if (ar[ar.length-1].length) { // have time?
s = ar[ar.length-1].replace(/^\s+/g,'');
ar = s.match(
/^(T\s*|)(\d{1,2}:\d{2}|)(\:\d{2}|)([,\.]\d+|)\s*(.*)/);
if (!ar) {
//fallthrough
} else if (ar[2].length === 0 && (ar[3].length ||
ar[4].length || ar[1].length)) {
_bad_date(elem,orig_s,"missing hours:minutes");
continue;
} else {
// normalize timestring as ISO-style
// note: its possible that all 3 are "" here
if (ar[2].length === 4) //hours needs padding
ar[2] = '0' + ar[2];
if (ar[4].charAt(0) === ',')
ar[4] = '.' + ar[4].substr(1);
if (ar[2].length) // got anything?
timestr = 'T' + ar[2] + ar[3] + ar[4];
s = ar[5];
}
if (s.length) {
if (s.charAt(0) === '(' &&
s.charAt(s.length-1) === ')') {
s = s.substr(1,s.length-2);
}
ar = s.match(
/^([A-Za-z\(\)]*|)\s*([\+\-]\d{1,4}|)(\:\d{2}|)\s*$/ );
if (!ar) {
//fallthrough
} else if (ar[1].length) {
i = ar[1].indexOf(')');
if (i !== -1 && i !== (ar[1].length-1))
ar = null;
else if (ar[1].lastIndexOf('(') > 0)
ar = null;
else if (i!==-1 && ar[1].indexOf('(')===-1)
ar = null;
else if (i===-1 && ar[1].indexOf('(')!==-1)
ar = null;
else {
if (i !== -1)
ar[1] = ar[1].substr(1,ar[1].length-2);
if (ar[1] !== 'UTC' && ar[1] !== 'GMT' &&
ar[1] !== 'Z') {
_bad_date(elem,orig_s,"timezone isn't"+
" 'UTC', 'GMT' or 'Z'");
continue;
}
if (i!==-1 && (ar[2].length||ar[3].length)){
//parenthesized tzname is followed
//by something.
_bad_date(elem,orig_s,
"gunk after '("+ar[1]+")'");
continue;
}
}
}
if (!ar) {
//fallthrough
} else if (ar[3].length===0 && ar[2].length===0){
//nothing - no hour and no minute
} else if (ar[3].length && ar[2].length === 0) {
ar = null; //min but no hour
} else if (ar[3].length && ar[2].length >= 4) {
ar = null; //hour too long when minute
} else if (ar[3].length===0 && ar[2].length>=4) {
s = ar[2]; //simple +NNN or +NNNN
ar[3] = ':' + s.substr( s.length - 2, 2);
ar[2] = s.substr(0, s.length - 2);
}
if (!ar) {
_bad_date(elem,orig_s,"not a time/timezone");
continue;
}
if (timestr.length === 0) //timezone but no time
timestr = 'T00:00'; //implies midnight
//normalize the timezone
timestr = timestr + 'Z'; // also marks splitpoint
if (ar[2].length) { //got an offset too?
if (ar[2].length === 2) //hours not '0'-padded
ar[2]=ar[2].charAt(0)+'0'+ar[2].charAt(1);
if (ar[3].length === 0)
ar[3] = ':00';
timestr = timestr + ar[2] + ar[3];
}
} // got tz
} // have timestr
s = ''; // don't do any more date matching
} // matched Md(Y) or dM(Y) or YMd
} // anything in the span?
} // have title= or not
// ++++++++++ iso 8901 matching +++++++++++
if (s.length) { // need to try iso
ar = s.replace(/\s+/g, ' ').match(iso);
//the replace() is needed to catch newlines
if (ar) {
if (ar[1].charAt(0) !== '-' && ar[1].charAt(0) !== '+')
y = parseInt( ar[1], 10 );
else {
y = parseInt( ar[1].substr(1,ar[1].length-1), 10);
if (ar[1].charAt(0) === '-')
y = -y;
}
m = parseInt( ar[2], 10 );
d = parseInt( ar[3], 10 );
timestr = ar[ar.length-1].replace(/\s+/g,'');
if (timestr.length) {
//per http://www.w3.org/TR/NOTE-datetime
//which is a subset of ISO 8601/EN 28601
ar = timestr.match(
/^(T\d\d:\d\d)(:\d\d|)([\.,]\d+|)(Z|\+\d\d:\d\d|\-\d\d:\d\d|)(.*)/ );
// 1 2 3 4 5
if (!ar || (ar && ar[ar.length-1].length !== 0)) {
_bad_date(elem,orig_s,'trail not iso');
continue;
}
if (ar[3].charAt(0) === ',')
ar[3] = '.' + ar[3].substr(1,ar[3].length-1);
if (ar[4].length > 1) { // have tzoff? (not just 'Z')
//add 'Z' split point mark for normalized timestr
ar[4] = 'Z' + ar[4];
}
timestr = ar[1] + ar[2] + ar[3] + ar[4];
} // got time
s = ''; // don't do any more matching
} // iso regex match
} // end try iso
// ++++++++++ basic validation of input +++++++++++
if (s.length) {
_bad_date(elem,orig_s, "unsupported format");
continue;
}
if (m < 1 || m > 12) {
_bad_date(elem,orig_s,"month # of range");
continue;
}
if (d < 1 || d > 31) {
_bad_date(elem,orig_s,"day # out of range");
continue;
}
if ((m === 2 && d > 29) || d > (30+ ((m - (m>>3)) & 1)) ) {
_bad_date(elem,orig_s,"invalid day for month");
continue;
}
if (timestr.length) {
// this was originally a check during development
// but may as well keep it for live use too
ar = timestr.match( normalized_time );
if (ar) {
if (parseInt(ar[1], 10) >= 24)
ar = null; //bad hours
else if (parseInt(ar[2],10) >= 60)
ar = null; //bad minutes
else if (ar[3].length && parseInt(ar[3].substr(1,2),10)>60)
ar = null; //bad seconds
else if (ar[3].length && parseInt(
ar[3].substr(1,2),10) === 60 &&
parseInt(ar[2],10) !== 59 &&
parseInt(ar[2],10) !== 23)
ar = null; //leap sec only valid for 23:59
else if (ar[5].length > 1) { //got 'Z+dd:dd'
i = parseInt(ar[5].substr(5,2), 10);
if (i >= 60)
i = 2400;
i += 100 * parseInt(ar[5].substr(2,2), 10);
if (ar[5].charAt(1) === '-')
i = -i;
if (i < -1200 || s > 1400)
ar = null;
}
}
if (!ar) {
_bad_date(elem,orig_s,"time/timezone not valid");
continue;
}
} //if timestr
// ++++++++++ convert to target format +++++++++++
(function () // pseudo-locals
{
//
// The code below is specific to the en.wiki.
// It probably will not work on other wikis.
//
var sm, sd, sy; // string forms of mon/day/year
var mdb, mde, yrb, tzb; // <a href="whatever"> and </a>
var nbsp = '\xA0'; //String.fromCharCode(160); // \u00a0
sd = d.toString(); // day num as string
sm = _mn_l[m-1]; // month name (may change below)
sy = s = ''; // assume no year
if (y === 0) {
if (out_datefmt === 'iso' || out_datefmt === 'ymd')
out_datefmt = 'mdy';
} else {
if (y < 100) { // for BC date or for disambiguation/clarity
if (linkage_needed || out_datefmt !== 'iso') {
if (ce_mode === '') { // mode not established yet
if (start_time & 1024) // so pick at "random"
ce_mode = 'BC';
else
ce_mode = 'BCE';
}
if (y < 0)
sy = nbsp + ce_mode;
else if (ce_mode.length === 3)
sy = nbsp + 'CE';
else
sy = nbsp + 'AD';
}
}
i = y;
if (i < 0) {
s = '_BC';
i = -i;
}
sy = i.toString() + sy;//NNN or 10 BC/AD/BCE/CE
s = i.toString() + s; // '10' or '10_BC' (link form)
}
mdb = mde = yrb = tzb = '';
if (linkage_needed) {
if (s) //caution: uses 's' temporary from above
yrb = '<a href="/wiki/' + s + '" ' +
'title="' + sy + '">';
tzb = '<a href="/wiki/Coordinated_Universal_Time" ' +
'title="Coordinated Universal Time">';
mdb = '<a href="/wiki/' + sm + '_' + sd + '" ' +
'title="'+ sm + ' ' + sd + '">';
mde = '</a>';
}
if (short_mname && (sm !== _mn_s[m-1])) {
sm = _mn_s[m-1] + '.';
}
/** disable ordinal suffix output per MOS:DATE
if (ord_suffix && (y === 0 || out_datefmt !== 'iso')) {
if (d >= 4 && d <= 20) {
sd += 'th';
} else {
switch (d % 10) {
case 1: sd += 'st'; break;
case 2: sd += 'nd'; break;
case 3: sd += 'rd'; break;
default: sd += 'th'; break;
}
}
}
**/
if (timestr) { // have time/timezone
i = timestr.indexOf('Z'); //split point mark
if (y !== 0 && out_datefmt === 'iso') {
if (i !== -1) { // got a 'Z'
if (i === (timestr.length - 1)) //only trailing Z
s = tzb + 'Z' + mde;
else //got an offset, so discard the 'Z'
s = timestr.substr(i+1, timestr.length-(i+1));
timestr = timestr.substr(0,i) + s;
}
} else { //non-iso
timestr = ' ' + timestr.substr(1, timestr.length-1);
// timestr now has ' ' instead of 'T'
if (i !== -1) { // got a 'Z'
if (i === (timestr.length - 1)) //only trailing Z
s = nbsp + '(' + tzb + 'UTC' + mde + ')';
else
s = nbsp + tzb + 'UTC' + mde +
timestr.substr(i+1, timestr.length-(i+1));
timestr = timestr.substr(0, i) + s;
}
}
}
s = '';
if (out_datefmt === 'dmy') {
if (y) sy = nbsp + yrb + sy + mde;
s = weekday.concat(mdb, sd, nbsp, sm, mde, sy, timestr);
} else if (out_datefmt === 'mdy' || y === 0) {
if (y) sy = ',' + nbsp + yrb + sy + mde;
s = weekday.concat(mdb, sm, nbsp, sd, mde, sy, timestr);
} else if (out_datefmt === 'ymd') {
//will have year because y === 0 already checked
s = weekday.concat(yrb, sy, mde, nbsp,
mdb, sm, nbsp, sd, mde, timestr);
} else { // iso
//will have year because y === 0 already checked
i = (y < 0 ? -y : y);
sy = i.toString();
if (i < 10) sy = '000' + sy;
else if (i < 100) sy = '00' + sy;
else if (i < 1000) sy = '0' + sy;
if (y < 0) // year is negative
sy = '-' + sy;
sm = m.toString();
if (m < 10) sm = '0' + sm;
if (d < 10) sd = '0' + sd;
//no weekday
s = yrb.concat(sy, mde, '-', mdb, sm, '-', sd, mde,
timestr);
}
})(); // day and month are valid
// 25-30% of the total time is spent here
if (linkage_needed) //need linkage? (classname can be "")
elem.innerHTML = s;
else
fn_settext(elem, s);
} // for (idx = 0; idx < dlist_len; idx++)
if (idx < dlist_len) { //have more to do
arguments.callee.sav_tslice = tslice;
arguments.callee.sav_ce_mode = ce_mode;
arguments.callee.last_idx = idx;
arguments.callee.last_len = dlist_len;
if (false) {
if (typeof(arguments.callee.timer) === 'undefined') {
arguments.callee.timer = setInterval(
function(){wpAutoDate(wpAutoDate.last_idx);}, 100 );
}
} else {
arguments.callee.timer = setTimeout(
function(){wpAutoDate(wpAutoDate.last_idx);}, 100 );
}
} else { // done all
if (typeof(arguments.callee.timer) !== 'undefined') {
clearInterval(arguments.callee.timer);
delete arguments.callee.timer;
}
if (typeof(arguments.callee.coll) === 'object') {
delete arguments.callee.coll;
}
arguments.callee.last_idx = 0;
arguments.callee.last_len = 0;
// fin!
}
dlist = null;
if (window.autodate_profiling) {
ar = fn_now();
if (!(arguments.callee.num_runs)) {
arguments.callee.first_time = ar;
arguments.callee.total_time = 0;
arguments.callee.num_done = 0;
arguments.callee.num_checks = 0;
arguments.callee.num_runs = 0;
}
start_time = (ar - start_time);
arguments.callee.total_time += start_time;
arguments.callee.num_done += profcntr_1;
arguments.callee.num_checks += profcntr_2;
arguments.callee.num_runs++;
//if (idx >= dlist_len)
{
i = profcntr_1;
if (i)
i = Math.round((start_time * 100)/ i) / 100;
s = 'autodate ' + '(' + arguments.callee.num_runs + ') ' +
((idx >= dlist_len) ? ('ran ') :
('running #' + idx + ' of ') )
+ dlist_len;
/*
s += ' (last: ' + start_time //time elapsed this round
+ '@' + profcntr_2
+ ' ' + profcntr_1
+ '@' + i
+ ')';
s += ' (totals: '+ (ar - arguments.callee.first_time)
+ '@' + arguments.callee.num_checks
+ ' ' + arguments.callee.num_done
+ '@' + arguments.callee.total_time
+ ')';
*/
i = arguments.callee.num_done;
if (i)
i = Math.round((arguments.callee.total_time * 100)/i)/100;
// in "a@b x@y" ...
// a: average run time per fn call (target is 250ms)
// b: average # of timeout checks per run (target is 1)
// x: average # of conversions per run
// y: average time of each conversion
s += ' avg: ' + Math.round((arguments.callee.total_time * 100)/
arguments.callee.num_runs) / 100
+ '@' + Math.round((arguments.callee.num_checks * 100)/
arguments.callee.num_runs) / 100
+ ' ' + Math.round((arguments.callee.num_done * 100)/
arguments.callee.num_runs) / 100
+ '@' + i
+ 'ms';
fn_settext(window.autodate_profiling, s );
}
if (idx >= dlist_len) {
arguments.callee.num_runs = 0;
arguments.callee.total_time = 0;
arguments.callee.num_done = 0;
}
} // if (profiling)
} // if (datefmt)
} catch(err) {
if (typeof(arguments.callee.timer) !== 'undefined') {
clearInterval(arguments.callee.timer);
delete arguments.callee.timer;
}
if (typeof(arguments.callee.coll) === 'object') {
delete arguments.callee.coll;
}
arguments.callee.last_idx = 0;
arguments.callee.last_len = 0;
//throw(err); //this should be commented out unless debugging
}
return;
}
if (window.addOnloadHook) {
if (typeof(window.wgAction) === 'string' && window.wgAction !== 'edit')
addOnloadHook(wpAutoDate);
}