datemath.ts 4.2 KB

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