APIs

Show:
  1. /*global window */
  2. /**
  3. * @license countdown.js v2.4.1 http://countdownjs.org
  4. * Copyright (c)2006-2014 Stephen M. McKamey.
  5. * Licensed under The MIT License.
  6. */
  7. /*jshint bitwise:false */
  8.  
  9. /**
  10. * @public
  11. * @type {Object|null}
  12. */
  13. var module;
  14.  
  15. /**
  16. * API entry
  17. * @public
  18. * @param {function(Object)|Date|number} start the starting date
  19. * @param {function(Object)|Date|number} end the ending date
  20. * @param {number} units the units to populate
  21. * @return {Object|number}
  22. */
  23. var countdown = (/**
  24. * @param {Object} module CommonJS Module
  25. */
  26. function (module) {
  27. /*jshint smarttabs:true */
  28.  
  29. 'use strict';
  30.  
  31. /**
  32. * @private
  33. * @const
  34. * @type {number}
  35. */
  36. var MILLISECONDS = 0x001;
  37.  
  38. /**
  39. * @private
  40. * @const
  41. * @type {number}
  42. */
  43. var SECONDS = 0x002;
  44.  
  45. /**
  46. * @private
  47. * @const
  48. * @type {number}
  49. */
  50. var MINUTES = 0x004;
  51.  
  52. /**
  53. * @private
  54. * @const
  55. * @type {number}
  56. */
  57. var HOURS = 0x008;
  58.  
  59. /**
  60. * @private
  61. * @const
  62. * @type {number}
  63. */
  64. var DAYS = 0x010;
  65.  
  66. /**
  67. * @private
  68. * @const
  69. * @type {number}
  70. */
  71. var WEEKS = 0x020;
  72.  
  73. /**
  74. * @private
  75. * @const
  76. * @type {number}
  77. */
  78. var MONTHS = 0x040;
  79.  
  80. /**
  81. * @private
  82. * @const
  83. * @type {number}
  84. */
  85. var YEARS = 0x080;
  86.  
  87. /**
  88. * @private
  89. * @const
  90. * @type {number}
  91. */
  92. var DECADES = 0x100;
  93.  
  94. /**
  95. * @private
  96. * @const
  97. * @type {number}
  98. */
  99. var CENTURIES = 0x200;
  100.  
  101. /**
  102. * @private
  103. * @const
  104. * @type {number}
  105. */
  106. var MILLENNIA = 0x400;
  107.  
  108. /**
  109. * @private
  110. * @const
  111. * @type {number}
  112. */
  113. var DEFAULTS = YEARS | MONTHS | DAYS | HOURS | MINUTES | SECONDS;
  114.  
  115. /**
  116. * @private
  117. * @const
  118. * @type {number}
  119. */
  120. var MILLISECONDS_PER_SECOND = 1000;
  121.  
  122. /**
  123. * @private
  124. * @const
  125. * @type {number}
  126. */
  127. var SECONDS_PER_MINUTE = 60;
  128.  
  129. /**
  130. * @private
  131. * @const
  132. * @type {number}
  133. */
  134. var MINUTES_PER_HOUR = 60;
  135.  
  136. /**
  137. * @private
  138. * @const
  139. * @type {number}
  140. */
  141. var HOURS_PER_DAY = 24;
  142.  
  143. /**
  144. * @private
  145. * @const
  146. * @type {number}
  147. */
  148. var MILLISECONDS_PER_DAY = HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE * MILLISECONDS_PER_SECOND;
  149.  
  150. /**
  151. * @private
  152. * @const
  153. * @type {number}
  154. */
  155. var DAYS_PER_WEEK = 7;
  156.  
  157. /**
  158. * @private
  159. * @const
  160. * @type {number}
  161. */
  162. var MONTHS_PER_YEAR = 12;
  163.  
  164. /**
  165. * @private
  166. * @const
  167. * @type {number}
  168. */
  169. var YEARS_PER_DECADE = 10;
  170.  
  171. /**
  172. * @private
  173. * @const
  174. * @type {number}
  175. */
  176. var DECADES_PER_CENTURY = 10;
  177.  
  178. /**
  179. * @private
  180. * @const
  181. * @type {number}
  182. */
  183. var CENTURIES_PER_MILLENNIUM = 10;
  184.  
  185. /**
  186. * @private
  187. * @param {number} x number
  188. * @return {number}
  189. */
  190. var ceil = Math.ceil;
  191.  
  192. /**
  193. * @private
  194. * @param {number} x number
  195. * @return {number}
  196. */
  197. var floor = Math.floor;
  198.  
  199. /**
  200. * @private
  201. * @param {Date} ref reference date
  202. * @param {number} shift number of months to shift
  203. * @return {number} number of days shifted
  204. */
  205. function borrowMonths(ref, shift) {
  206. var prevTime = ref.getTime();
  207.  
  208. // increment month by shift
  209. ref.setMonth(ref.getMonth() + shift);
  210.  
  211. // this is the trickiest since months vary in length
  212. return Math.round((ref.getTime() - prevTime) / MILLISECONDS_PER_DAY);
  213. }
  214.  
  215. /**
  216. * @private
  217. * @param {Date} ref reference date
  218. * @return {number} number of days
  219. */
  220. function daysPerMonth(ref) {
  221. var a = ref.getTime();
  222.  
  223. // increment month by 1
  224. var b = new Date(a);
  225. b.setMonth(ref.getMonth() + 1);
  226.  
  227. // this is the trickiest since months vary in length
  228. return Math.round((b.getTime() - a) / MILLISECONDS_PER_DAY);
  229. }
  230.  
  231. /**
  232. * @private
  233. * @param {Date} ref reference date
  234. * @return {number} number of days
  235. */
  236. function daysPerYear(ref) {
  237. var a = ref.getTime();
  238.  
  239. // increment year by 1
  240. var b = new Date(a);
  241. b.setFullYear(ref.getFullYear() + 1);
  242.  
  243. // this is the trickiest since years (periodically) vary in length
  244. return Math.round((b.getTime() - a) / MILLISECONDS_PER_DAY);
  245. }
  246.  
  247. /**
  248. * @private
  249. * @const
  250. * @type {number}
  251. */
  252. var LABEL_MILLISECONDS = 0;
  253.  
  254. /**
  255. * @private
  256. * @const
  257. * @type {number}
  258. */
  259. var LABEL_SECONDS = 1;
  260.  
  261. /**
  262. * @private
  263. * @const
  264. * @type {number}
  265. */
  266. var LABEL_MINUTES = 2;
  267.  
  268. /**
  269. * @private
  270. * @const
  271. * @type {number}
  272. */
  273. var LABEL_HOURS = 3;
  274.  
  275. /**
  276. * @private
  277. * @const
  278. * @type {number}
  279. */
  280. var LABEL_DAYS = 4;
  281.  
  282. /**
  283. * @private
  284. * @const
  285. * @type {number}
  286. */
  287. var LABEL_WEEKS = 5;
  288.  
  289. /**
  290. * @private
  291. * @const
  292. * @type {number}
  293. */
  294. var LABEL_MONTHS = 6;
  295.  
  296. /**
  297. * @private
  298. * @const
  299. * @type {number}
  300. */
  301. var LABEL_YEARS = 7;
  302.  
  303. /**
  304. * @private
  305. * @const
  306. * @type {number}
  307. */
  308. var LABEL_DECADES = 8;
  309.  
  310. /**
  311. * @private
  312. * @const
  313. * @type {number}
  314. */
  315. var LABEL_CENTURIES = 9;
  316.  
  317. /**
  318. * @private
  319. * @const
  320. * @type {number}
  321. */
  322. var LABEL_MILLENNIA = 10;
  323.  
  324. /**
  325. * @private
  326. * @type {Array}
  327. */
  328. var LABELS_SINGLUAR;
  329.  
  330. /**
  331. * @private
  332. * @type {Array}
  333. */
  334. var LABELS_PLURAL;
  335.  
  336. /**
  337. * @private
  338. * @type {string}
  339. */
  340. var LABEL_LAST;
  341.  
  342. /**
  343. * @private
  344. * @type {string}
  345. */
  346. var LABEL_DELIM;
  347.  
  348. /**
  349. * @private
  350. * @param {number} value
  351. * @param {number} unit unit index into label list
  352. * @return {string}
  353. */
  354. function plurality(value, unit) {
  355. return value + ' ' + ((value === 1) ? LABELS_SINGLUAR[unit] : LABELS_PLURAL[unit]);
  356. }
  357.  
  358. /**
  359. * Formats the entries as English labels
  360. *
  361. * @private
  362. * @param {Timespan} ts
  363. * @return {Array}
  364. */
  365. var formatList;
  366.  
  367. /**
  368. * Timespan representation of a duration of time
  369. *
  370. * @private
  371. * @this {Timespan}
  372. * @constructor
  373. */
  374. function Timespan() {
  375. }
  376.  
  377. /**
  378. * Formats the Timespan as a sentance
  379. *
  380. * @private
  381. * @return {string}
  382. */
  383. Timespan.prototype.toString = function () {
  384. var label = formatList(this);
  385.  
  386. var count = label.length;
  387. if (!count) {
  388. return '';
  389. }
  390. if (count > 1) {
  391. label[count - 1] = LABEL_LAST + label[count - 1];
  392. }
  393. return label.join(LABEL_DELIM);
  394. };
  395.  
  396. /**
  397. * Formats the Timespan as HTML
  398. *
  399. * @private
  400. * @param {string} tag HTML tag name to wrap each value
  401. * @return {string}
  402. */
  403. Timespan.prototype.toHTML = function (tag) {
  404. tag = tag || 'span';
  405. var label = formatList(this);
  406.  
  407. var count = label.length;
  408. if (!count) {
  409. return '';
  410. }
  411. for (var i = 0; i < count; i++) {
  412. // wrap each unit in tag
  413. label[i] = '<' + tag + '>' + label[i] + '</' + tag + '>';
  414. }
  415. if (--count) {
  416. label[count] = LABEL_LAST + label[count];
  417. }
  418. return label.join(LABEL_DELIM);
  419. };
  420.  
  421. /**
  422. * Formats the entries as English labels
  423. *
  424. * @private
  425. * @param {Timespan} ts
  426. * @return {Array}
  427. */
  428. formatList = function (ts) {
  429. var list = [];
  430.  
  431. var value = ts.millennia;
  432. if (value) {
  433. list.push(plurality(value, LABEL_MILLENNIA));
  434. }
  435.  
  436. value = ts.centuries;
  437. if (value) {
  438. list.push(plurality(value, LABEL_CENTURIES));
  439. }
  440.  
  441. value = ts.decades;
  442. if (value) {
  443. list.push(plurality(value, LABEL_DECADES));
  444. }
  445.  
  446. value = ts.years;
  447. if (value) {
  448. list.push(plurality(value, LABEL_YEARS));
  449. }
  450.  
  451. value = ts.months;
  452. if (value) {
  453. list.push(plurality(value, LABEL_MONTHS));
  454. }
  455.  
  456. value = ts.weeks;
  457. if (value) {
  458. list.push(plurality(value, LABEL_WEEKS));
  459. }
  460.  
  461. value = ts.days;
  462. if (value) {
  463. list.push(plurality(value, LABEL_DAYS));
  464. }
  465.  
  466. value = ts.hours;
  467. if (value) {
  468. list.push(plurality(value, LABEL_HOURS));
  469. }
  470.  
  471. value = ts.minutes;
  472. if (value) {
  473. list.push(plurality(value, LABEL_MINUTES));
  474. }
  475.  
  476. value = ts.seconds;
  477. if (value) {
  478. list.push(plurality(value, LABEL_SECONDS));
  479. }
  480.  
  481. value = ts.milliseconds;
  482. if (value) {
  483. list.push(plurality(value, LABEL_MILLISECONDS));
  484. }
  485.  
  486. return list;
  487. };
  488.  
  489. /**
  490. * Borrow any underflow units, carry any overflow units
  491. *
  492. * @private
  493. * @param {Timespan} ts
  494. * @param {string} toUnit
  495. */
  496. function rippleRounded(ts, toUnit) {
  497. switch (toUnit) {
  498. case 'seconds':
  499. if (ts.seconds !== SECONDS_PER_MINUTE || isNaN(ts.minutes)) {
  500. return;
  501. }
  502. // ripple seconds up to minutes
  503. ts.minutes++;
  504. ts.seconds = 0;
  505.  
  506. /* falls through */
  507. case 'minutes':
  508. if (ts.minutes !== MINUTES_PER_HOUR || isNaN(ts.hours)) {
  509. return;
  510. }
  511. // ripple minutes up to hours
  512. ts.hours++;
  513. ts.minutes = 0;
  514.  
  515. /* falls through */
  516. case 'hours':
  517. if (ts.hours !== HOURS_PER_DAY || isNaN(ts.days)) {
  518. return;
  519. }
  520. // ripple hours up to days
  521. ts.days++;
  522. ts.hours = 0;
  523.  
  524. /* falls through */
  525. case 'days':
  526. if (ts.days !== DAYS_PER_WEEK || isNaN(ts.weeks)) {
  527. return;
  528. }
  529. // ripple days up to weeks
  530. ts.weeks++;
  531. ts.days = 0;
  532.  
  533. /* falls through */
  534. case 'weeks':
  535. if (ts.weeks !== daysPerMonth(ts.refMonth) / DAYS_PER_WEEK || isNaN(ts.months)) {
  536. return;
  537. }
  538. // ripple weeks up to months
  539. ts.months++;
  540. ts.weeks = 0;
  541.  
  542. /* falls through */
  543. case 'months':
  544. if (ts.months !== MONTHS_PER_YEAR || isNaN(ts.years)) {
  545. return;
  546. }
  547. // ripple months up to years
  548. ts.years++;
  549. ts.months = 0;
  550.  
  551. /* falls through */
  552. case 'years':
  553. if (ts.years !== YEARS_PER_DECADE || isNaN(ts.decades)) {
  554. return;
  555. }
  556. // ripple years up to decades
  557. ts.decades++;
  558. ts.years = 0;
  559.  
  560. /* falls through */
  561. case 'decades':
  562. if (ts.decades !== DECADES_PER_CENTURY || isNaN(ts.centuries)) {
  563. return;
  564. }
  565. // ripple decades up to centuries
  566. ts.centuries++;
  567. ts.decades = 0;
  568.  
  569. /* falls through */
  570. case 'centuries':
  571. if (ts.centuries !== CENTURIES_PER_MILLENNIUM || isNaN(ts.millennia)) {
  572. return;
  573. }
  574. // ripple centuries up to millennia
  575. ts.millennia++;
  576. ts.centuries = 0;
  577. /* falls through */
  578. }
  579. }
  580.  
  581. /**
  582. * Ripple up partial units one place
  583. *
  584. * @private
  585. * @param {Timespan} ts timespan
  586. * @param {number} frac accumulated fractional value
  587. * @param {string} fromUnit source unit name
  588. * @param {string} toUnit target unit name
  589. * @param {number} conversion multiplier between units
  590. * @param {number} digits max number of decimal digits to output
  591. * @return {number} new fractional value
  592. */
  593. function fraction(ts, frac, fromUnit, toUnit, conversion, digits) {
  594. if (ts[fromUnit] >= 0) {
  595. frac += ts[fromUnit];
  596. delete ts[fromUnit];
  597. }
  598.  
  599. frac /= conversion;
  600. if (frac + 1 <= 1) {
  601. // drop if below machine epsilon
  602. return 0;
  603. }
  604.  
  605. if (ts[toUnit] >= 0) {
  606. // ensure does not have more than specified number of digits
  607. ts[toUnit] = +(ts[toUnit] + frac).toFixed(digits);
  608. rippleRounded(ts, toUnit);
  609. return 0;
  610. }
  611.  
  612. return frac;
  613. }
  614.  
  615. /**
  616. * Ripple up partial units to next existing
  617. *
  618. * @private
  619. * @param {Timespan} ts
  620. * @param {number} digits max number of decimal digits to output
  621. */
  622. function fractional(ts, digits) {
  623. var frac = fraction(ts, 0, 'milliseconds', 'seconds', MILLISECONDS_PER_SECOND, digits);
  624. if (!frac) {
  625. return;
  626. }
  627.  
  628. frac = fraction(ts, frac, 'seconds', 'minutes', SECONDS_PER_MINUTE, digits);
  629. if (!frac) {
  630. return;
  631. }
  632.  
  633. frac = fraction(ts, frac, 'minutes', 'hours', MINUTES_PER_HOUR, digits);
  634. if (!frac) {
  635. return;
  636. }
  637.  
  638. frac = fraction(ts, frac, 'hours', 'days', HOURS_PER_DAY, digits);
  639. if (!frac) {
  640. return;
  641. }
  642.  
  643. frac = fraction(ts, frac, 'days', 'weeks', DAYS_PER_WEEK, digits);
  644. if (!frac) {
  645. return;
  646. }
  647.  
  648. frac = fraction(ts, frac, 'weeks', 'months', daysPerMonth(ts.refMonth) / DAYS_PER_WEEK, digits);
  649. if (!frac) {
  650. return;
  651. }
  652.  
  653. frac = fraction(ts, frac, 'months', 'years', daysPerYear(ts.refMonth) / daysPerMonth(ts.refMonth), digits);
  654. if (!frac) {
  655. return;
  656. }
  657.  
  658. frac = fraction(ts, frac, 'years', 'decades', YEARS_PER_DECADE, digits);
  659. if (!frac) {
  660. return;
  661. }
  662.  
  663. frac = fraction(ts, frac, 'decades', 'centuries', DECADES_PER_CENTURY, digits);
  664. if (!frac) {
  665. return;
  666. }
  667.  
  668. frac = fraction(ts, frac, 'centuries', 'millennia', CENTURIES_PER_MILLENNIUM, digits);
  669.  
  670. // should never reach this with remaining fractional value
  671. if (frac) {
  672. throw new Error('Fractional unit overflow');
  673. }
  674. }
  675.  
  676. /**
  677. * Borrow any underflow units, carry any overflow units
  678. *
  679. * @private
  680. * @param {Timespan} ts
  681. */
  682. function ripple(ts) {
  683. var x;
  684.  
  685. if (ts.milliseconds < 0) {
  686. // ripple seconds down to milliseconds
  687. x = ceil(-ts.milliseconds / MILLISECONDS_PER_SECOND);
  688. ts.seconds -= x;
  689. ts.milliseconds += x * MILLISECONDS_PER_SECOND;
  690.  
  691. } else if (ts.milliseconds >= MILLISECONDS_PER_SECOND) {
  692. // ripple milliseconds up to seconds
  693. ts.seconds += floor(ts.milliseconds / MILLISECONDS_PER_SECOND);
  694. ts.milliseconds %= MILLISECONDS_PER_SECOND;
  695. }
  696.  
  697. if (ts.seconds < 0) {
  698. // ripple minutes down to seconds
  699. x = ceil(-ts.seconds / SECONDS_PER_MINUTE);
  700. ts.minutes -= x;
  701. ts.seconds += x * SECONDS_PER_MINUTE;
  702.  
  703. } else if (ts.seconds >= SECONDS_PER_MINUTE) {
  704. // ripple seconds up to minutes
  705. ts.minutes += floor(ts.seconds / SECONDS_PER_MINUTE);
  706. ts.seconds %= SECONDS_PER_MINUTE;
  707. }
  708.  
  709. if (ts.minutes < 0) {
  710. // ripple hours down to minutes
  711. x = ceil(-ts.minutes / MINUTES_PER_HOUR);
  712. ts.hours -= x;
  713. ts.minutes += x * MINUTES_PER_HOUR;
  714.  
  715. } else if (ts.minutes >= MINUTES_PER_HOUR) {
  716. // ripple minutes up to hours
  717. ts.hours += floor(ts.minutes / MINUTES_PER_HOUR);
  718. ts.minutes %= MINUTES_PER_HOUR;
  719. }
  720.  
  721. if (ts.hours < 0) {
  722. // ripple days down to hours
  723. x = ceil(-ts.hours / HOURS_PER_DAY);
  724. ts.days -= x;
  725. ts.hours += x * HOURS_PER_DAY;
  726.  
  727. } else if (ts.hours >= HOURS_PER_DAY) {
  728. // ripple hours up to days
  729. ts.days += floor(ts.hours / HOURS_PER_DAY);
  730. ts.hours %= HOURS_PER_DAY;
  731. }
  732.  
  733. while (ts.days < 0) {
  734. // NOTE: never actually seen this loop more than once
  735.  
  736. // ripple months down to days
  737. ts.months--;
  738. ts.days += borrowMonths(ts.refMonth, 1);
  739. }
  740.  
  741. // weeks is always zero here
  742.  
  743. if (ts.days >= DAYS_PER_WEEK) {
  744. // ripple days up to weeks
  745. ts.weeks += floor(ts.days / DAYS_PER_WEEK);
  746. ts.days %= DAYS_PER_WEEK;
  747. }
  748.  
  749. if (ts.months < 0) {
  750. // ripple years down to months
  751. x = ceil(-ts.months / MONTHS_PER_YEAR);
  752. ts.years -= x;
  753. ts.months += x * MONTHS_PER_YEAR;
  754.  
  755. } else if (ts.months >= MONTHS_PER_YEAR) {
  756. // ripple months up to years
  757. ts.years += floor(ts.months / MONTHS_PER_YEAR);
  758. ts.months %= MONTHS_PER_YEAR;
  759. }
  760.  
  761. // years is always non-negative here
  762. // decades, centuries and millennia are always zero here
  763.  
  764. if (ts.years >= YEARS_PER_DECADE) {
  765. // ripple years up to decades
  766. ts.decades += floor(ts.years / YEARS_PER_DECADE);
  767. ts.years %= YEARS_PER_DECADE;
  768.  
  769. if (ts.decades >= DECADES_PER_CENTURY) {
  770. // ripple decades up to centuries
  771. ts.centuries += floor(ts.decades / DECADES_PER_CENTURY);
  772. ts.decades %= DECADES_PER_CENTURY;
  773.  
  774. if (ts.centuries >= CENTURIES_PER_MILLENNIUM) {
  775. // ripple centuries up to millennia
  776. ts.millennia += floor(ts.centuries / CENTURIES_PER_MILLENNIUM);
  777. ts.centuries %= CENTURIES_PER_MILLENNIUM;
  778. }
  779. }
  780. }
  781. }
  782.  
  783. /**
  784. * Remove any units not requested
  785. *
  786. * @private
  787. * @param {Timespan} ts
  788. * @param {number} units the units to populate
  789. * @param {number} max number of labels to output
  790. * @param {number} digits max number of decimal digits to output
  791. */
  792. function pruneUnits(ts, units, max, digits) {
  793. var count = 0;
  794.  
  795. // Calc from largest unit to smallest to prevent underflow
  796. if (!(units & MILLENNIA) || (count >= max)) {
  797. // ripple millennia down to centuries
  798. ts.centuries += ts.millennia * CENTURIES_PER_MILLENNIUM;
  799. delete ts.millennia;
  800.  
  801. } else if (ts.millennia) {
  802. count++;
  803. }
  804.  
  805. if (!(units & CENTURIES) || (count >= max)) {
  806. // ripple centuries down to decades
  807. ts.decades += ts.centuries * DECADES_PER_CENTURY;
  808. delete ts.centuries;
  809.  
  810. } else if (ts.centuries) {
  811. count++;
  812. }
  813.  
  814. if (!(units & DECADES) || (count >= max)) {
  815. // ripple decades down to years
  816. ts.years += ts.decades * YEARS_PER_DECADE;
  817. delete ts.decades;
  818.  
  819. } else if (ts.decades) {
  820. count++;
  821. }
  822.  
  823. if (!(units & YEARS) || (count >= max)) {
  824. // ripple years down to months
  825. ts.months += ts.years * MONTHS_PER_YEAR;
  826. delete ts.years;
  827.  
  828. } else if (ts.years) {
  829. count++;
  830. }
  831.  
  832. if (!(units & MONTHS) || (count >= max)) {
  833. // ripple months down to days
  834. if (ts.months) {
  835. ts.days += borrowMonths(ts.refMonth, ts.months);
  836. }
  837. delete ts.months;
  838.  
  839. if (ts.days >= DAYS_PER_WEEK) {
  840. // ripple day overflow back up to weeks
  841. ts.weeks += floor(ts.days / DAYS_PER_WEEK);
  842. ts.days %= DAYS_PER_WEEK;
  843. }
  844.  
  845. } else if (ts.months) {
  846. count++;
  847. }
  848.  
  849. if (!(units & WEEKS) || (count >= max)) {
  850. // ripple weeks down to days
  851. ts.days += ts.weeks * DAYS_PER_WEEK;
  852. delete ts.weeks;
  853.  
  854. } else if (ts.weeks) {
  855. count++;
  856. }
  857.  
  858. if (!(units & DAYS) || (count >= max)) {
  859. //ripple days down to hours
  860. ts.hours += ts.days * HOURS_PER_DAY;
  861. delete ts.days;
  862.  
  863. } else if (ts.days) {
  864. count++;
  865. }
  866.  
  867. if (!(units & HOURS) || (count >= max)) {
  868. // ripple hours down to minutes
  869. ts.minutes += ts.hours * MINUTES_PER_HOUR;
  870. delete ts.hours;
  871.  
  872. } else if (ts.hours) {
  873. count++;
  874. }
  875.  
  876. if (!(units & MINUTES) || (count >= max)) {
  877. // ripple minutes down to seconds
  878. ts.seconds += ts.minutes * SECONDS_PER_MINUTE;
  879. delete ts.minutes;
  880.  
  881. } else if (ts.minutes) {
  882. count++;
  883. }
  884.  
  885. if (!(units & SECONDS) || (count >= max)) {
  886. // ripple seconds down to milliseconds
  887. ts.milliseconds += ts.seconds * MILLISECONDS_PER_SECOND;
  888. delete ts.seconds;
  889.  
  890. } else if (ts.seconds) {
  891. count++;
  892. }
  893.  
  894. // nothing to ripple milliseconds down to
  895. // so ripple back up to smallest existing unit as a fractional value
  896. if (!(units & MILLISECONDS) || (count >= max)) {
  897. fractional(ts, digits);
  898. }
  899. }
  900.  
  901. /**
  902. * Populates the Timespan object
  903. *
  904. * @private
  905. * @param {Timespan} ts
  906. * @param {?Date} start the starting date
  907. * @param {?Date} end the ending date
  908. * @param {number} units the units to populate
  909. * @param {number} max number of labels to output
  910. * @param {number} digits max number of decimal digits to output
  911. */
  912. function populate(ts, start, end, units, max, digits) {
  913. var now = new Date();
  914.  
  915. ts.start = start = start || now;
  916. ts.end = end = end || now;
  917. ts.units = units;
  918.  
  919. ts.value = end.getTime() - start.getTime();
  920. if (ts.value < 0) {
  921. // swap if reversed
  922. var tmp = end;
  923. end = start;
  924. start = tmp;
  925. }
  926.  
  927. // reference month for determining days in month
  928. ts.refMonth = new Date(start.getFullYear(), start.getMonth(), 15, 12, 0, 0);
  929. try {
  930. // reset to initial deltas
  931. ts.millennia = 0;
  932. ts.centuries = 0;
  933. ts.decades = 0;
  934. ts.years = end.getFullYear() - start.getFullYear();
  935. ts.months = end.getMonth() - start.getMonth();
  936. ts.weeks = 0;
  937. ts.days = end.getDate() - start.getDate();
  938. ts.hours = end.getHours() - start.getHours();
  939. ts.minutes = end.getMinutes() - start.getMinutes();
  940. ts.seconds = end.getSeconds() - start.getSeconds();
  941. ts.milliseconds = end.getMilliseconds() - start.getMilliseconds();
  942.  
  943. ripple(ts);
  944. pruneUnits(ts, units, max, digits);
  945.  
  946. } finally {
  947. delete ts.refMonth;
  948. }
  949.  
  950. return ts;
  951. }
  952.  
  953. /**
  954. * Determine an appropriate refresh rate based upon units
  955. *
  956. * @private
  957. * @param {number} units the units to populate
  958. * @return {number} milliseconds to delay
  959. */
  960. function getDelay(units) {
  961. if (units & MILLISECONDS) {
  962. // refresh very quickly
  963. return MILLISECONDS_PER_SECOND / 30; //30Hz
  964. }
  965.  
  966. if (units & SECONDS) {
  967. // refresh every second
  968. return MILLISECONDS_PER_SECOND; //1Hz
  969. }
  970.  
  971. if (units & MINUTES) {
  972. // refresh every minute
  973. return MILLISECONDS_PER_SECOND * SECONDS_PER_MINUTE;
  974. }
  975.  
  976. if (units & HOURS) {
  977. // refresh hourly
  978. return MILLISECONDS_PER_SECOND * SECONDS_PER_MINUTE * MINUTES_PER_HOUR;
  979. }
  980.  
  981. if (units & DAYS) {
  982. // refresh daily
  983. return MILLISECONDS_PER_SECOND * SECONDS_PER_MINUTE * MINUTES_PER_HOUR * HOURS_PER_DAY;
  984. }
  985.  
  986. // refresh the rest weekly
  987. return MILLISECONDS_PER_SECOND * SECONDS_PER_MINUTE * MINUTES_PER_HOUR * HOURS_PER_DAY * DAYS_PER_WEEK;
  988. }
  989.  
  990. /**
  991. * API entry point
  992. *
  993. * @public
  994. * @param {Date|number|null|function(Timespan,number)} start the starting date
  995. * @param {Date|number|null|function(Timespan,number)} end the ending date
  996. * @param {number} units the units to populate
  997. * @param {number} max number of labels to output
  998. * @param {number} digits max number of decimal digits to output
  999. * @return {Timespan|number}
  1000. */
  1001. function countdown(start, end, units, max, digits) {
  1002. var callback;
  1003.  
  1004. // ensure some units or use defaults
  1005. units = +units || DEFAULTS;
  1006. // max must be positive
  1007. max = (max > 0) ? max : NaN;
  1008. // clamp digits to an integer between [0, 20]
  1009. digits = (digits > 0) ? (digits < 20) ? Math.round(digits) : 20 : 0;
  1010.  
  1011. // ensure start date
  1012. if ('function' === typeof start) {
  1013. callback = start;
  1014. start = null;
  1015.  
  1016. } else if (!(start instanceof Date)) {
  1017. start = (start !== null && isFinite(start)) ? new Date(start) : null;
  1018. }
  1019.  
  1020. // ensure end date
  1021. if ('function' === typeof end) {
  1022. callback = end;
  1023. end = null;
  1024.  
  1025. } else if (!(end instanceof Date)) {
  1026. end = (end !== null && isFinite(end)) ? new Date(end) : null;
  1027. }
  1028.  
  1029. if (!start && !end) {
  1030. // used for unit testing
  1031. return new Timespan();
  1032. }
  1033.  
  1034. if (!callback) {
  1035. return populate(new Timespan(), /** @type{?Date} */(start), /** @type{?Date} */(end), units, max, digits);
  1036. }
  1037.  
  1038. // base delay off units
  1039. var delay = getDelay(units),
  1040. timerId,
  1041. fn = function () {
  1042. callback(
  1043. populate(new Timespan(), /** @type{?Date} */(start), /** @type{?Date} */(end), units, max, digits),
  1044. timerId
  1045. );
  1046. };
  1047.  
  1048. fn();
  1049. return (timerId = setInterval(fn, delay));
  1050. }
  1051.  
  1052. /**
  1053. * @public
  1054. * @const
  1055. * @type {number}
  1056. */
  1057. countdown.MILLISECONDS = MILLISECONDS;
  1058.  
  1059. /**
  1060. * @public
  1061. * @const
  1062. * @type {number}
  1063. */
  1064. countdown.SECONDS = SECONDS;
  1065.  
  1066. /**
  1067. * @public
  1068. * @const
  1069. * @type {number}
  1070. */
  1071. countdown.MINUTES = MINUTES;
  1072.  
  1073. /**
  1074. * @public
  1075. * @const
  1076. * @type {number}
  1077. */
  1078. countdown.HOURS = HOURS;
  1079.  
  1080. /**
  1081. * @public
  1082. * @const
  1083. * @type {number}
  1084. */
  1085. countdown.DAYS = DAYS;
  1086.  
  1087. /**
  1088. * @public
  1089. * @const
  1090. * @type {number}
  1091. */
  1092. countdown.WEEKS = WEEKS;
  1093.  
  1094. /**
  1095. * @public
  1096. * @const
  1097. * @type {number}
  1098. */
  1099. countdown.MONTHS = MONTHS;
  1100.  
  1101. /**
  1102. * @public
  1103. * @const
  1104. * @type {number}
  1105. */
  1106. countdown.YEARS = YEARS;
  1107.  
  1108. /**
  1109. * @public
  1110. * @const
  1111. * @type {number}
  1112. */
  1113. countdown.DECADES = DECADES;
  1114.  
  1115. /**
  1116. * @public
  1117. * @const
  1118. * @type {number}
  1119. */
  1120. countdown.CENTURIES = CENTURIES;
  1121.  
  1122. /**
  1123. * @public
  1124. * @const
  1125. * @type {number}
  1126. */
  1127. countdown.MILLENNIA = MILLENNIA;
  1128.  
  1129. /**
  1130. * @public
  1131. * @const
  1132. * @type {number}
  1133. */
  1134. countdown.DEFAULTS = DEFAULTS;
  1135.  
  1136. /**
  1137. * @public
  1138. * @const
  1139. * @type {number}
  1140. */
  1141. countdown.ALL = MILLENNIA | CENTURIES | DECADES | YEARS | MONTHS | WEEKS | DAYS | HOURS | MINUTES | SECONDS | MILLISECONDS;
  1142.  
  1143. /**
  1144. * Override the unit labels
  1145. * @public
  1146. * @param {string|Array} singular a pipe ('|') delimited list of singular unit name overrides
  1147. * @param {string|Array} plural a pipe ('|') delimited list of plural unit name overrides
  1148. * @param {string} last a prefix for the last unit if more than one (default: 'and ')
  1149. * @param {string} delim a delimiter to use between units (default: ', ')
  1150. */
  1151. var setLabels = countdown.setLabels = function (singular, plural, last, delim) {
  1152. singular = singular || [];
  1153. if (singular.split) {
  1154. singular = singular.split('|');
  1155. }
  1156. plural = plural || [];
  1157. if (plural.split) {
  1158. plural = plural.split('|');
  1159. }
  1160.  
  1161. for (var i = LABEL_MILLISECONDS; i <= LABEL_MILLENNIA; i++) {
  1162. // override any specified units
  1163. LABELS_SINGLUAR[i] = singular[i] || LABELS_SINGLUAR[i];
  1164. LABELS_PLURAL[i] = plural[i] || LABELS_PLURAL[i];
  1165. }
  1166.  
  1167. LABEL_LAST = ('string' === typeof last) ? last : LABEL_LAST;
  1168. LABEL_DELIM = ('string' === typeof delim) ? delim : LABEL_DELIM;
  1169. };
  1170.  
  1171. /**
  1172. * Revert to the default unit labels
  1173. * @public
  1174. */
  1175. var resetLabels = countdown.resetLabels = function () {
  1176. LABELS_SINGLUAR = 'millisecond|second|minute|hour|day|week|month|year|decade|century|millennium'.split('|');
  1177. LABELS_PLURAL = 'milliseconds|seconds|minutes|hours|days|weeks|months|years|decades|centuries|millennia'.split('|');
  1178. LABEL_LAST = 'and ';
  1179. LABEL_DELIM = ', ';
  1180. };
  1181.  
  1182. resetLabels();
  1183.  
  1184. if (module && module.exports) {
  1185. module.exports = countdown;
  1186.  
  1187. } else if (typeof window.define === 'function' && typeof window.define.amd !== 'undefined') {
  1188. window.define('countdown', [], function () {
  1189. return countdown;
  1190. });
  1191. }
  1192.  
  1193. return countdown;
  1194.  
  1195. })(module);