duration.ts 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. import { isInt } from '../util/misc'
  2. export interface DurationInput {
  3. years?: number
  4. year?: number
  5. months?: number
  6. month?: number
  7. weeks?: number
  8. week?: number
  9. days?: number
  10. day?: number
  11. hours?: number
  12. hour?: number
  13. minutes?: number
  14. minute?: number
  15. seconds?: number
  16. second?: number
  17. milliseconds?: number
  18. millisecond?: number
  19. ms?: number
  20. }
  21. export interface Duration {
  22. years: number
  23. months: number
  24. days: number
  25. milliseconds: number
  26. }
  27. const INTERNAL_UNITS = [ 'years', 'months', 'days', 'milliseconds' ]
  28. const PARSE_RE = /^(-?)(?:(\d+)\.)?(\d+):(\d\d)(?::(\d\d)(?:\.(\d\d\d))?)?/
  29. // Parsing and Creation
  30. export function createDuration(input, unit?: string): Duration | null {
  31. if (typeof input === 'string') {
  32. return parseString(input)
  33. } else if (typeof input === 'object' && input) { // non-null object
  34. return normalizeObject(input)
  35. } else if (typeof input === 'number') {
  36. return normalizeObject({ [unit || 'milliseconds']: input })
  37. } else {
  38. return null
  39. }
  40. }
  41. function parseString(s: string): Duration {
  42. let m = PARSE_RE.exec(s)
  43. if (m) {
  44. let sign = m[1] ? -1 : 1
  45. return {
  46. years: 0,
  47. months: 0,
  48. days: sign * (m[2] ? parseInt(m[2], 10) : 0),
  49. milliseconds: sign * (
  50. (m[3] ? parseInt(m[3], 10) : 0) * 60 * 60 * 1000 + // hours
  51. (m[4] ? parseInt(m[4], 10) : 0) * 60 * 1000 + // minutes
  52. (m[5] ? parseInt(m[5], 10) : 0) * 1000 + // seconds
  53. (m[6] ? parseInt(m[6], 10) : 0) // ms
  54. )
  55. }
  56. }
  57. return null
  58. }
  59. function normalizeObject(obj: DurationInput): Duration {
  60. return {
  61. years: obj.years || obj.year || 0,
  62. months: obj.months || obj.month || 0,
  63. days:
  64. (obj.days || obj.day || 0) +
  65. getWeeksFromInput(obj) * 7,
  66. milliseconds:
  67. (obj.hours || obj.hour || 0) * 60 * 60 * 1000 + // hours
  68. (obj.minutes || obj.minute || 0) * 60 * 1000 + // minutes
  69. (obj.seconds || obj.second || 0) * 1000 + // seconds
  70. (obj.milliseconds || obj.millisecond || obj.ms || 0) // ms
  71. }
  72. }
  73. export function getWeeksFromInput(obj: DurationInput) {
  74. return obj.weeks || obj.week || 0
  75. }
  76. // Equality
  77. export function durationsEqual(d0: Duration, d1: Duration): boolean {
  78. return d0.years === d1.years &&
  79. d0.months === d1.months &&
  80. d0.days === d1.days &&
  81. d0.milliseconds === d1.milliseconds
  82. }
  83. export function isSingleDay(dur: Duration) {
  84. return dur.years === 0 && dur.months === 0 && dur.days === 1 && dur.milliseconds === 0
  85. }
  86. // Simple Math
  87. export function addDurations(d0: Duration, d1: Duration) {
  88. return {
  89. years: d0.years + d1.years,
  90. months: d0.months + d1.months,
  91. days: d0.days + d1.days,
  92. milliseconds: d0.milliseconds + d1.milliseconds
  93. }
  94. }
  95. export function subtractDurations(d1: Duration, d0: Duration): Duration {
  96. return {
  97. years: d1.years - d0.years,
  98. months: d1.months - d0.months,
  99. days: d1.days - d0.days,
  100. milliseconds: d1.milliseconds - d0.milliseconds
  101. }
  102. }
  103. export function multiplyDuration(d: Duration, n: number) {
  104. return {
  105. years: d.years * n,
  106. months: d.months * n,
  107. days: d.days * n,
  108. milliseconds: d.milliseconds * n
  109. }
  110. }
  111. // Conversions
  112. // "Rough" because they are based on average-case Gregorian months/years
  113. export function asRoughYears(dur: Duration) {
  114. return asRoughDays(dur) / 365
  115. }
  116. export function asRoughMonths(dur: Duration) {
  117. return asRoughDays(dur) / 30
  118. }
  119. export function asRoughDays(dur: Duration) {
  120. return asRoughMs(dur) / 864e5
  121. }
  122. export function asRoughHours(dur: Duration) {
  123. return asRoughMs(dur) / (1000 * 60 * 60)
  124. }
  125. export function asRoughMinutes(dur: Duration) {
  126. return asRoughMs(dur) / (1000 * 60)
  127. }
  128. export function asRoughSeconds(dur: Duration) {
  129. return asRoughMs(dur) / 1000
  130. }
  131. export function asRoughMs(dur: Duration) {
  132. return dur.years * (365 * 864e5) +
  133. dur.months * (30 * 864e5) +
  134. dur.days * 864e5 +
  135. dur.milliseconds
  136. }
  137. // Advanced Math
  138. export function wholeDivideDurations(numerator: Duration, denominator: Duration): number {
  139. let res = null
  140. for (let i = 0; i < INTERNAL_UNITS.length; i++) {
  141. let unit = INTERNAL_UNITS[i]
  142. if (denominator[unit]) {
  143. let localRes = numerator[unit] / denominator[unit]
  144. if (!isInt(localRes) || (res !== null && res !== localRes)) {
  145. return null
  146. }
  147. res = localRes
  148. } else if (numerator[unit]) {
  149. // needs to divide by something but can't!
  150. return null
  151. }
  152. }
  153. return res
  154. }
  155. export function greatestDurationDenominator(dur: Duration, dontReturnWeeks?: boolean) {
  156. let ms = dur.milliseconds
  157. if (ms) {
  158. if (ms % 1000 !== 0) {
  159. return { unit: 'millisecond', value: ms }
  160. }
  161. if (ms % (1000 * 60) !== 0) {
  162. return { unit: 'second', value: ms / 1000 }
  163. }
  164. if (ms % (1000 * 60 * 60) !== 0) {
  165. return { unit: 'minute', value: ms / (1000 * 60) }
  166. }
  167. if (ms) {
  168. return { unit: 'hour', value: ms / (1000 * 60 * 60) }
  169. }
  170. }
  171. if (dur.days) {
  172. if (!dontReturnWeeks && dur.days % 7 === 0) {
  173. return { unit: 'week', value: dur.days / 7 }
  174. }
  175. return { unit: 'day', value: dur.days }
  176. }
  177. if (dur.months) {
  178. return { unit: 'month', value: dur.months }
  179. }
  180. if (dur.years) {
  181. return { unit: 'year', value: dur.years }
  182. }
  183. return { unit: 'millisecond', value: 0 }
  184. }