1/* http://keith-wood.name/countdown.html 2 Countdown for jQuery v1.6.3. 3 Written by Keith Wood (kbwood{at}iinet.com.au) January 2008. 4 Available under the MIT (https://github.com/jquery/jquery/blob/master/MIT-LICENSE.txt) license. 5 Please attribute the author if you use it. */ 6 7/* Display a countdown timer. 8 Attach it with options like: 9 $('div selector').countdown( 10 {until: new Date(2009, 1 - 1, 1, 0, 0, 0), onExpiry: happyNewYear}); */ 11 12(function($) { // Hide scope, no $ conflict 13 14/* Countdown manager. */ 15function Countdown() { 16 this.regional = []; // Available regional settings, indexed by language code 17 this.regional[''] = { // Default regional settings 18 // The display texts for the counters 19 labels: ['Years', 'Months', 'Weeks', 'Days', 'Hours', 'Minutes', 'Seconds'], 20 // The display texts for the counters if only one 21 labels1: ['Year', 'Month', 'Week', 'Day', 'Hour', 'Minute', 'Second'], 22 compactLabels: ['y', 'm', 'w', 'd'], // The compact texts for the counters 23 whichLabels: null, // Function to determine which labels to use 24 digits: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], // The digits to display 25 timeSeparator: ':', // Separator for time periods 26 isRTL: false // True for right-to-left languages, false for left-to-right 27 }; 28 this._defaults = { 29 until: null, // new Date(year, mth - 1, day, hr, min, sec) - date/time to count down to 30 // or numeric for seconds offset, or string for unit offset(s): 31 // 'Y' years, 'O' months, 'W' weeks, 'D' days, 'H' hours, 'M' minutes, 'S' seconds 32 since: null, // new Date(year, mth - 1, day, hr, min, sec) - date/time to count up from 33 // or numeric for seconds offset, or string for unit offset(s): 34 // 'Y' years, 'O' months, 'W' weeks, 'D' days, 'H' hours, 'M' minutes, 'S' seconds 35 timezone: null, // The timezone (hours or minutes from GMT) for the target times, 36 // or null for client local 37 serverSync: null, // A function to retrieve the current server time for synchronisation 38 format: 'dHMS', // Format for display - upper case for always, lower case only if non-zero, 39 // 'Y' years, 'O' months, 'W' weeks, 'D' days, 'H' hours, 'M' minutes, 'S' seconds 40 layout: '', // Build your own layout for the countdown 41 compact: false, // True to display in a compact format, false for an expanded one 42 significant: 0, // The number of periods with values to show, zero for all 43 description: '', // The description displayed for the countdown 44 expiryUrl: '', // A URL to load upon expiry, replacing the current page 45 expiryText: '', // Text to display upon expiry, replacing the countdown 46 alwaysExpire: false, // True to trigger onExpiry even if never counted down 47 onExpiry: null, // Callback when the countdown expires - 48 // receives no parameters and 'this' is the containing division
49 onTick: null, // Callback when the countdown is updated - 50 // receives int[7] being the breakdown by period (based on format) 51 // and 'this' is the containing division 52 tickInterval: 1 // Interval (seconds) between onTick callbacks 53 }; 54 $.extend(this._defaults, this.regional['']); 55 this._serverSyncs = []; 56 var now = (typeof Date.now == 'function' ? Date.now : 57 function() { return new Date().getTime(); }); 58 var perfAvail = (window.performance && typeof window.performance.now == 'function'); 59 // Shared timer for all countdowns 60 function timerCallBack(timestamp) { 61 var drawStart = (timestamp < 1e12 ? // New HTML5 high resolution timer 62 (perfAvail ? (performance.now() + performance.timing.navigationStart) : now()) : 63 // Integer milliseconds since unix epoch 64 timestamp || now()); 65 if (drawStart - animationStartTime >= 1000) { 66 plugin._updateTargets(); 67 animationStartTime = drawStart; 68 } 69 requestAnimationFrame(timerCallBack); 70 } 71 var requestAnimationFrame = window.requestAnimationFrame || 72 window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || 73 window.oRequestAnimationFrame || window.msRequestAnimationFrame || null; 74 // This is when we expect a fall-back to setInterval as it's much more fluid 75 var animationStartTime = 0; 76 if (!requestAnimationFrame || $.noRequestAnimationFrame) { 77 $.noRequestAnimationFrame = null; 78 setInterval(function() { plugin._updateTargets(); }, 980); // Fall back to good old setInterval 79 } 80 else { 81 animationStartTime = window.animationStartTime || 82 window.webkitAnimationStartTime || window.mozAnimationStartTime || 83 window.oAnimationStartTime || window.msAnimationStartTime || now(); 84 requestAnimationFrame(timerCallBack); 85 } 86} 87 88var Y = 0; // Years 89var O = 1; // Months 90var W = 2; // Weeks 91var D = 3; // Days 92var H = 4; // Hours 93var M = 5; // Minutes 94var S = 6; // Seconds 95 96$.extend(Countdown.prototype, { 97 /* Class name added to elements to indicate already configured with countdown. */ 98 markerClassName: 'hasCountdown', 99 /* Name of the data property for instance settings. */ 100 propertyName: 'countdown', 101 102 /* Class name for the right-to-left marker. */ 103 _rtlClass: 'countdown_rtl', 104 /* Class name for the countdown section marker. */ 105 _sectionClass: 'countdown_section', 106 /* Class name for the period amount marker. */ 107 _amountClass: 'countdown_amount', 108 /* Class name for the countdown row marker. */ 109 _rowClass: 'countdown_row', 110 /* Class name for the holding countdown marker. */ 111 _holdingClass: 'countdown_holding', 112 /* Class name for the showing countdown marker. */ 113 _showClass: 'countdown_show', 114 /* Class name for the description marker. */ 115 _descrClass: 'countdown_descr', 116 117 /* List of currently active countdown targets. */ 118 _timerTargets: [], 119 120 /* Override the default settings for all instances of the countdown widget. 121 @param options (object) the new settings to use as defaults */ 122 setDefaults: function(options) { 123 this._resetExtraLabels(this._defaults, options); 124 $.extend(this._defaults, options || {}); 125 }, 126 127 /* Convert a date/time to UTC. 128 @param tz (number) the hour or minute offset from GMT, e.g. +9, -360 129 @param year (Date) the date/time in that timezone or 130 (number) the year in that timezone 131 @param month (number, optional) the month (0 - 11) (omit if year is a Date) 132 @param day (number, optional) the day (omit if year is a Date) 133 @param hours (number, optional) the hour (omit if year is a Date) 134 @param mins (number, optional) the minute (omit if year is a Date) 135 @param secs (number, optional) the second (omit if year is a Date) 136 @param ms (number, optional) the millisecond (omit if year is a Date) 137 @return (Date) the equivalent UTC date/time */ 138 UTCDate: function(tz, year, month, day, hours, mins, secs, ms) { 139 if (typeof year == 'object' && year.constructor == Date) { 140 ms = year.getMilliseconds(); 141 secs = year.getSeconds(); 142 mins = year.getMinutes(); 143 hours = year.getHours(); 144 day = year.getDate();
145 month = year.getMonth(); 146 year = year.getFullYear(); 147 } 148 var d = new Date(); 149 d.setUTCFullYear(year); 150 d.setUTCDate(1); 151 d.setUTCMonth(month || 0); 152 d.setUTCDate(day || 1); 153 d.setUTCHours(hours || 0); 154 d.setUTCMinutes((mins || 0) - (Math.abs(tz) < 30 ? tz * 60 : tz)); 155 d.setUTCSeconds(secs || 0); 156 d.setUTCMilliseconds(ms || 0); 157 return d; 158 }, 159 160 /* Convert a set of periods into seconds. 161 Averaged for months and years. 162 @param periods (number[7]) the periods per year/month/week/day/hour/minute/second 163 @return (number) the corresponding number of seconds */ 164 periodsToSeconds: function(periods) { 165 return periods[0] * 31557600 + periods[1] * 2629800 + periods[2] * 604800 + 166 periods[3] * 86400 + periods[4] * 3600 + periods[5] * 60 + periods[6]; 167 }, 168 169 /* Attach the countdown widget to a div. 170 @param target (element) the containing division 171 @param options (object) the initial settings for the countdown */ 172 _attachPlugin: function(target, options) { 173 target = $(target); 174 if (target.hasClass(this.markerClassName)) { 175 return; 176 } 177 var inst = {options: $.extend({}, this._defaults), _periods: [0, 0, 0, 0, 0, 0, 0]}; 178 target.addClass(this.markerClassName).data(this.propertyName, inst); 179 this._optionPlugin(target, options); 180 }, 181 182 /* Add a target to the list of active ones. 183 @param target (element) the countdown target */ 184 _addTarget: function(target) { 185 if (!this._hasTarget(target)) { 186 this._timerTargets.push(target); 187 } 188 }, 189 190 /* See if a target is in the list of active ones. 191 @param target (element) the countdown target 192 @return (boolean) true if present, false if not */ 193 _hasTarget: function(target) { 194 return ($.inArray(target, this._timerTargets) > -1); 195 }, 196 197 /* Remove a target from the list of active ones. 198 @param target (element) the countdown target */ 199 _removeTarget: function(target) { 200 this._timerTargets = $.map(this._timerTargets, 201 function(value) { return (value == target ? null : value); }); // delete entry 202 }, 203 204 /* Update each active timer target. */ 205 _updateTargets: function() { 206 for (var i = this._timerTargets.length - 1; i >= 0; i--) { 207 this._updateCountdown(this._timerTargets[i]); 208 } 209 }, 210 211 /* Reconfigure the settings for a countdown div. 212 @param target (element) the control to affect 213 @param options (object) the new options for this instance or 214 (string) an individual property name 215 @param value (any) the individual property value (omit if options 216 is an object or to retrieve the value of a setting) 217 @return (any) if retrieving a value */ 218 _optionPlugin: function(target, options, value) { 219 target = $(target); 220 var inst = target.data(this.propertyName); 221 if (!options || (typeof options == 'string' && value == null)) { // Get option 222 var name = options; 223 options = (inst || {}).options; 224 return (options && name ? options[name] : options); 225 } 226 227 if (!target.hasClass(this.markerClassName)) { 228 return; 229 } 230 options = options || {}; 231 if (typeof options == 'string') { 232 var name = options; 233 options = {}; 234 options[name] = value; 235 } 236 if (options.layout) { 237 options.layout = options.layout.replace(/</g, '<').replace(/>/g, '>'); 238 } 239 this._resetExtraLabels(inst.options, options); 240 var timezoneChanged = (inst.options.timezone != options.timezone); 241 $.extend(inst.options, options); 242 this._adjustSettings(target, inst, 243 options.until != null || options.since != null || timezoneChanged); 244 var now = new Date(); 245 if ((inst._since && inst._since < now) || (inst._until && inst._until > now)) { 246 this._addTarget(target[0]); 247 } 248 this._updateCountdown(target, inst); 249 }, 250 251 /* Redisplay the countdown with an updated display. 252 @param target (jQuery) the containing division 253 @param inst (object) the current settings for this instance */ 254 _updateCountdown: function(target, inst) { 255 var $target = $(target); 256 inst = inst || $target.data(this.propertyName); 257 if (!inst) { 258 return; 259 } 260 $target.html(this._generateHTML(inst)).toggleClass(this._rtlClass, inst.options.isRTL);
261 if ($.isFunction(inst.options.onTick)) { 262 var periods = inst._hold != 'lap' ? inst._periods : 263 this._calculatePeriods(inst, inst._show, inst.options.significant, new Date()); 264 if (inst.options.tickInterval == 1 || 265 this.periodsToSeconds(periods) % inst.options.tickInterval == 0) { 266 inst.options.onTick.apply(target, [periods]); 267 } 268 } 269 var expired = inst._hold != 'pause' && 270 (inst._since ? inst._now.getTime() < inst._since.getTime() : 271 inst._now.getTime() >= inst._until.getTime()); 272 if (expired && !inst._expiring) { 273 inst._expiring = true; 274 if (this._hasTarget(target) || inst.options.alwaysExpire) { 275 this._removeTarget(target); 276 if ($.isFunction(inst.options.onExpiry)) { 277 inst.options.onExpiry.apply(target, []); 278 } 279 if (inst.options.expiryText) { 280 var layout = inst.options.layout; 281 inst.options.layout = inst.options.expiryText; 282 this._updateCountdown(target, inst); 283 inst.options.layout = layout; 284 } 285 if (inst.options.expiryUrl) { 286 window.location = inst.options.expiryUrl; 287 } 288 } 289 inst._expiring = false; 290 } 291 else if (inst._hold == 'pause') { 292 this._removeTarget(target); 293 } 294 $target.data(this.propertyName, inst); 295 }, 296 297 /* Reset any extra labelsn and compactLabelsn entries if changing labels. 298 @param base (object) the options to be updated 299 @param options (object) the new option values */ 300 _resetExtraLabels: function(base, options) { 301 var changingLabels = false; 302 for (var n in options) { 303 if (n != 'whichLabels' && n.match(/[Ll]abels/)) { 304 changingLabels = true; 305 break; 306 } 307 } 308 if (changingLabels) { 309 for (var n in base) { // Remove custom numbered labels 310 if (n.match(/[Ll]abels[02-9]|compactLabels1/)) { 311 base[n] = null; 312 } 313 } 314 } 315 }, 316 317 /* Calculate interal settings for an instance. 318 @param target (element) the containing division 319 @param inst (object) the current settings for this instance 320 @param recalc (boolean) true if until or since are set */ 321 _adjustSettings: function(target, inst, recalc) { 322 var now; 323 var serverOffset = 0; 324 var serverEntry = null; 325 for (var i = 0; i < this._serverSyncs.length; i++) { 326 if (this._serverSyncs[i][0] == inst.options.serverSync) { 327 serverEntry = this._serverSyncs[i][1]; 328 break; 329 } 330 } 331 if (serverEntry != null) { 332 serverOffset = (inst.options.serverSync ? serverEntry : 0); 333 now = new Date(); 334 } 335 else { 336 var serverResult = ($.isFunction(inst.options.serverSync) ? 337 inst.options.serverSync.apply(target, []) : null); 338 now = new Date(); 339 serverOffset = (serverResult ? now.getTime() - serverResult.getTime() : 0); 340 this._serverSyncs.push([inst.options.serverSync, serverOffset]); 341 } 342 var timezone = inst.options.timezone; 343 timezone = (timezone == null ? -now.getTimezoneOffset() : timezone); 344 if (recalc || (!recalc && inst._until == null && inst._since == null)) { 345 inst._since = inst.options.since; 346 if (inst._since != null) { 347 inst._since = this.UTCDate(timezone, this._determineTime(inst._since, null)); 348 if (inst._since && serverOffset) { 349 inst._since.setMilliseconds(inst._since.getMilliseconds() + serverOffset); 350 } 351 } 352 inst._until = this.UTCDate(timezone, this._determineTime(inst.options.until, now)); 353 if (serverOffset) { 354 inst._until.setMilliseconds(inst._until.getMilliseconds() + serverOffset); 355 } 356 } 357 inst._show = this._determineShow(inst); 358 }, 359 360 /* Remove the countdown widget from a div. 361 @param target (element) the containing division */ 362 _destroyPlugin: function(target) { 363 target = $(target); 364 if (!target.hasClass(this.markerClassName)) { 365 return; 366 } 367 this._removeTarget(target[0]); 368 target.removeClass(this.markerClassName).empty().removeData(this.propertyName); 369 }, 370 371 /* Pause a countdown widget at the current time. 372 Stop it running but remember and display the current time. 373 @param target (element) the containing division */ 374 _pausePlugin: function(target) { 375 this._hold(target, 'pause'); 376 }, 377 378 /* Pause a countdown widget at the current time. 379 Stop the display but keep the countdown running. 380 @param target (element) the containing division */ 381 _lapPlugin: function(target) { 382 this._hold(target, 'lap'); 383 }, 384 385 /* Resume a paused countdown widget. 386 @param target (element) the containing division */ 387 _resumePlugin: function(target) { 388 this._hold(target, null); 389 }, 390 391 /* Pause or resume a countdown widget. 392 @param target (element) the containing division 393 @param hold (string) the new hold setting */ 394 _hold: function(target, hold) { 395 var inst = $.data(target, this.propertyName); 396 if (inst) { 397 if (inst._hold == 'pause' && !hold) {
398 inst._periods = inst._savePeriods; 399 var sign = (inst._since ? '-' : '+'); 400 inst[inst._since ? '_since' : '_until'] = 401 this._determineTime(sign + inst._periods[0] + 'y' + 402 sign + inst._periods[1] + 'o' + sign + inst._periods[2] + 'w' + 403 sign + inst._periods[3] + 'd' + sign + inst._periods[4] + 'h' + 404 sign + inst._periods[5] + 'm' + sign + inst._periods[6] + 's'); 405 this._addTarget(target); 406 } 407 inst._hold = hold; 408 inst._savePeriods = (hold == 'pause' ? inst._periods : null); 409 $.data(target, this.propertyName, inst); 410 this._updateCountdown(target, inst); 411 } 412 }, 413 414 /* Return the current time periods. 415 @param target (element) the containing division 416 @return (number[7]) the current periods for the countdown */ 417 _getTimesPlugin: function(target) { 418 var inst = $.data(target, this.propertyName); 419 return (!inst ? null : (inst._hold == 'pause' ? inst._savePeriods : (!inst._hold ? inst._periods : 420 this._calculatePeriods(inst, inst._show, inst.options.significant, new Date())))); 421 }, 422 423 /* A time may be specified as an exact value or a relative one. 424 @param setting (string or number or Date) - the date/time value 425 as a relative or absolute value 426 @param defaultTime (Date) the date/time to use if no other is supplied 427 @return (Date) the corresponding date/time */ 428 _determineTime: function(setting, defaultTime) { 429 var offsetNumeric = function(offset) { // e.g. +300, -2 430 var time = new Date(); 431 time.setTime(time.getTime() + offset * 1000); 432 return time; 433 }; 434 var offsetString = function(offset) { // e.g. '+2d', '-4w', '+3h +30m' 435 offset = offset.toLowerCase(); 436 var time = new Date(); 437 var year = time.getFullYear(); 438 var month = time.getMonth(); 439 var day = time.getDate(); 440 var hour = time.getHours(); 441 var minute = time.getMinutes(); 442 var second = time.getSeconds(); 443 var pattern = /([+-]?[0-9]+)\s*(s|m|h|d|w|o|y)?/g; 444 var matches = pattern.exec(offset); 445 while (matches) { 446 switch (matches[2] || 's') { 447 case 's': second += parseInt(matches[1], 10); break; 448 case 'm': minute += parseInt(matches[1], 10); break; 449 case 'h': hour += parseInt(matches[1], 10); break; 450 case 'd': day += parseInt(matches[1], 10); break; 451 case 'w': day += parseInt(matches[1], 10) * 7; break; 452 case 'o': 453 month += parseInt(matches[1], 10); 454 day = Math.min(day, plugin._getDaysInMonth(year, month)); 455 break; 456 case 'y': 457 year += parseInt(matches[1], 10); 458 day = Math.min(day, plugin._getDaysInMonth(year, month)); 459 break; 460 } 461 matches = pattern.exec(offset); 462 } 463 return new Date(year, month, day, hour, minute, second, 0); 464 }; 465 var time = (setting == null ? defaultTime : 466 (typeof setting == 'string' ? offsetString(setting) : 467 (typeof setting == 'number' ? offsetNumeric(setting) : setting))); 468 if (time) time.setMilliseconds(0); 469 return time; 470 }, 471 472 /* Determine the number of days in a month. 473 @param year (number) the year 474 @param month (number) the month 475 @return (number) the days in that month */ 476 _getDaysInMonth: function(year, month) { 477 return 32 - new Date(year, month, 32).getDate(); 478 }, 479 480 /* Determine which set of labels should be used for an amount. 481 @param num (number) the amount to be displayed 482 @return (number) the set of labels to be used for this amount */ 483 _normalLabels: function(num) { 484 return num; 485 }, 486 487 /* Generate the HTML to display the countdown widget. 488 @param inst (object) the current settings for this instance 489 @return (string) the new HTML for the countdown display */ 490 _generateHTML: function(inst) { 491 var self = this; 492 // Determine what to show 493 inst._periods = (inst._hold ? inst._periods : 494 this._calculatePeriods(inst, inst._show, inst.options.significant, new Date())); 495 // Show all 'asNeeded' after first non-zero value 496 var shownNonZero = false; 497 var showCount = 0; 498 var sigCount = inst.options.significant; 499 var show = $.extend({}, inst._show); 500 for (var period = Y; period <= S; period++) {
501 shownNonZero |= (inst._show[period] == '?' && inst._periods[period] > 0); 502 show[period] = (inst._show[period] == '?' && !shownNonZero ? null : inst._show[period]); 503 showCount += (show[period] ? 1 : 0); 504 sigCount -= (inst._periods[period] > 0 ? 1 : 0); 505 } 506 var showSignificant = [false, false, false, false, false, false, false]; 507 for (var period = S; period >= Y; period--) { // Determine significant periods 508 if (inst._show[period]) { 509 if (inst._periods[period]) { 510 showSignificant[period] = true; 511 } 512 else { 513 showSignificant[period] = sigCount > 0; 514 sigCount--; 515 } 516 } 517 } 518 var labels = (inst.options.compact ? inst.options.compactLabels : inst.options.labels); 519 var whichLabels = inst.options.whichLabels || this._normalLabels; 520 var showCompact = function(period) { 521 var labelsNum = inst.options['compactLabels' + whichLabels(inst._periods[period])]; 522 return (show[period] ? self._translateDigits(inst, inst._periods[period]) + 523 (labelsNum ? labelsNum[period] : labels[period]) + ' ' : ''); 524 }; 525 var showFull = function(period) { 526 var labelsNum = inst.options['labels' + whichLabels(inst._periods[period])]; 527 return ((!inst.options.significant && show[period]) || 528 (inst.options.significant && showSignificant[period]) ? 529 '<span class="' + plugin._sectionClass + '">' + 530 '<span class="' + plugin._amountClass + '">' + 531 self._translateDigits(inst, inst._periods[period]) + '</span><br/>' + 532 (labelsNum ? labelsNum[period] : labels[period]) + '</span>' : ''); 533 }; 534 return (inst.options.layout ? this._buildLayout(inst, show, inst.options.layout, 535 inst.options.compact, inst.options.significant, showSignificant) : 536 ((inst.options.compact ? // Compact version 537 '<span class="' + this._rowClass + ' ' + this._amountClass + 538 (inst._hold ? ' ' + this._holdingClass : '') + '">' + 539 showCompact(Y) + showCompact(O) + showCompact(W) + showCompact(D) + 540 (show[H] ? this._minDigits(inst, inst._periods[H], 2) : '') + 541 (show[M] ? (show[H] ? inst.options.timeSeparator : '') + 542 this._minDigits(inst, inst._periods[M], 2) : '') + 543 (show[S] ? (show[H] || show[M] ? inst.options.timeSeparator : '') + 544 this._minDigits(inst, inst._periods[S], 2) : '') : 545 // Full version 546 '<span class="' + this._rowClass + ' ' + this._showClass + (inst.options.significant || showCount) + 547 (inst._hold ? ' ' + this._holdingClass : '') + '">' + 548 showFull(Y) + showFull(O) + showFull(W) + showFull(D) + 549 showFull(H) + showFull(M) + showFull(S)) + '</span>' + 550 (inst.options.description ? '<span class="' + this._rowClass + ' ' + this._descrClass + '">' + 551 inst.options.description + '</span>' : ''))); 552 }, 553 554 /* Construct a custom layout. 555 @param inst (object) the current settings for this instance 556 @param show (string[7]) flags indicating which periods are requested 557 @param layout (string) the customised layout 558 @param compact (boolean) true if using compact labels 559 @param significant (number) the number of periods with values to show, zero for all 560 @param showSignificant (boolean[7]) other periods to show for significance 561 @return (string) the custom HTML */ 562 _buildLayout: function(inst, show, layout, compact, significant, showSignificant) { 563 var labels = inst.options[compact ? 'compactLabels' : 'labels']; 564 var whichLabels = inst.options.whichLabels || this._normalLabels;
565 var labelFor = function(index) { 566 return (inst.options[(compact ? 'compactLabels' : 'labels') + 567 whichLabels(inst._periods[index])] || labels)[index]; 568 }; 569 var digit = function(value, position) { 570 return inst.options.digits[Math.floor(value / position) % 10]; 571 }; 572 var subs = {desc: inst.options.description, sep: inst.options.timeSeparator, 573 yl: labelFor(Y), yn: this._minDigits(inst, inst._periods[Y], 1), 574 ynn: this._minDigits(inst, inst._periods[Y], 2), 575 ynnn: this._minDigits(inst, inst._periods[Y], 3), y1: digit(inst._periods[Y], 1), 576 y10: digit(inst._periods[Y], 10), y100: digit(inst._periods[Y], 100), 577 y1000: digit(inst._periods[Y], 1000), 578 ol: labelFor(O), on: this._minDigits(inst, inst._periods[O], 1), 579 onn: this._minDigits(inst, inst._periods[O], 2), 580 onnn: this._minDigits(inst, inst._periods[O], 3), o1: digit(inst._periods[O], 1), 581 o10: digit(inst._periods[O], 10), o100: digit(inst._periods[O], 100), 582 o1000: digit(inst._periods[O], 1000), 583 wl: labelFor(W), wn: this._minDigits(inst, inst._periods[W], 1), 584 wnn: this._minDigits(inst, inst._periods[W], 2), 585 wnnn: this._minDigits(inst, inst._periods[W], 3), w1: digit(inst._periods[W], 1), 586 w10: digit(inst._periods[W], 10), w100: digit(inst._periods[W], 100), 587 w1000: digit(inst._periods[W], 1000), 588 dl: labelFor(D), dn: this._minDigits(inst, inst._periods[D], 1), 589 dnn: this._minDigits(inst, inst._periods[D], 2), 590 dnnn: this._minDigits(inst, inst._periods[D], 3), d1: digit(inst._periods[D], 1), 591 d10: digit(inst._periods[D], 10), d100: digit(inst._periods[D], 100), 592 d1000: digit(inst._periods[D], 1000), 593 hl: labelFor(H), hn: this._minDigits(inst, inst._periods[H], 1), 594 hnn: this._minDigits(inst, inst._periods[H], 2), 595 hnnn: this._minDigits(inst, inst._periods[H], 3), h1: digit(inst._periods[H], 1), 596 h10: digit(inst._periods[H], 10), h100: digit(inst._periods[H], 100), 597 h1000: digit(inst._periods[H], 1000), 598 ml: labelFor(M), mn: this._minDigits(inst, inst._periods[M], 1), 599 mnn: this._minDigits(inst, inst._periods[M], 2), 600 mnnn: this._minDigits(inst, inst._periods[M], 3), m1: digit(inst._periods[M], 1), 601 m10: digit(inst._periods[M], 10), m100: digit(inst._periods[M], 100), 602 m1000: digit(inst._periods[M], 1000), 603 sl: labelFor(S), sn: this._minDigits(inst, inst._periods[S], 1), 604 snn: this._minDigits(inst, inst._periods[S], 2), 605 snnn: this._minDigits(inst, inst._periods[S], 3), s1: digit(inst._periods[S], 1), 606 s10: digit(inst._periods[S], 10), s100: digit(inst._periods[S], 100), 607 s1000: digit(inst._periods[S], 1000)}; 608 var html = layout; 609 // Replace period containers: {p<}...{p>} 610 for (var i = Y; i <= S; i++) { 611 var period = 'yowdhms'.charAt(i); 612 var re = new RegExp('\\{' + period + '<\\}([\\s\\S]*)\\{' + period + '>\\}', 'g'); 613 html = html.replace(re, ((!significant && show[i]) || 614 (significant && showSignificant[i]) ? '$1' : '')); 615 } 616 // Replace period values: {pn} 617 $.each(subs, function(n, v) { 618 var re = new RegExp('\\{' + n + '\\}', 'g'); 619 html = html.replace(re, v); 620 }); 621 return html; 622 }, 623 624 /* Ensure a numeric value has at least n digits for display. 625 @param inst (object) the current settings for this instance 626 @param value (number) the value to display 627 @param len (number) the minimum length 628 @return (string) the display text */ 629 _minDigits: function(inst, value, len) { 630 value = '' + value; 631 if (value.length >= len) { 632 return this._translateDigits(inst, value); 633 } 634 value = '0000000000' + value; 635 return this._translateDigits(inst, value.substr(value.length - len)); 636 }, 637 638 /* Translate digits into other representations. 639 @param inst (object) the current settings for this instance 640 @param value (string) the text to translate 641 @return (string) the translated text */ 642 _translateDigits: function(inst, value) { 643 return ('' + value).replace(/[0-9]/g, function(digit) { 644 return inst.options.digits[digit]; 645 }); 646 }, 647 648 /* Translate the format into flags for each period. 649 @param inst (object) the current settings for this instance 650 @return (string[7]) flags indicating which periods are requested (?) or 651 required (!) by year, month, week, day, hour, minute, second */ 652 _determineShow: function(inst) { 653 var format = inst.options.format; 654 var show = []; 655 show[Y] = (format.match('y') ? '?' : (format.match('Y') ? '!' : null)); 656 show[O] = (format.match('o') ? '?' : (format.match('O') ? '!' : null)); 657 show[W] = (format.match('w') ? '?' : (format.match('W') ? '!' : null)); 658 show[D] = (format.match('d') ? '?' : (format.match('D') ? '!' : null)); 659 show[H] = (format.match('h') ? '?' : (format.match('H') ? '!' : null)); 660 show[M] = (format.match('m') ? '?' : (format.match('M') ? '!' : null)); 661 show[S] = (format.match('s') ? '?' : (format.match('S') ? '!' : null)); 662 return show; 663 }, 664 665 /* Calculate the requested periods between now and the target time. 666 @param inst (object) the current settings for this instance 667 @param show (string[7]) flags indicating which periods are requested/required 668 @param significant (number) the number of periods with values to show, zero for all 669 @param now (Date) the current date and time 670 @return (number[7]) the current time periods (always positive) 671 by year, month, week, day, hour, minute, second */
672 _calculatePeriods: function(inst, show, significant, now) { 673 // Find endpoints 674 inst._now = now; 675 inst._now.setMilliseconds(0); 676 var until = new Date(inst._now.getTime()); 677 if (inst._since) { 678 if (now.getTime() < inst._since.getTime()) { 679 inst._now = now = until; 680 } 681 else { 682 now = inst._since; 683 } 684 } 685 else { 686 until.setTime(inst._until.getTime()); 687 if (now.getTime() > inst._until.getTime()) { 688 inst._now = now = until; 689 } 690 } 691 // Calculate differences by period 692 var periods = [0, 0, 0, 0, 0, 0, 0]; 693 if (show[Y] || show[O]) { 694 // Treat end of months as the same 695 var lastNow = plugin._getDaysInMonth(now.getFullYear(), now.getMonth()); 696 var lastUntil = plugin._getDaysInMonth(until.getFullYear(), until.getMonth()); 697 var sameDay = (until.getDate() == now.getDate() || 698 (until.getDate() >= Math.min(lastNow, lastUntil) && 699 now.getDate() >= Math.min(lastNow, lastUntil))); 700 var getSecs = function(date) { 701 return (date.getHours() * 60 + date.getMinutes()) * 60 + date.getSeconds(); 702 }; 703 var months = Math.max(0, 704 (until.getFullYear() - now.getFullYear()) * 12 + until.getMonth() - now.getMonth() + 705 ((until.getDate() < now.getDate() && !sameDay) || 706 (sameDay && getSecs(until) < getSecs(now)) ? -1 : 0)); 707 periods[Y] = (show[Y] ? Math.floor(months / 12) : 0); 708 periods[O] = (show[O] ? months - periods[Y] * 12 : 0); 709 // Adjust for months difference and end of month if necessary 710 now = new Date(now.getTime()); 711 var wasLastDay = (now.getDate() == lastNow); 712 var lastDay = plugin._getDaysInMonth(now.getFullYear() + periods[Y], 713 now.getMonth() + periods[O]); 714 if (now.getDate() > lastDay) { 715 now.setDate(lastDay); 716 } 717 now.setFullYear(now.getFullYear() + periods[Y]); 718 now.setMonth(now.getMonth() + periods[O]); 719 if (wasLastDay) { 720 now.setDate(lastDay); 721 } 722 } 723 var diff = Math.floor((until.getTime() - now.getTime()) / 1000); 724 var extractPeriod = function(period, numSecs) { 725 periods[period] = (show[period] ? Math.floor(diff / numSecs) : 0); 726 diff -= periods[period] * numSecs; 727 }; 728 extractPeriod(W, 604800); 729 extractPeriod(D, 86400); 730 extractPeriod(H, 3600); 731 extractPeriod(M, 60); 732 extractPeriod(S, 1); 733 if (diff > 0 && !inst._since) { // Round up if left overs 734 var multiplier = [1, 12, 4.3482, 7, 24, 60, 60]; 735 var lastShown = S; 736 var max = 1; 737 for (var period = S; period >= Y; period--) { 738 if (show[period]) { 739 if (periods[lastShown] >= max) { 740 periods[lastShown] = 0; 741 diff = 1; 742 } 743 if (diff > 0) { 744 periods[period]++; 745 diff = 0; 746 lastShown = period; 747 max = 1; 748 } 749 } 750 max *= multiplier[period]; 751 } 752 } 753 if (significant) { // Zero out insignificant periods 754 for (var period = Y; period <= S; period++) { 755 if (significant && periods[period]) { 756 significant--; 757 } 758 else if (!significant) { 759 periods[period] = 0; 760 } 761 } 762 } 763 return periods; 764 } 765}); 766 767// The list of commands that return values and don't permit chaining 768var getters = ['getTimes']; 769 770/* Determine whether a command is a getter and doesn't permit chaining. 771 @param command (string, optional) the command to run 772 @param otherArgs ([], optional) any other arguments for the command 773 @return true if the command is a getter, false if not */ 774function isNotChained(command, otherArgs) { 775 if (command == 'option' && (otherArgs.length == 0 || 776 (otherArgs.length == 1 && typeof otherArgs[0] == 'string'))) { 777 return true; 778 } 779 return $.inArray(command, getters) > -1; 780} 781 782/* Process the countdown functionality for a jQuery selection. 783 @param options (object) the new settings to use for these instances (optional) or 784 (string) the command to run (optional) 785 @return (jQuery) for chaining further calls or 786 (any) getter value */ 787$.fn.countdown = function(options) { 788 var otherArgs = Array.prototype.slice.call(arguments, 1); 789 if (isNotChained(options, otherArgs)) { 790 return plugin['_' + options + 'Plugin'].
791 apply(plugin, [this[0]].concat(otherArgs)); 792 } 793 return this.each(function() { 794 if (typeof options == 'string') { 795 if (!plugin['_' + options + 'Plugin']) { 796 throw 'Unknown command: ' + options; 797 } 798 plugin['_' + options + 'Plugin']. 799 apply(plugin, [this].concat(otherArgs)); 800 } 801 else { 802 plugin._attachPlugin(this, options || {}); 803 } 804 }); 805}; 806 807/* Initialise the countdown functionality. */ 808var plugin = $.countdown = new Countdown(); // Singleton instance 809 810})(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.