pinkmine/libs/event-emitter/src/utils/string-with-dates-parser.ts
2023-11-10 07:45:41 +07:00

159 lines
4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import * as Moo from 'moo';
import { CalendarEvent } from '../models/calendar-event';
import * as Luxon from 'luxon';
import { Logger } from '@nestjs/common';
export const DEFAULT_PARAMS: Moo.Rules = {
WS: /[ \t]+/,
delimiter: /(?: - |: )/,
delimiter2: /(?:^\* |^- |(?!\d)T(?!>\d))/,
date: /\b(?:(?:\d{2}\.\d{2}\.(?:\d{2}|\d{4}))|(?:\d{4}-\d{2}-\d{2}))\b/,
time: /\b(?:\d{2}\b:\d{2}:\d{2}|\d{2}:\d{2})\b/,
word: /[\wА-Яа-я]+/,
other: { match: /./, lineBreaks: true },
NL: { match: /\n/, lineBreaks: true },
};
export const DEFAULT_DATE_FORMATS = ['yyyy-MM-dd', 'dd.MM.yyyy', 'dd.MM.yy'];
export const DEFAULT_TIME_FORMATS = ['HH:mm:ss', 'HH:mm'];
export const DEFAULT_EVENT_DURATION = 60 * 60 * 1000; // 1 hour in millis
const logger = new Logger('string-with-dates-parser');
export function parse(str: string, params?: Moo.Rules): Moo.Token[] {
if (!params) params = DEFAULT_PARAMS;
try {
const lexer = Moo.compile(params);
lexer.reset(str);
const res: Moo.Token[] = [];
let token = lexer.next();
while (token) {
res.push(token);
token = lexer.next();
}
return res;
} catch (ex) {
logger.error(
`Error at parse str=${str} with params=${params}, error message - ${ex}`,
);
return [];
}
}
export type ParserOpts = {
rules?: Moo.Rules;
dateFormats?: string[];
timeFormats?: string[];
};
export const DEFAULT_PARSER_OPTS: ParserOpts = {
rules: DEFAULT_PARAMS,
dateFormats: DEFAULT_DATE_FORMATS,
timeFormats: DEFAULT_TIME_FORMATS,
};
export function parseDate(
str: string,
formats?: string[],
): Luxon.DateTime | null {
if (!str) return null;
if (!formats) formats = DEFAULT_DATE_FORMATS;
let res: Luxon.DateTime;
for (let i = 0; i < formats.length; i++) {
const format = formats[i];
res = Luxon.DateTime.fromFormat(str, format);
if (res.isValid) return res;
}
return null;
}
export function parseTime(
str: string,
formats?: string[],
): Luxon.Duration | null {
if (!str) return null;
if (!formats) formats = DEFAULT_TIME_FORMATS;
let res: Luxon.DateTime;
for (let i = 0; i < formats.length; i++) {
const format = formats[i];
res = Luxon.DateTime.fromFormat(str, format);
if (res.isValid) {
const startOfDay = res.set({
hour: 0,
minute: 0,
second: 0,
millisecond: 0,
});
return res.diff(startOfDay).shiftToAll();
}
}
return null;
}
export function parseToCalendarEvent(
str: string,
opts?: ParserOpts,
): CalendarEvent | null {
if (!opts) opts = DEFAULT_PARSER_OPTS;
const tokens = parse(str, opts?.rules);
const words = tokens.filter((i) => i.type === 'word');
const dates = tokens.filter((i) => i.type === 'date');
if (dates.length < 0) return null;
const date1 = parseDate(dates[0]?.value, opts?.dateFormats);
const date2 = parseDate(dates[1]?.value, opts?.dateFormats);
const times = tokens.filter((i) => i.type === 'time');
const time1 = parseTime(times[0]?.value, opts?.timeFormats);
const time2 = parseTime(times[1]?.value, opts?.timeFormats);
let from: Luxon.DateTime;
let to: Luxon.DateTime;
let fullDay: boolean;
if (date1 && time1) {
from = date1.set({
hour: time1.hours,
minute: time1.minutes,
second: time1.seconds,
});
fullDay = false;
} else if (date1) {
from = date1;
fullDay = true;
} else {
return null;
}
if (date2 && time2) {
to = date2.set({
hour: time2.hours,
minute: time2.minutes,
second: time2.seconds,
});
} else if (date2) {
to = date2.plus({ day: 1 });
} else if (fullDay) {
to = from.plus({ day: 1 });
} else if (time2) {
to = date1.set({
hour: time2.hours,
minute: time2.minutes,
second: time2.seconds,
});
} else {
to = from.plus(Luxon.Duration.fromMillis(DEFAULT_EVENT_DURATION));
}
return {
from: from.toISO(),
fromTimestamp: from.toMillis(),
to: to.toISO(),
toTimestamp: to.toMillis(),
description: words.join(' '),
fullDay: fullDay,
};
}