/** * epiClock 2.0 - Create Epic Clocks Easily * * Copyright (c) 2008 Eric Garside (http://eric.garside.name) * Dual licensed under: * MIT: http://www.opensource.org/licenses/mit-license.php * GPLv3: http://www.opensource.org/licenses/gpl-3.0.html */ // Manager States var EC_HALT = 'disable', EC_RUN = 'enable', EC_KILL = 'destroy', // Clock Types EC_CLOCK = 0, EC_COUNTDOWN = 1, EC_COUNTUP = 2, EC_ROLLOVER = 3, EC_EXPIRE = 4, EC_LOOP = 5, EC_STOPWATCH = 6, EC_HOLDUP = 7; ;(function($){ var defaults = { epiClock: { offset: { hours: 0, minutes: 0, seconds: 0, days: 0, years: 0 }, arbitrary: { days: 0, years: 0 }, display: { years: true, military: false }, gmt: false, target: null, onTimer: null, onKill: null, onRender: function(v,val){v.html(val)}, format: null, frame: {}, dead: false, displace: 0, modifier: 0, variance: 0, daysadded: 0, paused: 0, tolerance: 0, selfLoc: -1, mode: EC_CLOCK }, formats: [ 'F j, Y, g:i:s a', // EC_CLOCK 'V{d} x{h} i{m} s{s}', // EC_COUNTDOWN 'Q{y} K{d} x{h} i{m} s{s}', // EC_COUNTUP 'V{d} x{h} i{m} s{s}', // EC_ROLLOVER 'x{h} i{m} s{s}', // EC_EXPIRE 'i{m} s{s}', // EC_LOOP 'x{h} i{m} s{s}', // EC_STOPWATCH 'Q{y} K{d} x{h} i{m} s{s}' // EC_HOLDUP ] }, // The current mode the clock manager is in current = null, // The interval timer for the clock loop = null, // The clocks we're managing clocks = []; /** * jQuery Entry Point - Clock Manager * * Provides an interface for the user to pause, destroy, or resume/start all clocks. */ $.epiclock = function(mode, precision){ mode = mode || EC_RUN; precision = precision || 5e2; if (mode == current) return; switch (mode){ case EC_KILL: $.each(clocks, function(){ this.removeData('epiClock'); }) clocks = []; case EC_HALT: if (loop){ clearInterval(loop); loop = null; } current = mode; break; case EC_RUN: if (!loop){ cycleClocks(); loop = setInterval(cycleClocks, precision); } current = mode; break; } return this; } function cycleClocks(){ $.each(clocks, function(i){ this.data('epiClock').render(); }) } /** * jQuery Entry Point * * Creates the clock displays */ $.fn.epiclock = function(options){ switch (options){ case 'destroy': return this.each(function(){ var jQ = $(this); if (jQ.data('epiClock') instanceof epiClock) jQ.data('epiClock').kill(); }) case 'disable': return this.each(function(){ var jQ = $(this); if (jQ.data('epiClock') instanceof epiClock) jQ.data('epiClock').pause(); }) case 'enable': return this.each(function(){ var jQ = $(this); if (jQ.data('epiClock') instanceof epiClock) jQ.data('epiClock').resume(); }) default: options = $.extend(true, {}, defaults.epiClock, options); break; } this.each(function(){ var object = $(this), format = (options.format || defaults.formats[options.mode]).split(''), isBuffering = false, label = '', span = '', buffer = '', clock = new epiClock(options, object); object.data('epiClock', clock); $.each(format, function(){ x = this+''; switch (x){ case ' ': if (!isBuffering) $(span).addClass('ecSpacer').appendTo(object); else buffer += x; break; case '{': isBuffering = true; break; case '}': isBuffering = false; $(label).html(buffer).appendTo(object); buffer = ''; break; default: // If we're buffering, this is label text if (isBuffering) buffer += x; // If it's a special character, it will be span updated else if (Date.prototype[x] || clock[x]) { clock.frame[x] = $(span).attr('ref', x).appendTo(object); } // If it's anything else, it's a single char label seperator else $(label).addClass('ecSeparator').html(x).appendTo(object); break; } }); clock.selfLoc = clocks.push(object) - 1; }) return this; } function epiClock(options, element){ if (this instanceof epiClock) return this.init(options, element); else return new epiClock(options, element); } epiClock.prototype = { Q: function() { return this.arbitrary.years }, E: function() { return this.arbitrary.days }, e: function() { return this.arbitrary.days.pad(0) }, zero: new Date(0), pause: function(){ if (this.dead) return; this.paused = new Date().valueOf(); this.dead = true; }, resume: function(){ if (!this.dead) return; if (this.mode == EC_STOPWATCH) this.displace += (this.paused - new Date().valueOf()); this.paused = 0; this.dead = false; }, kill: function(){ // Remove and Renumber Clocks Array clocks.splice(this.selfLoc,1); $.each(clocks, function(i){this.data('epiClock').selfLoc = i}); // Call on kill, set dead if ($.isFunction(this.onKill)) this.onKill(); this.dead = true; }, init: function(options, element){ if (options.mode < EC_CLOCK || options.mode > EC_HOLDUP) throw new Exception( 'Invalid Clock Mode.' ); var clock = this; $.each(options, function(k, v){ clock[k] = v; }); switch (this.mode){ case EC_LOOP: case EC_EXPIRE: this.target = this.target || new Date(); case EC_COUNTDOWN: case EC_ROLLOVER: this.modifier = -1; this.variance = 1; break; case EC_STOPWATCH: this.displace += -1 * new Date().valueOf(); return; case EC_HOLDUP: this.variance = -1; this.modifier = 1; break; default: this.modifier = 1; this.variance = 0; break; } if (this.gmt) this.normalize(); switch (true){ case this.target instanceof Date: this.target = this.target.valueOf(); break; case typeof this.target == 'string': this.target = new Date(this.target).valueOf(); break; } this.displace += this.modifier * this.calculateOffset(); }, calculateOffset: function(offset){ offset = offset || this.offset; return ( offset.years * 3157056e4 + offset.days * 864e5 + offset.hours * 36e5 + offset.minutes * 6e4 + (this.variance + offset.seconds) * 1e3 ); }, normalize: function(){ this.displace += new Date().getTimezoneOffset()*6e4; }, render: function(){ if (!this.tick()) return; var clock = this, time = (this.mode == EC_HOLDUP) ? this.zero : this.now; $.each(this.frame, function(k,v){ var val = ($.isFunction(time[k]) ? time[k]() : clock[k]()) + ''; if (v.data('last') != val) clock.onRender(v, val); v.data('last', val) }) }, tick: function(){ if (this.dead) return false; var now = new Date().valueOf() + this.displace; switch (this.mode){ case EC_HOLDUP: if (this.target < now) this.mode = EC_COUNTUP; case EC_COUNTUP: now -= this.target; break; case EC_ROLLOVER: if (now > this.target) now = now - this.target; else now = this.target - now; break; case EC_COUNTDOWN: case EC_EXPIRE: case EC_LOOP: now = this.target - now; if (now < this.tolerance) return this.timerEnd(); break; } this.now = new Date(now); var days = this.now.V(); if (days <= this.daysadded) return true; this.daysadded = days; this.arbitrary.days += days; if (this.arbitrary.days < 365) return true; this.arbitrary.years += Math.floor(this.arbitrary.days/365.4 % 365.4); this.arbitrary.days = Math.floor(this.arbitrary.days % 365.4); return true; }, timerEnd: function(){ if ($.isFunction(this.onTimer)) this.onTimer(); switch (this.mode){ case EC_COUNTDOWN: case EC_EXPIRE: this.kill(); break; case EC_LOOP: this.displace += this.modifier * this.calculateOffset(); return this.render(); case EC_ROLLOVER: this.mode = EC_COUNTUP; return true; } this.now = new Date(0); return true; } }; $.extend(String.prototype, { pad: function(s,l){ l=l||2; return this.length < l ? new Array(1+l-this.length).join(s) + this : this }, rpad: function(s,l){ l=l||2; return this.length < l ? this + new Array(1+l-this.length).join(s) : this } }) $.extend(Number.prototype, { pad: function(s,l){ return (this+'').pad(s,l) }, rpad: function(s,l){ return (this+'').rpad(s,l) } }) /** Prototype the Date function **/ $.extend(Date.prototype, { // Assistance Definitions modCalc: function(mod1,mod2){return (Math.floor(Math.floor(this.valueOf()/1e3)/mod1)%mod2)}, months: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'], days: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'], suffix: [null, 'st', 'nd', 'rd'], // Timer Functions V: function(){return this.modCalc(864e2,1e5)}, // Days v: function(){return this.V().pad(0)}, // Paded Days K: function(){return this.V()%365}, // Days Offset for Years k: function(){return this.K().pad(0)}, // Padded Offset Days X: function(){return this.modCalc(36e2,24)}, // Hours x: function(){return this.X().pad(0)}, // Padded Hours // Day d: function() { return this.getDate().pad('0') }, D: function() { return this.days[this.getDay()].substring(0,3) }, j: function() { return this.getDate() }, l: function() { return this.days[this.getDay()] }, N: function() { return this.getDay() + 1 }, S: function() { return this.suffix[this.getDate()] || 'th' }, w: function() { return this.getDay() }, z: function() { return Math.round((this-this.f())/864e5) }, // Week W: function() { return Math.ceil(((((this-this.f())/864e5) + this.f().w())/7)) }, // Month F: function() { return this.months[this.getMonth()]; }, m: function() { return (this.getMonth()+1).pad(0) }, M: function() { return this.months[this.getMonth()].substring(0,3) }, n: function() { return this.getMonth() + 1 }, // Year L: function() { var Y = this.Y(); return Y%4 ? false : Y%100 ? true : Y%400 ? false : true }, f: function() { return new Date(this.getFullYear(),0,1) }, Y: function() { return this.getFullYear() }, y: function() { return ('' + this.getFullYear()).substr(2) }, // Time a: function() { return this.getHours() < 12 ? 'am' : 'pm' }, A: function() { return this.a().toUpperCase() }, B: function() { return Math.floor((((this.getHours()) * 36e5) + (this.getMinutes() * 6e4) + (this.getSeconds() * 1e3))/864e2).pad(0,3) }, g: function() { return this.getHours()%12 || 12 }, G: function() { return this.getHours() }, h: function() { return this.g().pad('0') }, H: function() { return this.getHours().pad('0') }, i: function() { return this.getMinutes().pad(0) }, s: function() { return this.getSeconds().pad('0') }, u: function() { return this.getTime()%1000 }, // Timezone O: function() { var t = this.getTimezoneOffset() / 60; return (t >= 0 ? '+' : '-') + Math.abs(t).pad(0).rpad(0,4) }, P: function() { var t = this.O(); return t.substr(0,3) + ':' + t.substr(3)}, Z: function() { return this.getTimezoneOffset() * 60;}, // Full Date/Time c: function() { return this.Y()+'-'+this.m()+'-'+this.d()+'T'+this.H()+':'+this.i()+':'+this.s()+this.P()}, r: function() { return this.toString() }, U: function() { return this.getTime() / 1000 } }); })(jQuery);