datemath.ts 3.8 KB

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