143 lines
4.6 KiB
TypeScript
143 lines
4.6 KiB
TypeScript
import moment from 'moment';
|
|
|
|
interface Success {
|
|
success: true;
|
|
value: moment.Moment;
|
|
}
|
|
|
|
interface Failure {
|
|
success: false;
|
|
error: string;
|
|
}
|
|
|
|
enum Type {
|
|
Operation = 'operation',
|
|
Unit = 'unit',
|
|
Value = 'value',
|
|
}
|
|
|
|
enum Operation {
|
|
Divide = '/',
|
|
Add = '+',
|
|
Substract = '-',
|
|
}
|
|
|
|
enum Unit {
|
|
Year = 'y',
|
|
Month = 'M',
|
|
Week = 'w',
|
|
Day = 'd',
|
|
Hour = 'h',
|
|
Minute = 'm',
|
|
Second = 's',
|
|
}
|
|
|
|
export const parseRelativeTime = (value: string, divide: 'endOf' | 'startOf', nowDate = moment()): Success | Failure => {
|
|
if (isValidDate(value)) {
|
|
return success(asDate(value));
|
|
}
|
|
|
|
if (value.substr(0, 3) === 'now') {
|
|
let time = nowDate;
|
|
let currentIndex = 'now'.length;
|
|
|
|
let expectNext = Type.Operation;
|
|
let lastOperation = Operation.Add;
|
|
let lastNumber = -1;
|
|
while (currentIndex < value.length) {
|
|
const currentChar = value.charAt(currentIndex);
|
|
switch (expectNext) {
|
|
case Type.Operation:
|
|
if (!isOperation(currentChar)) {
|
|
return failure('Expected one of / + - at index ' + currentIndex + ' but was ' + currentChar);
|
|
}
|
|
lastOperation = currentChar;
|
|
expectNext = currentChar === Operation.Divide ? Type.Unit : Type.Value;
|
|
currentIndex++;
|
|
break;
|
|
case Type.Value:
|
|
if (isNaN(parseInt(currentChar, 10))) {
|
|
return failure('Expected number at index ' + currentIndex + ' but was ' + currentChar);
|
|
}
|
|
let valueIndex = currentIndex;
|
|
while (!isNaN(parseInt(value.charAt(valueIndex + 1), 10)) && valueIndex + 1 < value.length) {
|
|
valueIndex++;
|
|
}
|
|
lastNumber = parseInt(value.substr(currentIndex, valueIndex), 10);
|
|
|
|
expectNext = Type.Unit;
|
|
currentIndex = valueIndex + 1;
|
|
break;
|
|
case Type.Unit:
|
|
if (!isUnit(currentChar)) {
|
|
return failure(
|
|
'Expected unit (' + Object.values(Unit) + ') at index ' + currentIndex + ' but was ' + currentChar
|
|
);
|
|
}
|
|
|
|
// tslint:disable-next-line:no-nested-switch
|
|
switch (lastOperation) {
|
|
case Operation.Divide:
|
|
time = time[divide](currentChar);
|
|
expectNext = Type.Operation;
|
|
break;
|
|
case Operation.Add:
|
|
time = time.add(lastNumber, currentChar);
|
|
expectNext = Type.Operation;
|
|
break;
|
|
case Operation.Substract:
|
|
time = time.subtract(lastNumber, currentChar);
|
|
expectNext = Type.Operation;
|
|
break;
|
|
default:
|
|
throw new Error('oops');
|
|
}
|
|
|
|
currentIndex++;
|
|
break;
|
|
default:
|
|
throw new Error('oopsie');
|
|
}
|
|
}
|
|
if (expectNext === Type.Unit) {
|
|
return failure('Expected unit at the end but got nothing');
|
|
}
|
|
if (expectNext === Type.Value) {
|
|
return failure('Expected number at the end but got nothing');
|
|
}
|
|
return success(time);
|
|
}
|
|
|
|
if (value.indexOf('now') !== -1) {
|
|
return failure("'now' must be at the start");
|
|
}
|
|
|
|
return failure("Expected valid date or 'now' at index 0");
|
|
};
|
|
|
|
export const success = (value: moment.Moment): Success => {
|
|
return {success: true, value};
|
|
};
|
|
export const failure = (error: string): Failure => {
|
|
return {success: false, error};
|
|
};
|
|
|
|
const isOperation = (char: string): char is Operation => {
|
|
return Object.values(Operation).indexOf(char as Operation) !== -1;
|
|
};
|
|
const isUnit = (char: string): char is Unit => {
|
|
return Object.values(Unit).indexOf(char as Unit) !== -1;
|
|
};
|
|
|
|
export const isValidDate = (value: string, format?: string) => {
|
|
return asDate(value, format).isValid();
|
|
};
|
|
|
|
export const asDate = (value: string, format = 'YYYY-MM-DD HH:mm') => {
|
|
return moment(value, format, true);
|
|
};
|
|
export const isSameDate = (from: moment.Moment, to?: moment.Moment): boolean => {
|
|
const fromString = from.format('YYYYMMDD');
|
|
return to === undefined || fromString === to.format('YYYYMMDD');
|
|
};
|