Jump to content

User:Fullstop/autodate.js

From Wikipedia, the free encyclopedia
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
//
// 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', '&quot;') + '"' +
            '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 (&nbsp) 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);
}