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