time-span.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829
  1. /*
  2. * JavaScript TimeSpan Library
  3. *
  4. * Copyright (c) 2010 Michael Stum, Charlie Robbins
  5. *
  6. * Permission is hereby granted, free of charge, to any person obtaining
  7. * a copy of this software and associated documentation files (the
  8. * "Software"), to deal in the Software without restriction, including
  9. * without limitation the rights to use, copy, modify, merge, publish,
  10. * distribute, sublicense, and/or sell copies of the Software, and to
  11. * permit persons to whom the Software is furnished to do so, subject to
  12. * the following conditions:
  13. *
  14. * The above copyright notice and this permission notice shall be
  15. * included in all copies or substantial portions of the Software.
  16. *
  17. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  18. * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  19. * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  20. * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  21. * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  22. * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  23. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  24. */
  25. //
  26. // ### Time constants
  27. //
  28. var msecPerSecond = 1000,
  29. msecPerMinute = 60000,
  30. msecPerHour = 3600000,
  31. msecPerDay = 86400000;
  32. //
  33. // ### Timespan Parsers
  34. //
  35. var timeSpanWithDays = /^(\d+):(\d+):(\d+):(\d+)(\.\d+)?/,
  36. timeSpanNoDays = /^(\d+):(\d+):(\d+)(\.\d+)?/;
  37. //
  38. // ### function TimeSpan (milliseconds, seconds, minutes, hours, days)
  39. // #### @milliseconds {Number} Number of milliseconds for this instance.
  40. // #### @seconds {Number} Number of seconds for this instance.
  41. // #### @minutes {Number} Number of minutes for this instance.
  42. // #### @hours {Number} Number of hours for this instance.
  43. // #### @days {Number} Number of days for this instance.
  44. // Constructor function for the `TimeSpan` object which represents a length
  45. // of positive or negative milliseconds componentized into milliseconds,
  46. // seconds, hours, and days.
  47. //
  48. var TimeSpan = exports.TimeSpan = function (milliseconds, seconds, minutes, hours, days) {
  49. this.msecs = 0;
  50. if (isNumeric(days)) {
  51. this.msecs += (days * msecPerDay);
  52. }
  53. if (isNumeric(hours)) {
  54. this.msecs += (hours * msecPerHour);
  55. }
  56. if (isNumeric(minutes)) {
  57. this.msecs += (minutes * msecPerMinute);
  58. }
  59. if (isNumeric(seconds)) {
  60. this.msecs += (seconds * msecPerSecond);
  61. }
  62. if (isNumeric(milliseconds)) {
  63. this.msecs += milliseconds;
  64. }
  65. };
  66. //
  67. // ## Factory methods
  68. // Helper methods for creating new TimeSpan objects
  69. // from various criteria: milliseconds, seconds, minutes,
  70. // hours, days, strings and other `TimeSpan` instances.
  71. //
  72. //
  73. // ### function fromMilliseconds (milliseconds)
  74. // #### @milliseconds {Number} Amount of milliseconds for the new TimeSpan instance.
  75. // Creates a new `TimeSpan` instance with the specified `milliseconds`.
  76. //
  77. exports.fromMilliseconds = function (milliseconds) {
  78. if (!isNumeric(milliseconds)) { return }
  79. return new TimeSpan(milliseconds, 0, 0, 0, 0);
  80. }
  81. //
  82. // ### function fromSeconds (seconds)
  83. // #### @milliseconds {Number} Amount of seconds for the new TimeSpan instance.
  84. // Creates a new `TimeSpan` instance with the specified `seconds`.
  85. //
  86. exports.fromSeconds = function (seconds) {
  87. if (!isNumeric(seconds)) { return }
  88. return new TimeSpan(0, seconds, 0, 0, 0);
  89. };
  90. //
  91. // ### function fromMinutes (milliseconds)
  92. // #### @milliseconds {Number} Amount of minutes for the new TimeSpan instance.
  93. // Creates a new `TimeSpan` instance with the specified `minutes`.
  94. //
  95. exports.fromMinutes = function (minutes) {
  96. if (!isNumeric(minutes)) { return }
  97. return new TimeSpan(0, 0, minutes, 0, 0);
  98. };
  99. //
  100. // ### function fromHours (hours)
  101. // #### @milliseconds {Number} Amount of hours for the new TimeSpan instance.
  102. // Creates a new `TimeSpan` instance with the specified `hours`.
  103. //
  104. exports.fromHours = function (hours) {
  105. if (!isNumeric(hours)) { return }
  106. return new TimeSpan(0, 0, 0, hours, 0);
  107. };
  108. //
  109. // ### function fromDays (days)
  110. // #### @milliseconds {Number} Amount of days for the new TimeSpan instance.
  111. // Creates a new `TimeSpan` instance with the specified `days`.
  112. //
  113. exports.fromDays = function (days) {
  114. if (!isNumeric(days)) { return }
  115. return new TimeSpan(0, 0, 0, 0, days);
  116. };
  117. //
  118. // ### function parse (str)
  119. // #### @str {string} Timespan string to parse.
  120. // Creates a new `TimeSpan` instance from the specified
  121. // string, `str`.
  122. //
  123. exports.parse = function (str) {
  124. var match, milliseconds;
  125. function parseMilliseconds (value) {
  126. return value ? parseFloat('0' + value) * 1000 : 0;
  127. }
  128. // If we match against a full TimeSpan:
  129. // [days]:[hours]:[minutes]:[seconds].[milliseconds]?
  130. if ((match = str.match(timeSpanWithDays))) {
  131. return new TimeSpan(parseMilliseconds(match[5]), match[4], match[3], match[2], match[1]);
  132. }
  133. // If we match against a partial TimeSpan:
  134. // [hours]:[minutes]:[seconds].[milliseconds]?
  135. if ((match = str.match(timeSpanNoDays))) {
  136. return new TimeSpan(parseMilliseconds(match[4]), match[3], match[2], match[1], 0);
  137. }
  138. return null;
  139. };
  140. //
  141. // List of default singular time modifiers and associated
  142. // computation algoritm. Assumes in order, smallest to greatest
  143. // performing carry forward additiona / subtraction for each
  144. // Date-Time component.
  145. //
  146. var parsers = {
  147. 'milliseconds': {
  148. exp: /(\d+)milli(?:second)?[s]?/i,
  149. compute: function (delta, computed) {
  150. return _compute(delta, computed, {
  151. current: 'milliseconds',
  152. next: 'seconds',
  153. max: 1000
  154. });
  155. }
  156. },
  157. 'seconds': {
  158. exp: /(\d+)second[s]?/i,
  159. compute: function (delta, computed) {
  160. return _compute(delta, computed, {
  161. current: 'seconds',
  162. next: 'minutes',
  163. max: 60
  164. });
  165. }
  166. },
  167. 'minutes': {
  168. exp: /(\d+)minute[s]?/i,
  169. compute: function (delta, computed) {
  170. return _compute(delta, computed, {
  171. current: 'minutes',
  172. next: 'hours',
  173. max: 60
  174. });
  175. }
  176. },
  177. 'hours': {
  178. exp: /(\d+)hour[s]?/i,
  179. compute: function (delta, computed) {
  180. return _compute(delta, computed, {
  181. current: 'hours',
  182. next: 'days',
  183. max: 24
  184. });
  185. }
  186. },
  187. 'days': {
  188. exp: /(\d+)day[s]?/i,
  189. compute: function (delta, computed) {
  190. var days = monthDays(computed.months, computed.years),
  191. sign = delta >= 0 ? 1 : -1,
  192. opsign = delta >= 0 ? -1 : 1,
  193. clean = 0;
  194. function update (months) {
  195. if (months < 0) {
  196. computed.years -= 1;
  197. return 11;
  198. }
  199. else if (months > 11) {
  200. computed.years += 1;
  201. return 0
  202. }
  203. return months;
  204. }
  205. if (delta) {
  206. while (Math.abs(delta) >= days) {
  207. computed.months += sign * 1;
  208. computed.months = update(computed.months);
  209. delta += opsign * days;
  210. days = monthDays(computed.months, computed.years);
  211. }
  212. computed.days += (opsign * delta);
  213. }
  214. if (computed.days < 0) { clean = -1 }
  215. else if (computed.days > months[computed.months]) { clean = 1 }
  216. if (clean === -1 || clean === 1) {
  217. computed.months += clean;
  218. computed.months = update(computed.months);
  219. computed.days = months[computed.months] + computed.days;
  220. }
  221. return computed;
  222. }
  223. },
  224. 'months': {
  225. exp: /(\d+)month[s]?/i,
  226. compute: function (delta, computed) {
  227. var round = delta > 0 ? Math.floor : Math.ceil;
  228. if (delta) {
  229. computed.years += round.call(null, delta / 12);
  230. computed.months += delta % 12;
  231. }
  232. if (computed.months > 11) {
  233. computed.years += Math.floor((computed.months + 1) / 12);
  234. computed.months = ((computed.months + 1) % 12) - 1;
  235. }
  236. return computed;
  237. }
  238. },
  239. 'years': {
  240. exp: /(\d+)year[s]?/i,
  241. compute: function (delta, computed) {
  242. if (delta) { computed.years += delta; }
  243. return computed;
  244. }
  245. }
  246. };
  247. //
  248. // Compute the list of parser names for
  249. // later use.
  250. //
  251. var parserNames = Object.keys(parsers);
  252. //
  253. // ### function parseDate (str)
  254. // #### @str {string} String to parse into a date
  255. // Parses the specified liberal Date-Time string according to
  256. // ISO8601 **and**:
  257. //
  258. // 1. `2010-04-03T12:34:15Z+12MINUTES`
  259. // 2. `NOW-4HOURS`
  260. //
  261. // Valid modifiers for the more liberal Date-Time string(s):
  262. //
  263. // YEAR, YEARS
  264. // MONTH, MONTHS
  265. // DAY, DAYS
  266. // HOUR, HOURS
  267. // MINUTE, MINUTES
  268. // SECOND, SECONDS
  269. // MILLI, MILLIS, MILLISECOND, MILLISECONDS
  270. //
  271. exports.parseDate = function (str) {
  272. var dateTime = Date.parse(str),
  273. iso = '^([^Z]+)',
  274. zulu = 'Z([\\+|\\-])?',
  275. diff = {},
  276. computed,
  277. modifiers,
  278. sign;
  279. //
  280. // If Date string supplied actually conforms
  281. // to UTC Time (ISO8601), return a new Date.
  282. //
  283. if (!isNaN(dateTime)) {
  284. return new Date(dateTime);
  285. }
  286. //
  287. // Create the `RegExp` for the end component
  288. // of the target `str` to parse.
  289. //
  290. parserNames.forEach(function (group) {
  291. zulu += '(\\d+[a-zA-Z]+)?';
  292. });
  293. if (/^NOW/i.test(str)) {
  294. //
  295. // If the target `str` is a liberal `NOW-*`,
  296. // then set the base `dateTime` appropriately.
  297. //
  298. dateTime = Date.now();
  299. zulu = zulu.replace(/Z/, 'NOW');
  300. }
  301. else if (/^\-/.test(str) || /^\+/.test(str)) {
  302. dateTime = Date.now();
  303. zulu = zulu.replace(/Z/, '');
  304. }
  305. else {
  306. //
  307. // Parse the `ISO8601` component, and the end
  308. // component from the target `str`.
  309. //
  310. dateTime = str.match(new RegExp(iso, 'i'));
  311. dateTime = Date.parse(dateTime[1]);
  312. }
  313. //
  314. // If there was no match on either part then
  315. // it must be a bad value.
  316. //
  317. if (!dateTime || !(modifiers = str.match(new RegExp(zulu, 'i')))) {
  318. return null;
  319. }
  320. //
  321. // Create a new `Date` object from the `ISO8601`
  322. // component of the target `str`.
  323. //
  324. dateTime = new Date(dateTime);
  325. sign = modifiers[1] === '+' ? 1 : -1;
  326. //
  327. // Create an Object-literal for consistently accessing
  328. // the various components of the computed Date.
  329. //
  330. var computed = {
  331. milliseconds: dateTime.getMilliseconds(),
  332. seconds: dateTime.getSeconds(),
  333. minutes: dateTime.getMinutes(),
  334. hours: dateTime.getHours(),
  335. days: dateTime.getDate(),
  336. months: dateTime.getMonth(),
  337. years: dateTime.getFullYear()
  338. };
  339. //
  340. // Parse the individual component spans (months, years, etc)
  341. // from the modifier strings that we parsed from the end
  342. // of the target `str`.
  343. //
  344. modifiers.slice(2).filter(Boolean).forEach(function (modifier) {
  345. parserNames.forEach(function (name) {
  346. var match;
  347. if (!(match = modifier.match(parsers[name].exp))) {
  348. return;
  349. }
  350. diff[name] = sign * parseInt(match[1], 10);
  351. })
  352. });
  353. //
  354. // Compute the total `diff` by iteratively computing
  355. // the partial components from smallest to largest.
  356. //
  357. parserNames.forEach(function (name) {
  358. computed = parsers[name].compute(diff[name], computed);
  359. });
  360. return new Date(
  361. computed.years,
  362. computed.months,
  363. computed.days,
  364. computed.hours,
  365. computed.minutes,
  366. computed.seconds,
  367. computed.milliseconds
  368. );
  369. };
  370. //
  371. // ### function fromDates (start, end, abs)
  372. // #### @start {Date} Start date of the `TimeSpan` instance to return
  373. // #### @end {Date} End date of the `TimeSpan` instance to return
  374. // #### @abs {boolean} Value indicating to return an absolute value
  375. // Returns a new `TimeSpan` instance representing the difference between
  376. // the `start` and `end` Dates.
  377. //
  378. exports.fromDates = function (start, end, abs) {
  379. if (typeof start === 'string') {
  380. start = exports.parseDate(start);
  381. }
  382. if (typeof end === 'string') {
  383. end = exports.parseDate(end);
  384. }
  385. if (!(start instanceof Date && end instanceof Date)) {
  386. return null;
  387. }
  388. var differenceMsecs = end.valueOf() - start.valueOf();
  389. if (abs) {
  390. differenceMsecs = Math.abs(differenceMsecs);
  391. }
  392. return new TimeSpan(differenceMsecs, 0, 0, 0, 0);
  393. };
  394. //
  395. // ## Module Helpers
  396. // Module-level helpers for various utilities such as:
  397. // instanceOf, parsability, and cloning.
  398. //
  399. //
  400. // ### function test (str)
  401. // #### @str {string} String value to test if it is a TimeSpan
  402. // Returns a value indicating if the specified string, `str`,
  403. // is a parsable `TimeSpan` value.
  404. //
  405. exports.test = function (str) {
  406. return timeSpanWithDays.test(str) || timeSpanNoDays.test(str);
  407. };
  408. //
  409. // ### function instanceOf (timeSpan)
  410. // #### @timeSpan {Object} Object to check TimeSpan quality.
  411. // Returns a value indicating if the specified `timeSpan` is
  412. // in fact a `TimeSpan` instance.
  413. //
  414. exports.instanceOf = function (timeSpan) {
  415. return timeSpan instanceof TimeSpan;
  416. };
  417. //
  418. // ### function clone (timeSpan)
  419. // #### @timeSpan {TimeSpan} TimeSpan object to clone.
  420. // Returns a new `TimeSpan` instance with the same value
  421. // as the `timeSpan` object supplied.
  422. //
  423. exports.clone = function (timeSpan) {
  424. if (!(timeSpan instanceof TimeSpan)) { return }
  425. return exports.fromMilliseconds(timeSpan.totalMilliseconds());
  426. };
  427. //
  428. // ## Addition
  429. // Methods for adding `TimeSpan` instances,
  430. // milliseconds, seconds, hours, and days to other
  431. // `TimeSpan` instances.
  432. //
  433. //
  434. // ### function add (timeSpan)
  435. // #### @timeSpan {TimeSpan} TimeSpan to add to this instance
  436. // Adds the specified `timeSpan` to this instance.
  437. //
  438. TimeSpan.prototype.add = function (timeSpan) {
  439. if (!(timeSpan instanceof TimeSpan)) { return }
  440. this.msecs += timeSpan.totalMilliseconds();
  441. };
  442. //
  443. // ### function addMilliseconds (milliseconds)
  444. // #### @milliseconds {Number} Number of milliseconds to add.
  445. // Adds the specified `milliseconds` to this instance.
  446. //
  447. TimeSpan.prototype.addMilliseconds = function (milliseconds) {
  448. if (!isNumeric(milliseconds)) { return }
  449. this.msecs += milliseconds;
  450. };
  451. //
  452. // ### function addSeconds (seconds)
  453. // #### @seconds {Number} Number of seconds to add.
  454. // Adds the specified `seconds` to this instance.
  455. //
  456. TimeSpan.prototype.addSeconds = function (seconds) {
  457. if (!isNumeric(seconds)) { return }
  458. this.msecs += (seconds * msecPerSecond);
  459. };
  460. //
  461. // ### function addMinutes (minutes)
  462. // #### @minutes {Number} Number of minutes to add.
  463. // Adds the specified `minutes` to this instance.
  464. //
  465. TimeSpan.prototype.addMinutes = function (minutes) {
  466. if (!isNumeric(minutes)) { return }
  467. this.msecs += (minutes * msecPerMinute);
  468. };
  469. //
  470. // ### function addHours (hours)
  471. // #### @hours {Number} Number of hours to add.
  472. // Adds the specified `hours` to this instance.
  473. //
  474. TimeSpan.prototype.addHours = function (hours) {
  475. if (!isNumeric(hours)) { return }
  476. this.msecs += (hours * msecPerHour);
  477. };
  478. //
  479. // ### function addDays (days)
  480. // #### @days {Number} Number of days to add.
  481. // Adds the specified `days` to this instance.
  482. //
  483. TimeSpan.prototype.addDays = function (days) {
  484. if (!isNumeric(days)) { return }
  485. this.msecs += (days * msecPerDay);
  486. };
  487. //
  488. // ## Subtraction
  489. // Methods for subtracting `TimeSpan` instances,
  490. // milliseconds, seconds, hours, and days from other
  491. // `TimeSpan` instances.
  492. //
  493. //
  494. // ### function subtract (timeSpan)
  495. // #### @timeSpan {TimeSpan} TimeSpan to subtract from this instance.
  496. // Subtracts the specified `timeSpan` from this instance.
  497. //
  498. TimeSpan.prototype.subtract = function (timeSpan) {
  499. if (!(timeSpan instanceof TimeSpan)) { return }
  500. this.msecs -= timeSpan.totalMilliseconds();
  501. };
  502. //
  503. // ### function subtractMilliseconds (milliseconds)
  504. // #### @milliseconds {Number} Number of milliseconds to subtract.
  505. // Subtracts the specified `milliseconds` from this instance.
  506. //
  507. TimeSpan.prototype.subtractMilliseconds = function (milliseconds) {
  508. if (!isNumeric(milliseconds)) { return }
  509. this.msecs -= milliseconds;
  510. };
  511. //
  512. // ### function subtractSeconds (seconds)
  513. // #### @seconds {Number} Number of seconds to subtract.
  514. // Subtracts the specified `seconds` from this instance.
  515. //
  516. TimeSpan.prototype.subtractSeconds = function (seconds) {
  517. if (!isNumeric(seconds)) { return }
  518. this.msecs -= (seconds * msecPerSecond);
  519. };
  520. //
  521. // ### function subtractMinutes (minutes)
  522. // #### @minutes {Number} Number of minutes to subtract.
  523. // Subtracts the specified `minutes` from this instance.
  524. //
  525. TimeSpan.prototype.subtractMinutes = function (minutes) {
  526. if (!isNumeric(minutes)) { return }
  527. this.msecs -= (minutes * msecPerMinute);
  528. };
  529. //
  530. // ### function subtractHours (hours)
  531. // #### @hours {Number} Number of hours to subtract.
  532. // Subtracts the specified `hours` from this instance.
  533. //
  534. TimeSpan.prototype.subtractHours = function (hours) {
  535. if (!isNumeric(hours)) { return }
  536. this.msecs -= (hours * msecPerHour);
  537. };
  538. //
  539. // ### function subtractDays (days)
  540. // #### @days {Number} Number of days to subtract.
  541. // Subtracts the specified `days` from this instance.
  542. //
  543. TimeSpan.prototype.subtractDays = function (days) {
  544. if (!isNumeric(days)) { return }
  545. this.msecs -= (days * msecPerDay);
  546. };
  547. //
  548. // ## Getters
  549. // Methods for retrieving components of a `TimeSpan`
  550. // instance: milliseconds, seconds, minutes, hours, and days.
  551. //
  552. //
  553. // ### function totalMilliseconds (roundDown)
  554. // #### @roundDown {boolean} Value indicating if the value should be rounded down.
  555. // Returns the total number of milliseconds for this instance, rounding down
  556. // to the nearest integer if `roundDown` is set.
  557. //
  558. TimeSpan.prototype.totalMilliseconds = function (roundDown) {
  559. var result = this.msecs;
  560. if (roundDown === true) {
  561. result = Math.floor(result);
  562. }
  563. return result;
  564. };
  565. //
  566. // ### function totalSeconds (roundDown)
  567. // #### @roundDown {boolean} Value indicating if the value should be rounded down.
  568. // Returns the total number of seconds for this instance, rounding down
  569. // to the nearest integer if `roundDown` is set.
  570. //
  571. TimeSpan.prototype.totalSeconds = function (roundDown) {
  572. var result = this.msecs / msecPerSecond;
  573. if (roundDown === true) {
  574. result = Math.floor(result);
  575. }
  576. return result;
  577. };
  578. //
  579. // ### function totalMinutes (roundDown)
  580. // #### @roundDown {boolean} Value indicating if the value should be rounded down.
  581. // Returns the total number of minutes for this instance, rounding down
  582. // to the nearest integer if `roundDown` is set.
  583. //
  584. TimeSpan.prototype.totalMinutes = function (roundDown) {
  585. var result = this.msecs / msecPerMinute;
  586. if (roundDown === true) {
  587. result = Math.floor(result);
  588. }
  589. return result;
  590. };
  591. //
  592. // ### function totalHours (roundDown)
  593. // #### @roundDown {boolean} Value indicating if the value should be rounded down.
  594. // Returns the total number of hours for this instance, rounding down
  595. // to the nearest integer if `roundDown` is set.
  596. //
  597. TimeSpan.prototype.totalHours = function (roundDown) {
  598. var result = this.msecs / msecPerHour;
  599. if (roundDown === true) {
  600. result = Math.floor(result);
  601. }
  602. return result;
  603. };
  604. //
  605. // ### function totalDays (roundDown)
  606. // #### @roundDown {boolean} Value indicating if the value should be rounded down.
  607. // Returns the total number of days for this instance, rounding down
  608. // to the nearest integer if `roundDown` is set.
  609. //
  610. TimeSpan.prototype.totalDays = function (roundDown) {
  611. var result = this.msecs / msecPerDay;
  612. if (roundDown === true) {
  613. result = Math.floor(result);
  614. }
  615. return result;
  616. };
  617. //
  618. // ### @milliseconds
  619. // Returns the length of this `TimeSpan` instance in milliseconds.
  620. //
  621. TimeSpan.prototype.__defineGetter__('milliseconds', function () {
  622. return this.msecs % 1000;
  623. });
  624. //
  625. // ### @seconds
  626. // Returns the length of this `TimeSpan` instance in seconds.
  627. //
  628. TimeSpan.prototype.__defineGetter__('seconds', function () {
  629. return Math.floor(this.msecs / msecPerSecond) % 60;
  630. });
  631. //
  632. // ### @minutes
  633. // Returns the length of this `TimeSpan` instance in minutes.
  634. //
  635. TimeSpan.prototype.__defineGetter__('minutes', function () {
  636. return Math.floor(this.msecs / msecPerMinute) % 60;
  637. });
  638. //
  639. // ### @hours
  640. // Returns the length of this `TimeSpan` instance in hours.
  641. //
  642. TimeSpan.prototype.__defineGetter__('hours', function () {
  643. return Math.floor(this.msecs / msecPerHour) % 24;
  644. });
  645. //
  646. // ### @days
  647. // Returns the length of this `TimeSpan` instance in days.
  648. //
  649. TimeSpan.prototype.__defineGetter__('days', function () {
  650. return Math.floor(this.msecs / msecPerDay);
  651. });
  652. //
  653. // ## Instance Helpers
  654. // Various help methods for performing utilities
  655. // such as equality and serialization
  656. //
  657. //
  658. // ### function equals (timeSpan)
  659. // #### @timeSpan {TimeSpan} TimeSpan instance to assert equal
  660. // Returns a value indicating if the specified `timeSpan` is equal
  661. // in milliseconds to this instance.
  662. //
  663. TimeSpan.prototype.equals = function (timeSpan) {
  664. if (!(timeSpan instanceof TimeSpan)) { return }
  665. return this.msecs === timeSpan.totalMilliseconds();
  666. };
  667. //
  668. // ### function toString ()
  669. // Returns a string representation of this `TimeSpan`
  670. // instance according to current `format`.
  671. //
  672. TimeSpan.prototype.toString = function () {
  673. if (!this.format) { return this._format() }
  674. return this.format(this);
  675. };
  676. //
  677. // ### @private function _format ()
  678. // Returns the default string representation of this instance.
  679. //
  680. TimeSpan.prototype._format = function () {
  681. return [
  682. this.days,
  683. this.hours,
  684. this.minutes,
  685. this.seconds + '.' + this.milliseconds
  686. ].join(':')
  687. };
  688. //
  689. // ### @private function isNumeric (input)
  690. // #### @input {Number} Value to check numeric quality of.
  691. // Returns a value indicating the numeric quality of the
  692. // specified `input`.
  693. //
  694. function isNumeric (input) {
  695. return input && !isNaN(parseFloat(input)) && isFinite(input);
  696. };
  697. //
  698. // ### @private function _compute (delta, date, computed, options)
  699. // #### @delta {Number} Channge in this component of the date
  700. // #### @computed {Object} Currently computed date.
  701. // #### @options {Object} Options for the computation
  702. // Performs carry forward addition or subtraction for the
  703. // `options.current` component of the `computed` date, carrying
  704. // it forward to `options.next` depending on the maximum value,
  705. // `options.max`.
  706. //
  707. function _compute (delta, computed, options) {
  708. var current = options.current,
  709. next = options.next,
  710. max = options.max,
  711. round = delta > 0 ? Math.floor : Math.ceil;
  712. if (delta) {
  713. computed[next] += round.call(null, delta / max);
  714. computed[current] += delta % max;
  715. }
  716. if (Math.abs(computed[current]) >= max) {
  717. computed[next] += round.call(null, computed[current] / max)
  718. computed[current] = computed[current] % max;
  719. }
  720. return computed;
  721. }
  722. //
  723. // ### @private monthDays (month, year)
  724. // #### @month {Number} Month to get days for.
  725. // #### @year {Number} Year of the month to get days for.
  726. // Returns the number of days in the specified `month` observing
  727. // leap years.
  728. //
  729. var months = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  730. function monthDays (month, year) {
  731. if (((year % 100 !== 0 && year % 4 === 0)
  732. || year % 400 === 0) && month === 1) {
  733. return 29;
  734. }
  735. return months[month];
  736. }