datemath.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. // @ts-ignore
  2. import _ from 'lodash';
  3. import moment from 'moment';
  4. const units = ['y', 'M', 'w', 'd', 'h', 'm', 's'];
  5. export type Timezone = 'utc';
  6. /**
  7. * Parses different types input to a moment instance. There is a specific formatting language that can be used
  8. * if text arg is string. See unit tests for examples.
  9. * @param text
  10. * @param roundUp See parseDateMath function.
  11. * @param timezone Only string 'utc' is acceptable here, for anything else, local timezone is used.
  12. */
  13. export function parse(
  14. text: string | moment.Moment | Date,
  15. roundUp?: boolean,
  16. timezone?: Timezone
  17. ): moment.Moment | undefined {
  18. if (!text) {
  19. return undefined;
  20. }
  21. if (typeof text !== 'string') {
  22. if (moment.isMoment(text)) {
  23. return text;
  24. }
  25. if (_.isDate(text)) {
  26. return moment(text);
  27. }
  28. // We got some non string which is not a moment nor Date. TS should be able to check for that but not always.
  29. return undefined;
  30. } else {
  31. let time;
  32. let mathString = '';
  33. let index;
  34. let parseString;
  35. if (text.substring(0, 3) === 'now') {
  36. if (timezone === 'utc') {
  37. time = moment.utc();
  38. } else {
  39. time = moment();
  40. }
  41. mathString = text.substring('now'.length);
  42. } else {
  43. index = text.indexOf('||');
  44. if (index === -1) {
  45. parseString = text;
  46. mathString = ''; // nothing else
  47. } else {
  48. parseString = text.substring(0, index);
  49. mathString = text.substring(index + 2);
  50. }
  51. // We're going to just require ISO8601 timestamps, k?
  52. time = moment(parseString, moment.ISO_8601);
  53. }
  54. if (!mathString.length) {
  55. return time;
  56. }
  57. return parseDateMath(mathString, time, roundUp);
  58. }
  59. }
  60. /**
  61. * Checks if text is a valid date which in this context means that it is either a Moment instance or it can be parsed
  62. * by parse function. See parse function to see what is considered acceptable.
  63. * @param text
  64. */
  65. export function isValid(text: string | moment.Moment): boolean {
  66. const date = parse(text);
  67. if (!date) {
  68. return false;
  69. }
  70. if (moment.isMoment(date)) {
  71. return date.isValid();
  72. }
  73. return false;
  74. }
  75. /**
  76. * Parses math part of the time string and shifts supplied time according to that math. See unit tests for examples.
  77. * @param mathString
  78. * @param time
  79. * @param roundUp If true it will round the time to endOf time unit, otherwise to startOf time unit.
  80. */
  81. // TODO: Had to revert Andrejs `time: moment.Moment` to `time: any`
  82. export function parseDateMath(mathString: string, time: any, roundUp?: boolean): moment.Moment | undefined {
  83. const dateTime = time;
  84. let i = 0;
  85. const len = mathString.length;
  86. while (i < len) {
  87. const c = mathString.charAt(i++);
  88. let type;
  89. let num;
  90. let unit;
  91. if (c === '/') {
  92. type = 0;
  93. } else if (c === '+') {
  94. type = 1;
  95. } else if (c === '-') {
  96. type = 2;
  97. } else {
  98. return undefined;
  99. }
  100. if (isNaN(parseInt(mathString.charAt(i), 10))) {
  101. num = 1;
  102. } else if (mathString.length === 2) {
  103. num = mathString.charAt(i);
  104. } else {
  105. const numFrom = i;
  106. while (!isNaN(parseInt(mathString.charAt(i), 10))) {
  107. i++;
  108. if (i > 10) {
  109. return undefined;
  110. }
  111. }
  112. num = parseInt(mathString.substring(numFrom, i), 10);
  113. }
  114. if (type === 0) {
  115. // rounding is only allowed on whole, single, units (eg M or 1M, not 0.5M or 2M)
  116. if (num !== 1) {
  117. return undefined;
  118. }
  119. }
  120. unit = mathString.charAt(i++);
  121. if (!_.includes(units, unit)) {
  122. return undefined;
  123. } else {
  124. if (type === 0) {
  125. if (roundUp) {
  126. dateTime.endOf(unit);
  127. } else {
  128. dateTime.startOf(unit);
  129. }
  130. } else if (type === 1) {
  131. dateTime.add(num, unit);
  132. } else if (type === 2) {
  133. dateTime.subtract(num, unit);
  134. }
  135. }
  136. }
  137. return dateTime;
  138. }