jquery.epiclock.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. /**
  2. * epiClock 2.0 - Create Epic Clocks Easily
  3. *
  4. * Copyright (c) 2008 Eric Garside (http://eric.garside.name)
  5. * Dual licensed under:
  6. * MIT: http://www.opensource.org/licenses/mit-license.php
  7. * GPLv3: http://www.opensource.org/licenses/gpl-3.0.html
  8. */
  9. // Manager States
  10. var EC_HALT = 'disable',
  11. EC_RUN = 'enable',
  12. EC_KILL = 'destroy',
  13. // Clock Types
  14. EC_CLOCK = 0,
  15. EC_COUNTDOWN = 1,
  16. EC_COUNTUP = 2,
  17. EC_ROLLOVER = 3,
  18. EC_EXPIRE = 4,
  19. EC_LOOP = 5,
  20. EC_STOPWATCH = 6,
  21. EC_HOLDUP = 7;
  22. ;(function($){
  23. var defaults = {
  24. epiClock: {
  25. offset: {
  26. hours: 0,
  27. minutes: 0,
  28. seconds: 0,
  29. days: 0,
  30. years: 0
  31. },
  32. arbitrary: {
  33. days: 0,
  34. years: 0
  35. },
  36. display: {
  37. years: true,
  38. military: false
  39. },
  40. gmt: false,
  41. target: null,
  42. onTimer: null,
  43. onKill: null,
  44. onRender: function(v,val){v.html(val)},
  45. format: null,
  46. frame: {},
  47. dead: false,
  48. displace: 0,
  49. modifier: 0,
  50. variance: 0,
  51. daysadded: 0,
  52. paused: 0,
  53. tolerance: 0,
  54. selfLoc: -1,
  55. mode: EC_CLOCK
  56. },
  57. formats: [
  58. 'F j, Y, g:i:s a', // EC_CLOCK
  59. 'V{d} x{h} i{m} s{s}', // EC_COUNTDOWN
  60. 'Q{y} K{d} x{h} i{m} s{s}', // EC_COUNTUP
  61. 'V{d} x{h} i{m} s{s}', // EC_ROLLOVER
  62. 'x{h} i{m} s{s}', // EC_EXPIRE
  63. 'i{m} s{s}', // EC_LOOP
  64. 'x{h} i{m} s{s}', // EC_STOPWATCH
  65. 'Q{y} K{d} x{h} i{m} s{s}' // EC_HOLDUP
  66. ]
  67. },
  68. // The current mode the clock manager is in
  69. current = null,
  70. // The interval timer for the clock
  71. loop = null,
  72. // The clocks we're managing
  73. clocks = [];
  74. /**
  75. * jQuery Entry Point - Clock Manager
  76. *
  77. * Provides an interface for the user to pause, destroy, or resume/start all clocks.
  78. */
  79. $.epiclock = function(mode, precision){
  80. mode = mode || EC_RUN;
  81. precision = precision || 5e2;
  82. if (mode == current) return;
  83. switch (mode){
  84. case EC_KILL:
  85. $.each(clocks, function(){
  86. this.removeData('epiClock');
  87. })
  88. clocks = [];
  89. case EC_HALT:
  90. if (loop){
  91. clearInterval(loop);
  92. loop = null;
  93. }
  94. current = mode;
  95. break;
  96. case EC_RUN:
  97. if (!loop){
  98. cycleClocks();
  99. loop = setInterval(cycleClocks, precision);
  100. }
  101. current = mode;
  102. break;
  103. }
  104. return this;
  105. }
  106. function cycleClocks(){
  107. $.each(clocks, function(i){
  108. this.data('epiClock').render();
  109. })
  110. }
  111. /**
  112. * jQuery Entry Point
  113. *
  114. * Creates the clock displays
  115. */
  116. $.fn.epiclock = function(options){
  117. switch (options){
  118. case 'destroy':
  119. return this.each(function(){
  120. var jQ = $(this);
  121. if (jQ.data('epiClock') instanceof epiClock)
  122. jQ.data('epiClock').kill();
  123. })
  124. case 'disable':
  125. return this.each(function(){
  126. var jQ = $(this);
  127. if (jQ.data('epiClock') instanceof epiClock)
  128. jQ.data('epiClock').pause();
  129. })
  130. case 'enable':
  131. return this.each(function(){
  132. var jQ = $(this);
  133. if (jQ.data('epiClock') instanceof epiClock)
  134. jQ.data('epiClock').resume();
  135. })
  136. default:
  137. options = $.extend(true, {}, defaults.epiClock, options);
  138. break;
  139. }
  140. this.each(function(){
  141. var object = $(this),
  142. format = (options.format || defaults.formats[options.mode]).split(''),
  143. isBuffering = false,
  144. label = '<label class="epiClock"></label>',
  145. span = '<span class="epiClock"></span>',
  146. buffer = '',
  147. clock = new epiClock(options, object);
  148. object.data('epiClock', clock);
  149. $.each(format, function(){
  150. x = this+'';
  151. switch (x){
  152. case ' ':
  153. if (!isBuffering)
  154. $(span).addClass('ecSpacer').appendTo(object);
  155. else buffer += x;
  156. break;
  157. case '{':
  158. isBuffering = true;
  159. break;
  160. case '}':
  161. isBuffering = false;
  162. $(label).html(buffer).appendTo(object);
  163. buffer = '';
  164. break;
  165. default:
  166. // If we're buffering, this is label text
  167. if (isBuffering) buffer += x;
  168. // If it's a special character, it will be span updated
  169. else if (Date.prototype[x] || clock[x]) {
  170. clock.frame[x] = $(span).attr('ref', x).appendTo(object);
  171. }
  172. // If it's anything else, it's a single char label seperator
  173. else
  174. $(label).addClass('ecSeparator').html(x).appendTo(object);
  175. break;
  176. }
  177. });
  178. clock.selfLoc = clocks.push(object) - 1;
  179. })
  180. return this;
  181. }
  182. function epiClock(options, element){
  183. if (this instanceof epiClock)
  184. return this.init(options, element);
  185. else return new epiClock(options, element);
  186. }
  187. epiClock.prototype = {
  188. Q: function() { return this.arbitrary.years },
  189. E: function() { return this.arbitrary.days },
  190. e: function() { return this.arbitrary.days.pad(0) },
  191. zero: new Date(0),
  192. pause: function(){
  193. if (this.dead) return;
  194. this.paused = new Date().valueOf();
  195. this.dead = true;
  196. },
  197. resume: function(){
  198. if (!this.dead) return;
  199. if (this.mode == EC_STOPWATCH)
  200. this.displace += (this.paused - new Date().valueOf());
  201. this.paused = 0;
  202. this.dead = false;
  203. },
  204. kill: function(){
  205. // Remove and Renumber Clocks Array
  206. clocks.splice(this.selfLoc,1);
  207. $.each(clocks, function(i){this.data('epiClock').selfLoc = i});
  208. // Call on kill, set dead
  209. if ($.isFunction(this.onKill)) this.onKill();
  210. this.dead = true;
  211. },
  212. init: function(options, element){
  213. if (options.mode < EC_CLOCK || options.mode > EC_HOLDUP)
  214. throw new Exception( 'Invalid Clock Mode.' );
  215. var clock = this;
  216. $.each(options, function(k, v){
  217. clock[k] = v;
  218. });
  219. switch (this.mode){
  220. case EC_LOOP:
  221. case EC_EXPIRE:
  222. this.target = this.target || new Date();
  223. case EC_COUNTDOWN:
  224. case EC_ROLLOVER:
  225. this.modifier = -1;
  226. this.variance = 1;
  227. break;
  228. case EC_STOPWATCH:
  229. this.displace += -1 * new Date().valueOf();
  230. return;
  231. case EC_HOLDUP:
  232. this.variance = -1;
  233. this.modifier = 1;
  234. break;
  235. default:
  236. this.modifier = 1;
  237. this.variance = 0;
  238. break;
  239. }
  240. if (this.gmt)
  241. this.normalize();
  242. switch (true){
  243. case this.target instanceof Date:
  244. this.target = this.target.valueOf();
  245. break;
  246. case typeof this.target == 'string':
  247. this.target = new Date(this.target).valueOf();
  248. break;
  249. }
  250. this.displace += this.modifier * this.calculateOffset();
  251. },
  252. calculateOffset: function(offset){
  253. offset = offset || this.offset;
  254. return (
  255. offset.years * 3157056e4 +
  256. offset.days * 864e5 +
  257. offset.hours * 36e5 +
  258. offset.minutes * 6e4 +
  259. (this.variance + offset.seconds) * 1e3
  260. );
  261. },
  262. normalize: function(){
  263. this.displace += new Date().getTimezoneOffset()*6e4;
  264. },
  265. render: function(){
  266. if (!this.tick()) return;
  267. var clock = this,
  268. time = (this.mode == EC_HOLDUP) ? this.zero : this.now;
  269. $.each(this.frame, function(k,v){
  270. var val = ($.isFunction(time[k]) ? time[k]() : clock[k]()) + '';
  271. if (v.data('last') != val) clock.onRender(v, val);
  272. v.data('last', val)
  273. })
  274. },
  275. tick: function(){
  276. if (this.dead) return false;
  277. var now = new Date().valueOf() + this.displace;
  278. switch (this.mode){
  279. case EC_HOLDUP:
  280. if (this.target < now) this.mode = EC_COUNTUP;
  281. case EC_COUNTUP:
  282. now -= this.target;
  283. break;
  284. case EC_ROLLOVER:
  285. if (now > this.target) now = now - this.target;
  286. else now = this.target - now;
  287. break;
  288. case EC_COUNTDOWN:
  289. case EC_EXPIRE:
  290. case EC_LOOP:
  291. now = this.target - now;
  292. if (now < this.tolerance) return this.timerEnd();
  293. break;
  294. }
  295. this.now = new Date(now);
  296. var days = this.now.V();
  297. if (days <= this.daysadded) return true;
  298. this.daysadded = days;
  299. this.arbitrary.days += days;
  300. if (this.arbitrary.days < 365) return true;
  301. this.arbitrary.years += Math.floor(this.arbitrary.days/365.4 % 365.4);
  302. this.arbitrary.days = Math.floor(this.arbitrary.days % 365.4);
  303. return true;
  304. },
  305. timerEnd: function(){
  306. if ($.isFunction(this.onTimer)) this.onTimer();
  307. switch (this.mode){
  308. case EC_COUNTDOWN:
  309. case EC_EXPIRE:
  310. this.kill();
  311. break;
  312. case EC_LOOP:
  313. this.displace += this.modifier * this.calculateOffset();
  314. return this.render();
  315. case EC_ROLLOVER:
  316. this.mode = EC_COUNTUP;
  317. return true;
  318. }
  319. this.now = new Date(0);
  320. return true;
  321. }
  322. };
  323. $.extend(String.prototype, {
  324. pad: function(s,l){ l=l||2; return this.length < l ? new Array(1+l-this.length).join(s) + this : this },
  325. rpad: function(s,l){ l=l||2; return this.length < l ? this + new Array(1+l-this.length).join(s) : this }
  326. })
  327. $.extend(Number.prototype, {
  328. pad: function(s,l){ return (this+'').pad(s,l) },
  329. rpad: function(s,l){ return (this+'').rpad(s,l) }
  330. })
  331. /** Prototype the Date function **/
  332. $.extend(Date.prototype, {
  333. // Assistance Definitions
  334. modCalc: function(mod1,mod2){return (Math.floor(Math.floor(this.valueOf()/1e3)/mod1)%mod2)},
  335. months: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'],
  336. days: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'],
  337. suffix: [null, 'st', 'nd', 'rd'],
  338. // Timer Functions
  339. V: function(){return this.modCalc(864e2,1e5)}, // Days
  340. v: function(){return this.V().pad(0)}, // Paded Days
  341. K: function(){return this.V()%365}, // Days Offset for Years
  342. k: function(){return this.K().pad(0)}, // Padded Offset Days
  343. X: function(){return this.modCalc(36e2,24)}, // Hours
  344. x: function(){return this.X().pad(0)}, // Padded Hours
  345. // Day
  346. d: function() { return this.getDate().pad('0') },
  347. D: function() { return this.days[this.getDay()].substring(0,3) },
  348. j: function() { return this.getDate() },
  349. l: function() { return this.days[this.getDay()] },
  350. N: function() { return this.getDay() + 1 },
  351. S: function() { return this.suffix[this.getDate()] || 'th' },
  352. w: function() { return this.getDay() },
  353. z: function() { return Math.round((this-this.f())/864e5) },
  354. // Week
  355. W: function() { return Math.ceil(((((this-this.f())/864e5) + this.f().w())/7)) },
  356. // Month
  357. F: function() { return this.months[this.getMonth()]; },
  358. m: function() { return (this.getMonth()+1).pad(0) },
  359. M: function() { return this.months[this.getMonth()].substring(0,3) },
  360. n: function() { return this.getMonth() + 1 },
  361. // Year
  362. L: function() { var Y = this.Y(); return Y%4 ? false : Y%100 ? true : Y%400 ? false : true },
  363. f: function() { return new Date(this.getFullYear(),0,1) },
  364. Y: function() { return this.getFullYear() },
  365. y: function() { return ('' + this.getFullYear()).substr(2) },
  366. // Time
  367. a: function() { return this.getHours() < 12 ? 'am' : 'pm' },
  368. A: function() { return this.a().toUpperCase() },
  369. B: function() { return Math.floor((((this.getHours()) * 36e5) + (this.getMinutes() * 6e4) + (this.getSeconds() * 1e3))/864e2).pad(0,3) },
  370. g: function() { return this.getHours()%12 || 12 },
  371. G: function() { return this.getHours() },
  372. h: function() { return this.g().pad('0') },
  373. H: function() { return this.getHours().pad('0') },
  374. i: function() { return this.getMinutes().pad(0) },
  375. s: function() { return this.getSeconds().pad('0') },
  376. u: function() { return this.getTime()%1000 },
  377. // Timezone
  378. O: function() { var t = this.getTimezoneOffset() / 60; return (t >= 0 ? '+' : '-') + Math.abs(t).pad(0).rpad(0,4) },
  379. P: function() { var t = this.O(); return t.substr(0,3) + ':' + t.substr(3)},
  380. Z: function() { return this.getTimezoneOffset() * 60;},
  381. // Full Date/Time
  382. c: function() { return this.Y()+'-'+this.m()+'-'+this.d()+'T'+this.H()+':'+this.i()+':'+this.s()+this.P()},
  383. r: function() { return this.toString() },
  384. U: function() { return this.getTime() / 1000 }
  385. });
  386. })(jQuery);