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