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(/</g, "<").replace(/>/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.