PageSourceSearch

https://www.ottawacamping.ca/scripts/common/countdown.js

js ottawacamping.ca collected 2026-09-26 16:55:37 UTC 33,726 bytes, 966 lines download raw bytes

1/*  http://keith-wood.name/countdown.html
2    Countdown for jQuery v1.6.3 — optimized vanilla JS rewrite (2026).
3    Original by Keith Wood (kbwood{at}iinet.com.au) January 2008, MIT License.
4    jQuery plugin interface ($.fn.countdown / $.countdown) fully preserved.
5    Vanilla JS ex:  Countdown.init("#defaultCountdown", { until: endDate });
6*/
7
8(function ($) {
9  "use strict";
10
11  var Y = 0,
12    O = 1,
13    W = 2,
14    D = 3,
15    H = 4,
16    M = 5,
17    S = 6;
18
19  // Instance storage — WeakMap avoids jQuery $.data overhead and prevents leaks.
20  // Falls back to an expando property for environments without WeakMap.
21  var _store = typeof WeakMap !== "undefined" ? new WeakMap() : null;
22  var _PROP = "_cdInst_" + Math.random().toString(36).slice(2);
23  function _getData(el) {
24    return _store ? _store.get(el) : el[_PROP];
25  }
26  function _setData(el, v) {
27    _store ? _store.set(el, v) : (el[_PROP] = v);
28  }
29  function _delData(el) {
30    _store ? _store.delete(el) : delete el[_PROP];
31  }
32
33  // ── Shared timer ────────────────────────────────────────────────────────────
34  // Using setInterval at 980 ms instead of a perpetual requestAnimationFrame
35  // loop. rAF fired ~60× per second even when the only work was a timestamp
36  // comparison; that was the primary source of CPU burn.
37  // The timer is stopped entirely when there are no active targets and when the
38  // browser tab is hidden (visibilitychange API).
39
40  var _targets = [];
41  var _timerId = null;
42  var _visHidden = false;
43
44  function _startTimer() {
45    if (_timerId === null && _targets.length > 0 && !_visHidden) {
46      _timerId = setInterval(_tickAll, 980);
47    }
48  }
49
50  function _stopTimer() {
51    if (_timerId !== null) {
52      clearInterval(_timerId);
53      _timerId = null;
54    }
55  }
56
57  function _addTarget(el) {
58    if (_targets.indexOf(el) === -1) {
59      _targets.push(el);
60      _startTimer();
61    }
62  }
63
64  function _removeTarget(el) {
65    var i = _targets.indexOf(el);
66    if (i !== -1) _targets.splice(i, 1);
67    if (_targets.length === 0) _stopTimer();
68  }
69
70  function _hasTarget(el) {
71    return _targets.indexOf(el) !== -1;
72  }
73
74  function _tickAll() {
75    for (var i = _targets.length - 1; i >= 0; i--) {
76      _plugin._updateCountdown(_targets[i]);
77    }
78  }
79
80  // Pause tick loop while the tab is not visible; resume with an immediate
81  // update when it becomes visible again so the display is never stale.
82  if (typeof document.addEventListener === "function") {
83    document.addEventListener("visibilitychange", function () {
84      if (document.hidden) {
85        _visHidden = true;
86        _stopTimer();
87      } else {
88        _visHidden = false;
89        if (_targets.length > 0) {
90          _tickAll();
91          _startTimer();
92        }
93      }
94    });
95  }
96
97  // ── Regex cache ─────────────────────────────────────────────────────────────
98  // _buildLayout previously created `new RegExp(...)` on every call for every
99  // substitution key. Cache them; layouts are finite and keys are static.
100
101  var _reCache = Object.create(null);
102  function _re(pattern, flags) {
103    var key = pattern + "\x00" + (flags || "");
104    if (!_reCache[key]) _reCache[key] = new RegExp(pattern, flags || "");
105    return _reCache[key];
106  }
107
108  // ── Utilities ────────────────────────────────────────────────────────────────
109
110  function _extend(target) {
111    for (var i = 1; i < arguments.length; i++) {
112      var src = arguments[i];
113      if (!src) continue;
114      for (var k in src) {
115        if (Object.prototype.hasOwnProperty.call(src, k)) target[k] = src[k];
116      }
117    }
118    return target;
119  }
120
121  // ── Countdown constructor ────────────────────────────────────────────────────
122
123  function Countdown() {
124    this.regional = [];
125    this.regional[""] = {
126      labels: ["Years", "Months", "Weeks", "DAYS", "HOURS", "MINS", "SECS"],
127      labels1: ["Year", "Month", "Week", "DAY", "HOUR", "MIN", "SECS"],
128      compactLabels: ["y", "m", "w", "d"],
129      whichLabels: null,
130      digits: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"],
131      timeSeparator: ":",
132      isRTL: false
133    };
134    this._defaults = {
135      until: null,
136      since: null,
137      timezone: null,
138      serverSync: null,
139      format: "dHMS",
140      layout: "",
141      compact: false,
142      significant: 0,
143      description: "",
144      expiryUrl: "",
145      expiryText: "",
146      alwaysExpire: false,
147      onExpiry: null,
148      onTick: null,
149      tickInterval: 1
150    };
151    _extend(this._defaults, this.regional[""]);
152    this._serverSyncs = [];
153  }
154
155  _extend(Countdown.prototype, {
156    markerClassName: "hasCountdown clearfix",
157    propertyName: "countdown",
158
159    _rtlClass: "countdown_rtl",
160    _sectionClass: "countdown_section",
161    _amountClass: "countdown_amount",
162    _rowClass: "countdown_row",
163    _holdingClass: "countdown_holding",
164    _showClass: "countdown_show",
165    _descrClass: "countdown_descr",
166
167    /* Override the default settings for all instances of the countdown widget.
168       @param  options  (object) the new settings to use as defaults */
169    setDefaults: function (options) {
170      this._resetExtraLabels(this._defaults, options);
171      _extend(this._defaults, options || {});
172    },
173
174    /*  Convert a date/time to UTC.
175        @param  tz      (number) the hour or minute offset from GMT, e.g. +9, -360
176        @param  year    (Date) the date/time in that timezone or
177                        (number) the year in that timezone
178        @param  month   (number, optional) the month (0–11)
179        @param  day     (number, optional) the day
180        @param  hours   (number, optional) the hour
181        @param  mins    (number, optional) the minute
182        @param  secs    (number, optional) the second
183        @param  ms      (number, optional) the millisecond
184        @return  (Date) the equivalent UTC date/time
185    */
186    UTCDate: function (tz, year, month, day, hours, mins, secs, ms) {
187      if (typeof year === "object" && year instanceof Date) {
188        ms = year.getMilliseconds();
189        secs = year.getSeconds();
190        mins = year.getMinutes();
191        hours = year.getHours();
192        day = year.getDate();
193        month = year.getMonth();
194        year = year.getFullYear();
195      }
196      var d = new Date();
197      d.setUTCFullYear(year);
198      d.setUTCDate(1);
199      d.setUTCMonth(month || 0);
200      d.setUTCDate(day || 1);
201      d.setUTCHours(hours || 0);
202      d.setUTCMinutes((mins || 0) - (Math.abs(tz) < 30 ? tz * 60 : tz));
203      d.setUTCSeconds(secs || 0);
204      d.setUTCMilliseconds(ms || 0);
205      return d;
206    },
207
208    /*  Convert a set of periods into seconds (averaged for months and years).
209        @param  periods  (number[7]) periods per year/month/week/day/hour/minute/second
210        @return  (number) the corresponding number of seconds
211    */
212    periodsToSeconds: function (periods) {
213      return (
214        periods[0] * 31557600 +
215        periods[1] * 2629800 +
216        periods[2] * 604800 +
217        periods[3] * 86400 +
218        periods[4] * 3600 +
219        periods[5] * 60 +
220        periods[6]
221      );
222    },
223
224    /*  Attach the countdown widget to a div.
225        @param  target   (element) the containing division
226        @param  options  (object) the initial settings for the countdown */
227    _attachPlugin: function (target, options) {
228      if (target.classList.contains("hasCountdown")) return;
229      var inst = { options: _extend({}, this._defaults), _periods: [0, 0, 0, 0, 0, 0, 0] };
230      target.classList.add("hasCountdown", "clearfix");
231      _setData(target, inst);
232      this._optionPlugin(target, options);
233    },
234
235    /*  Reconfigure the settings for a countdown div.
236        @param  target   (element) the control to affect
237        @param  options  (object) the new options or (string) an individual property name
238        @param  value    (any) the individual property value
239        @return  (any) if retrieving a value
240    */
241    _optionPlugin: function (target, options, value) {
242      var inst = _getData(target);
243      if (!options || (typeof options === "string" && value == null)) {
244        var opts = (inst || {}).options;
245        return opts && options ? opts[options] : opts;
246      }
247      if (!target.classList.contains("hasCountdown")) return;
248      options = options || {};
249      if (typeof options === "string") {
250        var o = {};
251        o[options] = value;
252        options = o;
253      }
254      if (options.layout) {
255        options.layout = options.layout.replace(/&lt;/g, "<").replace(/&gt;/g, ">");
256      }
257      this._resetExtraLabels(inst.options, options);
258      var tzChanged = inst.options.timezone !== options.timezone;
259      _extend(inst.options, options);
260      this._adjustSettings(
261        target,
262        inst,
263        options.until != null || options.since != null || tzChanged
264      );
265      var now = new Date();
266      if ((inst._since && inst._since < now) || (inst._until && inst._until > now)) {
267        _addTarget(target);
268      }
269      this._updateCountdown(target, inst);
270    },
271
272    /* Redisplay the countdown with an updated display.
273       @param  target  (element) the containing division
274       @param  inst    (object) the current settings for this instance */
275    _updateCountdown: function (target, inst) {
276      inst = inst || _getData(target);
277      if (!inst) return;
278
279      var html = this._generateHTML(inst);
280      var rtlNow = inst.options.isRTL;
281
282      // Only write to the DOM when content actually changed — avoids unnecessary
283      // reflow/repaint on every tick when the displayed value is identical.
284      if (inst._lastHTML !== html || inst._lastRTL !== rtlNow) {
285        target.innerHTML = html;
286        target.classList.toggle(this._rtlClass, !!rtlNow);
287        inst._lastHTML = html;
288        inst._lastRTL = rtlNow;
289      }
290
291      if (typeof inst.options.onTick === "function") {
292        var periods =
293          inst._hold !== "lap"
294            ? inst._periods
295            : this._calculatePeriods(inst, inst._show, inst.options.significant, new Date());
296        if (
297          inst.options.tickInterval === 1 ||
298          this.periodsToSeconds(periods) % inst.options.tickInterval === 0
299        ) {
300          inst.options.onTick.call(target, periods);
301        }
302      }
303
304      var expired =
305        inst._hold !== "pause" &&
306        (inst._since
307          ? inst._now.getTime() < inst._since.getTime()
308          : inst._now.getTime() >= inst._until.getTime());
309
310      if (expired && !inst._expiring) {
311        inst._expiring = true;
312        if (_hasTarget(target) || inst.options.alwaysExpire) {
313          _removeTarget(target);
314          if (typeof inst.options.onExpiry === "function") {
315            inst.options.onExpiry.call(target);
316          }
317          if (inst.options.expiryText) {
318            var savedLayout = inst.options.layout;
319            inst.options.layout = inst.options.expiryText;
320            this._updateCountdown(target, inst);
321            inst.options.layout = savedLayout;
322          }
323          if (inst.options.expiryUrl) {
324            window.location = inst.options.expiryUrl;
325          }
326        }
327        inst._expiring = false;
328      } else if (inst._hold === "pause") {
329        _removeTarget(target);
330      }
331
332      _setData(target, inst);
333    },
334
335    /* Reset any extra labelsN and compactLabelsN entries if changing labels.
336       @param  base     (object) the options to be updated
337       @param  options  (object) the new option values */
338    _resetExtraLabels: function (base, options) {
339      var changingLabels = false;
340      var n;
341      for (n in options) {
342        if (n !== "whichLabels" && /[Ll]abels/.test(n)) {
343          changingLabels = true;
344          break;
345        }
346      }
347      if (changingLabels) {
348        for (n in base) {
349          if (/[Ll]abels[02-9]|compactLabels1/.test(n)) base[n] = null;
350        }
351      }
352    },
353
354    /* Calculate internal settings for an instance.
355       @param  target  (element) the containing division
356       @param  inst    (object) the current settings for this instance
357       @param  recalc  (boolean) true if until or since are set */
358    _adjustSettings: function (target, inst, recalc) {
359      var now,
360        serverOffset = 0,
361        serverEntry = null;
362      for (var i = 0; i < this._serverSyncs.length; i++) {
363        if (this._serverSyncs[i][0] === inst.options.serverSync) {
364          serverEntry = this._serverSyncs[i][1];
365          break;
366        }
367      }
368      if (serverEntry != null) {
369        serverOffset = inst.options.serverSync ? serverEntry : 0;
370        now = new Date();
371      } else {
372        var serverResult =
373          typeof inst.options.serverSync === "function"
374            ? inst.options.serverSync.call(target)
375            : null;
376        now = new Date();
377        serverOffset = serverResult ? now.getTime() - serverResult.getTime() : 0;
378        this._serverSyncs.push([inst.options.serverSync, serverOffset]);
379      }
380      var timezone = inst.options.timezone;
381      timezone = timezone == null ? -now.getTimezoneOffset() : timezone;
382      if (recalc || (!recalc && inst._until == null && inst._since == null)) {
383        inst._since = inst.options.since;
384        if (inst._since != null) {
385          inst._since = this.UTCDate(timezone, this._determineTime(inst._since, null));
386          if (inst._since && serverOffset) {
387            inst._since.setMilliseconds(inst._since.getMilliseconds() + serverOffset);
388          }
389        }
390        inst._until = this.UTCDate(timezone, this._determineTime(inst.options.until, now));
391        if (serverOffset) {
392          inst._until.setMilliseconds(inst._until.getMilliseconds() + serverOffset);
393        }
394      }
395      inst._show = this._determineShow(inst);
396    },
397
398    /* Remove the countdown widget from a div.
399       @param  target  (element) the containing division */
400    _destroyPlugin: function (target) {
401      if (!target.classList.contains("hasCountdown")) return;
402      _removeTarget(target);
403      target.classList.remove("hasCountdown", "clearfix");
404      target.innerHTML = "";
405      _delData(target);
406    },
407
408    /* Pause a countdown widget at the current time. */
409    _pausePlugin: function (target) {
410      this._hold(target, "pause");
411    },
412
413    /* Stop the display but keep the countdown running. */
414    _lapPlugin: function (target) {
415      this._hold(target, "lap");
416    },
417
418    /* Resume a paused countdown widget. */
419    _resumePlugin: function (target) {
420      this._hold(target, null);
421    },
422
423    /* Pause or resume a countdown widget.
424       @param  target  (element) the containing division
425       @param  hold    (string) the new hold setting */
426    _hold: function (target, hold) {
427      var inst = _getData(target);
428      if (!inst) return;
429      if (inst._hold === "pause" && !hold) {
430        inst._periods = inst._savePeriods;
431        var sign = inst._since ? "-" : "+";
432        var p = inst._periods;
433        inst[inst._since ? "_since" : "_until"] = this._determineTime(
434          sign +
435            p[0] +
436            "y" +
437            sign +
438            p[1] +
439            "o" +
440            sign +
441            p[2] +
442            "w" +
443            sign +
444            p[3] +
445            "d" +
446            sign +
447            p[4] +
448            "h" +
449            sign +
450            p[5] +
451            "m" +
452            sign +
453            p[6] +
454            "s"
455        );
456        _addTarget(target);
457      }
458      inst._hold = hold;
459      inst._savePeriods = hold === "pause" ? inst._periods : null;
460      _setData(target, inst);
461      this._updateCountdown(target, inst);
462    },
463
464    /* Return the current time periods.
465       @param  target  (element) the containing division
466       @return  (number[7]) the current periods for the countdown */
467    _getTimesPlugin: function (target) {
468      var inst = _getData(target);
469      return !inst
470        ? null
471        : inst._hold === "pause"
472          ? inst._savePeriods
473          : !inst._hold
474            ? inst._periods
475            : this._calculatePeriods(inst, inst._show, inst.options.significant, new Date());
476    },
477
478    /* Determine the date/time from a relative or absolute setting.
479       @param  setting      (string|number|Date) the date/time value
480       @param  defaultTime  (Date) the date/time to use if no other is supplied
481       @return  (Date) the corresponding date/time */
482    _determineTime: function (setting, defaultTime) {
483      var offsetNumeric = function (offset) {
484        var t = new Date();
485        t.setTime(t.getTime() + offset * 1000);
486        return t;
487      };
488      var offsetString = function (offset) {
489        offset = offset.toLowerCase();
490        var t = new Date();
491        var year = t.getFullYear();
492        var month = t.getMonth();
493        var day = t.getDate();
494        var hour = t.getHours();
495        var minute = t.getMinutes();
496        var second = t.getSeconds();
497        var pattern = /([+-]?[0-9]+)\s*(s|m|h|d|w|o|y)?/g;
498        var matches;
499        while ((matches = pattern.exec(offset)) !== null) {
500          var n = parseInt(matches[1], 10);
501          switch (matches[2] || "s") {
502            case "s":
503              second += n;
504              break;
505            case "m":
506              minute += n;
507              break;
508            case "h":
509              hour += n;
510              break;
511            case "d":
512              day += n;
513              break;
514            case "w":
515              day += n * 7;
516              break;
517            case "o":
518              month += n;
519              day = Math.min(day, _plugin._getDaysInMonth(year, month));
520              break;
521            case "y":
522              year += n;
523              day = Math.min(day, _plugin._getDaysInMonth(year, month));
524              break;
525          }
526        }
527        return new Date(year, month, day, hour, minute, second, 0);
528      };
529      var time =
530        setting == null
531          ? defaultTime
532          : typeof setting === "string"
533            ? offsetString(setting)
534            : typeof setting === "number"
535              ? offsetNumeric(setting)
536              : setting;
537      if (time) time.setMilliseconds(0);
538      return time;
539    },
540
541    /* Determine the number of days in a month.
542       @param  year   (number) the year
543       @param  month  (number) the month
544       @return  (number) the days in that month */
545    _getDaysInMonth: function (year, month) {
546      return 32 - new Date(year, month, 32).getDate();
547    },
548
549    /* Determine which set of labels should be used for an amount. */
550    _normalLabels: function (num) {
551      return num;
552    },
553
554    /* Generate the HTML to display the countdown widget.
555       @param  inst  (object) the current settings for this instance
556       @return  (string) the new HTML for the countdown display */
557    _generateHTML: function (inst) {
558      var self = this;
559      inst._periods = inst._hold
560        ? inst._periods
561        : this._calculatePeriods(inst, inst._show, inst.options.significant, new Date());
562
563      var shownNonZero = false,
564        showCount = 0,
565        sigCount = inst.options.significant;
566      var show = inst._show.slice();
567      var period;
568      for (period = Y; period <= S; period++) {
569        shownNonZero = shownNonZero || (inst._show[period] === "?" && inst._periods[period] > 0);
570        show[period] = inst._show[period] === "?" && !shownNonZero ? null : inst._show[period];
571        if (show[period]) showCount++;
572        if (inst._periods[period] > 0) sigCount--;
573      }
574
575      var showSignificant = [false, false, false, false, false, false, false];
576      for (period = S; period >= Y; period--) {
577        if (inst._show[period]) {
578          if (inst._periods[period]) {
579            showSignificant[period] = true;
580          } else {
581            showSignificant[period] = sigCount > 0;
582            sigCount--;
583          }
584        }
585      }
586
587      var labels = inst.options.compact ? inst.options.compactLabels : inst.options.labels;
588      var whichLabels = inst.options.whichLabels || this._normalLabels;
589
590      if (inst.options.layout) {
591        return this._buildLayout(
592          inst,
593          show,
594          inst.options.layout,
595          inst.options.compact,
596          inst.options.significant,
597          showSignificant
598        );
599      }
600
601      var descHtml = inst.options.description
602        ? '<span class="' +
603          this._rowClass +
604          " " +
605          this._descrClass +
606          '">' +
607          inst.options.description +
608          "</span>"
609        : "";
610      var holdClass = inst._hold ? " " + this._holdingClass : "";
611
612      if (inst.options.compact) {
613        var showCompact = function (p) {
614          var labelsNum = inst.options["compactLabels" + whichLabels(inst._periods[p])];
615          return show[p]
616            ? self._translateDigits(inst, inst._periods[p]) +
617                (labelsNum ? labelsNum[p] : labels[p]) +
618                " "
619            : "";
620        };
621        return (
622          '<span class="' +
623          this._rowClass +
624          " " +
625          this._amountClass +
626          holdClass +
627          '">' +
628          showCompact(Y) +
629          showCompact(O) +
630          showCompact(W) +
631          showCompact(D) +
632          (show[H] ? this._minDigits(inst, inst._periods[H], 2) : "") +
633          (show[M]
634            ? (show[H] ? inst.options.timeSeparator : "") +
635              this._minDigits(inst, inst._periods[M], 2)
636            : "") +
637          (show[S]
638            ? (show[H] || show[M] ? inst.options.timeSeparator : "") +
639              this._minDigits(inst, inst._periods[S], 2)
640            : "") +
641          "</span>" +
642          descHtml
643        );
644      }
645
646      var showFull = function (p) {
647        var labelsNum = inst.options["labels" + whichLabels(inst._periods[p])];
648        return (!inst.options.significant && show[p]) ||
649          (inst.options.significant && showSignificant[p])
650          ? '<span class="' +
651              self._sectionClass +
652              '">' +
653              '<span class="' +
654              self._amountClass +
655              '">' +
656              self._translateDigits(inst, inst._periods[p]) +
657              "</span><br/>" +
658              (labelsNum ? labelsNum[p] : labels[p]) +
659              "</span>"
660          : "";
661      };
662
663      return (
664        '<span class="' +
665        this._rowClass +
666        " " +
667        this._showClass +
668        (inst.options.significant || showCount) +
669        holdClass +
670        '">' +
671        showFull(Y) +
672        showFull(O) +
673        showFull(W) +
674        showFull(D) +
675        showFull(H) +
676        showFull(M) +
677        showFull(S) +
678        "</span>" +
679        descHtml
680      );
681    },
682
683    /* Construct a custom layout.
684       @param  inst             (object) the current settings for this instance
685       @param  show             (string[7]) flags indicating which periods are requested
686       @param  layout           (string) the customised layout
687       @param  compact          (boolean) true if using compact labels
688       @param  significant      (number) the number of periods with values to show
689       @param  showSignificant  (boolean[7]) other periods to show for significance
690       @return  (string) the custom HTML */
691    _buildLayout: function (inst, show, layout, compact, significant, showSignificant) {
692      var labels = inst.options[compact ? "compactLabels" : "labels"];
693      var whichLabels = inst.options.whichLabels || this._normalLabels;
694      var self = this;
695
696      var labelFor = function (i) {
697        return (inst.options[
698          (compact ? "compactLabels" : "labels") + whichLabels(inst._periods[i])
699        ] || labels)[i];
700      };
701      var digit = function (value, position) {
702        return inst.options.digits[Math.floor(value / position) % 10];
703      };
704
705      var p = inst._periods;
706      var subs = {
707        desc: inst.options.description,
708        sep: inst.options.timeSeparator,
709        yl: labelFor(Y),
710        yn: self._minDigits(inst, p[Y], 1),
711        ynn: self._minDigits(inst, p[Y], 2),
712        ynnn: self._minDigits(inst, p[Y], 3),
713        y1: digit(p[Y], 1),
714        y10: digit(p[Y], 10),
715        y100: digit(p[Y], 100),
716        y1000: digit(p[Y], 1000),
717        ol: labelFor(O),
718        on: self._minDigits(inst, p[O], 1),
719        onn: self._minDigits(inst, p[O], 2),
720        onnn: self._minDigits(inst, p[O], 3),
721        o1: digit(p[O], 1),
722        o10: digit(p[O], 10),
723        o100: digit(p[O], 100),
724        o1000: digit(p[O], 1000),
725        wl: labelFor(W),
726        wn: self._minDigits(inst, p[W], 1),
727        wnn: self._minDigits(inst, p[W], 2),
728        wnnn: self._minDigits(inst, p[W], 3),
729        w1: digit(p[W], 1),
730        w10: digit(p[W], 10),
731        w100: digit(p[W], 100),
732        w1000: digit(p[W], 1000),
733        dl: labelFor(D),
734        dn: self._minDigits(inst, p[D], 1),
735        dnn: self._minDigits(inst, p[D], 2),
736        dnnn: self._minDigits(inst, p[D], 3),
737        d1: digit(p[D], 1),
738        d10: digit(p[D], 10),
739        d100: digit(p[D], 100),
740        d1000: digit(p[D], 1000),
741        hl: labelFor(H),
742        hn: self._minDigits(inst, p[H], 1),
743        hnn: self._minDigits(inst, p[H], 2),
744        hnnn: self._minDigits(inst, p[H], 3),
745        h1: digit(p[H], 1),
746        h10: digit(p[H], 10),
747        h100: digit(p[H], 100),
748        h1000: digit(p[H], 1000),
749        ml: labelFor(M),
750        mn: self._minDigits(inst, p[M], 1),
751        mnn: self._minDigits(inst, p[M], 2),
752        mnnn: self._minDigits(inst, p[M], 3),
753        m1: digit(p[M], 1),
754        m10: digit(p[M], 10),
755        m100: digit(p[M], 100),
756        m1000: digit(p[M], 1000),
757        sl: labelFor(S),
758        sn: self._minDigits(inst, p[S], 1),
759        snn: self._minDigits(inst, p[S], 2),
760        snnn: self._minDigits(inst, p[S], 3),
761        s1: digit(p[S], 1),
762        s10: digit(p[S], 10),
763        s100: digit(p[S], 100),
764        s1000: digit(p[S], 1000)
765      };
766
767      var html = layout;
768      // Replace period containers: {p<}...{p>} — cached regex
769      for (var i = Y; i <= S; i++) {
770        var pc = "yowdhms".charAt(i);
771        html = html.replace(
772          _re("\\{" + pc + "<\\}([\\s\\S]*)\\{" + pc + ">\\}", "g"),
773          (!significant && show[i]) || (significant && showSignificant[i]) ? "$1" : ""
774        );
775      }
776      // Replace period values: {key} — cached regex
777      for (var n in subs) {
778        if (Object.prototype.hasOwnProperty.call(subs, n)) {
779          html = html.replace(_re("\\{" + n + "\\}", "g"), subs[n]);
780        }
781      }
782      return html;
783    },
784
785    /* Ensure a numeric value has at least n digits for display.
786       @param  inst   (object) the current settings for this instance
787       @param  value  (number) the value to display
788       @param  len    (number) the minimum length
789       @return  (string) the display text */
790    _minDigits: function (inst, value, len) {
791      value = "" + value;
792      if (value.length >= len) return this._translateDigits(inst, value);
793      value = "0000000000" + value;
794      return this._translateDigits(inst, value.substr(value.length - len));
795    },
796
797    /* Translate digits into other representations.
798       @param  inst   (object) the current settings for this instance
799       @param  value  (string) the text to translate
800       @return  (string) the translated text */
801    _translateDigits: function (inst, value) {
802      return ("" + value).replace(/[0-9]/g, function (d) {
803        return inst.options.digits[d];
804      });
805    },
806
807    /* Translate the format into flags for each period.
808       @param  inst  (object) the current settings for this instance
809       @return  (string[7]) flags: '?' requested, '!' required, null hidden */
810    _determineShow: function (inst) {
811      var fmt = inst.options.format;
812      var show = [];
813      show[Y] = fmt.match("y") ? "?" : fmt.match("Y") ? "!" : null;
814      show[O] = fmt.match("o") ? "?" : fmt.match("O") ? "!" : null;
815      show[W] = fmt.match("w") ? "?" : fmt.match("W") ? "!" : null;
816      show[D] = fmt.match("d") ? "?" : fmt.match("D") ? "!" : null;
817      show[H] = fmt.match("h") ? "?" : fmt.match("H") ? "!" : null;
818      show[M] = fmt.match("m") ? "?" : fmt.match("M") ? "!" : null;
819      show[S] = fmt.match("s") ? "?" : fmt.match("S") ? "!" : null;
820      return show;
821    },
822
823    /* Calculate the requested periods between now and the target time.
824       @param  inst         (object) the current settings for this instance
825       @param  show         (string[7]) flags indicating which periods are requested/required
826       @param  significant  (number) the number of periods with values to show, zero for all
827       @param  now          (Date) the current date and time
828       @return  (number[7]) the current time periods (always positive) */
829    _calculatePeriods: function (inst, show, significant, now) {
830      inst._now = now;
831      inst._now.setMilliseconds(0);
832      var until = new Date(inst._now.getTime());
833      if (inst._since) {
834        if (now.getTime() < inst._since.getTime()) {
835          inst._now = now = until;
836        } else {
837          now = inst._since;
838        }
839      } else {
840        until.setTime(inst._until.getTime());
841        if (now.getTime() > inst._until.getTime()) {
842          inst._now = now = until;
843        }
844      }
845
846      var periods = [0, 0, 0, 0, 0, 0, 0];
847      if (show[Y] || show[O]) {
848        var lastNow = _plugin._getDaysInMonth(now.getFullYear(), now.getMonth());
849        var lastUntil = _plugin._getDaysInMonth(until.getFullYear(), until.getMonth());
850        var sameDay =
851          until.getDate() === now.getDate() ||
852          (until.getDate() >= Math.min(lastNow, lastUntil) &&
853            now.getDate() >= Math.min(lastNow, lastUntil));
854        var getSecs = function (date) {
855          return (date.getHours() * 60 + date.getMinutes()) * 60 + date.getSeconds();
856        };
857        var months = Math.max(
858          0,
859          (until.getFullYear() - now.getFullYear()) * 12 +
860            until.getMonth() -
861            now.getMonth() +
862            ((until.getDate() < now.getDate() && !sameDay) ||
863            (sameDay && getSecs(until) < getSecs(now))
864              ? -1
865              : 0)
866        );
867        periods[Y] = show[Y] ? Math.floor(months / 12) : 0;
868        periods[O] = show[O] ? months - periods[Y] * 12 : 0;
869        now = new Date(now.getTime());
870        var wasLastDay = now.getDate() === lastNow;
871        var lastDay = _plugin._getDaysInMonth(
872          now.getFullYear() + periods[Y],
873          now.getMonth() + periods[O]
874        );
875        if (now.getDate() > lastDay) now.setDate(lastDay);
876        now.setFullYear(now.getFullYear() + periods[Y]);
877        now.setMonth(now.getMonth() + periods[O]);
878        if (wasLastDay) now.setDate(lastDay);
879      }
880
881      var diff = Math.floor((until.getTime() - now.getTime()) / 1000);
882      var extractPeriod = function (period, numSecs) {
883        periods[period] = show[period] ? Math.floor(diff / numSecs) : 0;
884        diff -= periods[period] * numSecs;
885      };
886      extractPeriod(W, 604800);
887      extractPeriod(D, 86400);
888      extractPeriod(H, 3600);
889      extractPeriod(M, 60);
890      extractPeriod(S, 1);
891
892      if (diff > 0 && !inst._since) {
893        var multiplier = [1, 12, 4.3482, 7, 24, 60, 60];
894        var lastShown = S;
895        var max = 1;
896        var period;
897        for (period = S; period >= Y; period--) {
898          if (show[period]) {
899            if (periods[lastShown] >= max) {
900              periods[lastShown] = 0;
901              diff = 1;
902            }
903            if (diff > 0) {
904              periods[period]++;
905              diff = 0;
906              lastShown = period;
907              max = 1;
908            }
909          }
910          max *= multiplier[period];
911        }
912      }
913
914      if (significant) {
915        for (period = Y; period <= S; period++) {
916          if (significant && periods[period]) {
917            significant--;
918          } else if (!significant) {
919            periods[period] = 0;
920          }
921        }
922      }
923      return periods;
924    }
925  });
926
927  // ── jQuery plugin interface ─────────────────────────────────────────────────
928
929  var _getters = ["getTimes"];
930
931  function _isNotChained(command, otherArgs) {
932    if (
933      command === "option" &&
934      (otherArgs.length === 0 || (otherArgs.length === 1 && typeof otherArgs[0] === "string"))
935    )
936      return true;
937    return _getters.indexOf(command) > -1;
938  }
939
940  $.fn.countdown = function (options) {
941    var otherArgs = Array.prototype.slice.call(arguments, 1);
942    if (_isNotChained(options, otherArgs)) {
943      return _plugin["_" + options + "Plugin"].apply(_plugin, [this[0]].concat(otherArgs));
944    }
945    return this.each(function () {
946      if (typeof options === "string") {
947        if (!_plugin["_" + options + "Plugin"]) throw "Unknown command: " + options;
948        _plugin["_" + options + "Plugin"].apply(_plugin, [this].concat(otherArgs));
949      } else {
950        _plugin._attachPlugin(this, options || {});
951      }
952    });
953  };
954
955  var _plugin = ($.countdown = new Countdown());
956
957  _plugin.init = function (target, options) {
958    var el = typeof target === "string" ? document.querySelector(target) : target;
959    if (el) this._attachPlugin(el, options || {});
960    return el;
961  };
962
963  if (typeof window !== "undefined") {
964    window.Countdown = _plugin;
965  }
966})(jQuery);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.