tell-me-when
Version:
human relative date and time parser
837 lines • 38 kB
JavaScript
import { GrammarNode } from "./util/GrammarNode.mjs";
import { ParseNode } from "./util/ParseNode.mjs";
import { ParseRootNode } from "./util/ParseRootNode.mjs";
import * as base from "./util/parse.mjs";
const {
token,
group,
named,
oneOf,
longestOf,
negativeLookahead
} = GrammarNode;
export const space = token(/\s+/);
export class FullYearNode extends ParseNode {
constructor(wrapped) {
super('FullYear', wrapped.from, wrapped.to);
this.wrapped = wrapped;
}
year(input) {
return parseInt(this.substringOf(input));
}
dateFns(input) {
return [['setYear', this.year(input)]];
}
}
const FullYear = token(/\d{4}/).parseAs(FullYearNode);
export class TwoDigitYearNode extends ParseNode {
constructor(wrapped) {
super('TwoDigitYear', wrapped.from, wrapped.to);
this.wrapped = wrapped;
}
year(input) {
const digits = parseInt(this.substringOf(input).replace(/^'/, ''));
return digits >= 70 ? 1900 + digits : 2000 + digits;
}
dateFns(input) {
return [['setYear', this.year(input)]];
}
}
const TwoDigitYear = token(/'?\d\d/).parseAs(TwoDigitYearNode);
const YearNum = FullYear.or(TwoDigitYear);
const YearNumNotHour = oneOf(FullYear, token(/'\d\d/).parseAs(TwoDigitYearNode), group(token(/\d\d/).parseAs(TwoDigitYearNode), negativeLookahead(/:|\s*[ap](m|\s)/i)));
export class MonthNumNode extends ParseNode {
constructor(wrapped) {
super('MonthNum', wrapped.from, wrapped.to);
this.wrapped = wrapped;
}
month(input) {
return parseInt(this.substringOf(input)) - 1;
}
dateFns(input) {
return [['setMonth', this.month(input)], ['closestToNow', [['if', {
afterNow: [['addYears', -1]]
}]], [['if', {
beforeNow: [['addYears', 1]]
}]]], ['startOfMonth'], ['makeInterval', ['addMonths', 1]]];
}
}
const MonthNum = token(/1[0-2]|0?[1-9]/).parseAs(MonthNumNode);
export class MonthNameNode extends ParseNode {
static months = {
jan: 0,
feb: 1,
mar: 2,
apr: 3,
may: 4,
jun: 5,
jul: 6,
aug: 7,
sep: 8,
oct: 9,
nov: 10,
dec: 11
};
month(input) {
return MonthNameNode.months[input.substring(this.from, this.from + 3).toLowerCase()];
}
dateFns(input) {
const month = this.month(input);
return [['setMonth', month], ['closestToNow', [['if', {
afterNow: [['addYears', -1]]
}]], [['if', {
beforeNow: [['addYears', 1]]
}]]], ['startOfMonth'], ['makeInterval', ['addMonths', 1]]];
}
}
const MonthNameFull = named('MonthNameFull', /(january|february|march|may|april|june|july|august|september|october|november|december)(?![a-z])/i).parseAs(MonthNameNode);
const MonthNameAbbrev = named('MonthNameAbbrev', /(jan|feb|mar|apr|may|jun|jul|aug|sept?|oct|nov|dec)(?![a-z])/i).parseAs(MonthNameNode);
const MonthName = MonthNameFull.or(group(MonthNameAbbrev, group('.').maybe()));
export class RelativeMonthNameNode extends ParseNode {
dateFns(input) {
var _this$find;
const month = (_this$find = this.find(MonthNameNode)) === null || _this$find === void 0 ? void 0 : _this$find.month(input);
if (month == null) throw new Error(`failed to find month name node`);
if (this.find('Next')) {
return [['setMonth', month], ['startOfMonth'], ['if', {
beforeNow: [['addYears', 1]]
}], ['makeInterval', ['addMonths', 1]]];
}
if (this.find('AfterNext')) {
return [['setMonth', month], ['startOfMonth'], ['if', {
beforeNow: [['addYears', 1]]
}], ['addYears', 1], ['makeInterval', ['addMonths', 1]]];
}
if (this.find('Last')) {
return [['setMonth', month], ['startOfMonth'], ['addMonths', 1], ['if', {
afterNow: [['addYears', -1]]
}], ['addMonths', -1], ['makeInterval', ['addMonths', 1]]];
}
if (this.find('BeforeLast')) {
return [['setMonth', month], ['startOfMonth'], ['addMonths', 1], ['if', {
afterNow: [['addYears', -1]]
}], ['addMonths', -1], ['addYears', -1], ['makeInterval', ['addMonths', 1]]];
}
return [];
}
}
export const RelativeMonthName = named('RelativeMonthName', oneOf(group(named('Last', /last/i), space, MonthName), group(named('Next', /next/i), space, MonthName), group(MonthName, space, named('BeforeLast', /before\s+last/i)), group(MonthName, space, named('AfterNext', /after\s+next/i)))).parseAs(RelativeMonthNameNode);
const MonthNameNoDot = MonthNameFull.or(MonthNameAbbrev);
const Month = MonthName.or(MonthNum);
const MonthNoDot = MonthNameNoDot.or(MonthNum);
export class DayOfMonthNumNode extends ParseNode {
constructor(wrapped) {
super('DayOfMonthNum', wrapped.from, wrapped.to);
this.wrapped = wrapped;
}
dayOfMonth(input) {
return parseInt(this.substringOf(input));
}
}
const DayOfMonthNum = token(/[12][0-9]|3[01]|0?[1-9]/).parseAs(DayOfMonthNumNode);
export class NthDayOfMonthNode extends ParseNode {
constructor(wrapped) {
super('NthDayOfMonth', wrapped.from, wrapped.to);
this.wrapped = wrapped;
}
dayOfMonth(input) {
const value = this.substringOf(input).toLowerCase();
switch (value) {
case '1st':
case 'first':
return 1;
case '2nd':
case 'second':
return 2;
case '3rd':
case 'third':
return 3;
case '4th':
case 'fourth':
return 4;
case '5th':
case 'fifth':
return 5;
case '6th':
case 'sixth':
return 6;
case '7th':
case 'seventh':
return 7;
case '8th':
case 'eighth':
return 8;
case '9th':
case 'ninth':
return 9;
case '10th':
case 'tenth':
return 10;
case '11th':
case 'eleventh':
return 11;
case '12th':
case 'twelfth':
return 12;
case '13th':
case 'thirteenth':
return 13;
case '14th':
case 'fourteenth':
return 14;
case '15th':
case 'fifteenth':
return 15;
case '16th':
case 'sixteenth':
return 16;
case '17th':
case 'seventeenth':
return 17;
case '18th':
case 'eighteenth':
return 18;
case '19th':
case 'ninteenth':
return 19;
case '20th':
case 'twentieth':
return 20;
case '21st':
case 'twenty-first':
return 21;
case '22nd':
case 'twenty-second':
return 22;
case '23rd':
case 'twenty-third':
return 23;
case '24th':
case 'twenty-fourth':
return 24;
case '25th':
case 'twenty-fifth':
return 25;
case '26th':
case 'twenty-sixth':
return 26;
case '27th':
case 'twenty-seventh':
return 27;
case '28th':
case 'twenty-eighth':
return 28;
case '29th':
case 'twenty-ninthy':
return 29;
case '30th':
case 'thirtieth':
return 30;
case '31st':
case 'thirty-first':
return 31;
}
}
}
const NthDayOfMonth = token(/(1st|first|2nd|second|3rd|third|4th|fourth|5th|fifth|6th|sixth|7th|seventh|8th|eighth|9th|ninth|10th|tenth|11th|eleventh|12th|twelfth|13th|thirteenth|14th|fourteenth|15th|fifteenth|16th|sixteenth|17th|seventeenth|18th|eighteenth|19th|ninteenth|20th|twentieth|21st|twenty-first|22nd|twenty-second|23rd|twenty-third|24th|twenty-fourth|25th|twenty-fifth|26th|twenty-sixth|27th|twenty-seventh|28th|twenty-eighth|29th|twenty-ninthy|30th|thirtieth|31st|thirty-first)(?![a-z])/i).parseAs(NthDayOfMonthNode);
const DayOfMonth = NthDayOfMonth.or(DayOfMonthNum);
export class RelativeIntervalNode extends ParseNode {
dateFns() {
const {
intervalName
} = this;
const offset = this.find(`Next${intervalName}`) ? 1 : this.find(`Last${intervalName}`) ? -1 : this.find(`${intervalName}BeforeLast`) ? -2 : this.find(`${intervalName}AfterNext`) ? 2 : 0;
return [...(offset ? [[`add${intervalName}s`, offset]] : []), [`startOf${intervalName}`], [`makeInterval`, [`add${intervalName}s`, 1]]];
}
}
export class RelativeSecondNode extends RelativeIntervalNode {
get intervalName() {
return 'Second';
}
}
export class RelativeMinuteNode extends RelativeIntervalNode {
get intervalName() {
return 'Minute';
}
}
export class RelativeHourNode extends RelativeIntervalNode {
get intervalName() {
return 'Hour';
}
}
export class RelativeWeekNode extends RelativeIntervalNode {
get intervalName() {
return 'Week';
}
}
export class RelativeMonthNode extends RelativeIntervalNode {
get intervalName() {
return 'Month';
}
}
export class RelativeYearNode extends RelativeIntervalNode {
get intervalName() {
return 'Year';
}
}
const RelativeIntervalNodes = {
Second: RelativeSecondNode,
Minute: RelativeMinuteNode,
Hour: RelativeHourNode,
Week: RelativeWeekNode,
Month: RelativeMonthNode,
Year: RelativeYearNode
};
const RelativeIntervalBase = intervalName => oneOf(named(`This${intervalName}`, new RegExp(`this\\s+${intervalName}`, 'i')), named(`Last${intervalName}`, new RegExp(`last\\s+${intervalName}`, 'i')), named(`Next${intervalName}`, new RegExp(`next\\s+${intervalName}`, 'i')), named(`${intervalName}BeforeLast`, new RegExp(`(the\\s+)?${intervalName}\\s+before\\s+last`, 'i')), named(`${intervalName}AfterNext`, new RegExp(`(the\\s+)?${intervalName}\\s+after\\s+next`, 'i')));
const RelativeInterval = intervalName => named(`Relative${intervalName}`, RelativeIntervalBase(intervalName)).parseAs(RelativeIntervalNodes[intervalName]);
export const RelativeSecond = RelativeInterval('Second');
export const RelativeMinute = RelativeInterval('Minute');
export const RelativeHour = RelativeInterval('Hour');
export const RelativeWeek = RelativeInterval('Week');
export const RelativeMonth = RelativeInterval('Month');
export const RelativeYear = RelativeInterval('Year');
export class DateNode extends ParseNode {
yearFns(input) {
var _ref, _this$find2;
return ((_ref = this.find(FullYearNode) || this.find(TwoDigitYearNode)) === null || _ref === void 0 ? void 0 : _ref.dateFns(input)) || ((_this$find2 = this.find(RelativeYearNode)) === null || _this$find2 === void 0 ? void 0 : _this$find2.dateFns().filter(fn => fn[0] === 'addYears'));
}
monthFns(input) {
var _ref2;
const month = (_ref2 = (this === null || this === void 0 ? void 0 : this.find(MonthNumNode)) || (this === null || this === void 0 ? void 0 : this.find(MonthNameNode))) === null || _ref2 === void 0 ? void 0 : _ref2.month(input);
return month != null ? [['setMonth', month]] : undefined;
}
relativeMonthFns(input) {
var _this$find3;
return (_this$find3 = this.find(RelativeMonthNameNode)) === null || _this$find3 === void 0 ? void 0 : _this$find3.dateFns(input).filter(fn => fn[0] !== 'makeInterval');
}
day(input) {
var _ref3;
return (_ref3 = (this === null || this === void 0 ? void 0 : this.find(DayOfMonthNumNode)) || (this === null || this === void 0 ? void 0 : this.find(NthDayOfMonthNode))) === null || _ref3 === void 0 ? void 0 : _ref3.dayOfMonth(input);
}
dateFns(input) {
const year = this.yearFns(input);
const relativeMonth = this.relativeMonthFns(input);
const month = relativeMonth || this.monthFns(input);
const day = this.day(input);
if (year == null) {
return [...(month || []), ...(day != null ? [['setDate', day]] : []), ...(relativeMonth ? day != null ? [['startOfDay']] : [] : [[day != null ? 'startOfDay' : month != null ? 'startOfMonth' : 'startOfYear'], ['closestToNow', [['if', {
afterNow: [[month != null ? 'addYears' : 'addMonths', -1]]
}]], [['if', {
beforeNow: [[month != null ? 'addYears' : 'addMonths', 1]]
}]]]]), ['makeInterval', [day != null ? 'addDays' : 'addMonths', 1]]];
}
return [...(year || []), ...(month || []), ...(day != null ? [['setDate', day]] : []), [day != null ? 'startOfDay' : month != null ? 'startOfMonth' : 'startOfYear'], ['makeInterval', [day != null ? 'addDays' : month != null ? 'addMonths' : 'addYears', 1]]];
}
}
const Date = named('Date', longestOf(group(FullYear, oneOf(group(MonthNameNoDot, DayOfMonth.maybe()), group('.', MonthNoDot, group('.', DayOfMonth).maybe()), group('-', MonthNoDot, group('-', DayOfMonth).maybe()), group('_', MonthNoDot, group('_', DayOfMonth).maybe()), group('/', MonthNoDot, group('/', DayOfMonth).maybe()), group(space, Month, group(space, DayOfMonth).maybe())).maybe()), group(RelativeYear, group(space, Month, group(space, DayOfMonth).maybe()).maybe()), group(MonthName, longestOf(group(space, DayOfMonth, group(space.or(group(space.maybe(), ',', space.maybe())), oneOf(YearNumNotHour, RelativeYear)).maybe()), group(space.or(group(space.maybe(), ',', space.maybe())), oneOf(YearNum, RelativeYear))).maybe()), group(RelativeMonthName, group(space, DayOfMonth).maybe()), group(MonthNameNoDot, oneOf(group('.', DayOfMonth, group('.', YearNum).maybe()), group(NthDayOfMonth, YearNumNotHour.maybe()), DayOfMonth, group('-', DayOfMonth, group('-', YearNum).maybe()), group('_', DayOfMonth, group('_', YearNum).maybe()), group('/', DayOfMonth, group('/', YearNum).maybe())).maybe()), group(MonthNum, longestOf(group(/[- ._/]/, FullYear), group(NthDayOfMonth, YearNumNotHour.maybe()), group('.', DayOfMonth, group('.', YearNum).maybe()), group('-', DayOfMonth, group('-', YearNum).maybe()), group('_', DayOfMonth, group('_', YearNum).maybe()), group('/', DayOfMonth, group('/', YearNum).maybe()), group(space, DayOfMonth, group(space, YearNumNotHour).maybe()))), group(group('the', space).maybe(), NthDayOfMonth, oneOf(group(MonthNameNoDot, YearNumNotHour.maybe()), group('.', MonthNoDot, group('.', YearNum).maybe()), group('-', MonthNoDot, group('-', YearNum).maybe()), group('_', MonthNoDot, group('_', YearNum).maybe()), group('/', MonthNoDot, group('/', YearNum).maybe()), group(space, group(group('day', space).maybe(), 'of', space).maybe(), MonthName, group(space.or(group(space.maybe(), ',', space.maybe())), oneOf(YearNumNotHour, RelativeYear)).maybe())).maybe()), group(DayOfMonthNum, oneOf(group(MonthNameNoDot, YearNumNotHour.maybe()), group('.', MonthNoDot, group('.', YearNum).maybe()), group('-', MonthNoDot, group('-', YearNum).maybe()), group('_', MonthNoDot, group('_', YearNum).maybe()), group('/', MonthNoDot, group('/', YearNum).maybe()), group(space, MonthName, group(space, RelativeYear).maybe()), group(space, Month, group(space, YearNumNotHour).maybe()))))).parseAs(DateNode);
const DayDate = named('DayDate', longestOf(group(FullYear, oneOf(group(MonthNameNoDot, DayOfMonth), group('.', MonthNoDot, group('.', DayOfMonth)), group('-', MonthNoDot, group('-', DayOfMonth)), group('_', MonthNoDot, group('_', DayOfMonth)), group('/', MonthNoDot, group('/', DayOfMonth)), group(space, Month, group(space, DayOfMonth))).maybe()), group(RelativeYear, space, Month, group(space, DayOfMonth)), group(MonthName, group(space, DayOfMonth, group(space.or(group(space.maybe(), ',', space.maybe())), oneOf(YearNumNotHour, RelativeYear)).maybe())), group(RelativeMonthName, group(space, DayOfMonth)), group(MonthNameNoDot, oneOf(group('.', DayOfMonth, group('.', YearNum).maybe()), group(NthDayOfMonth, YearNumNotHour.maybe()), DayOfMonth, group('-', DayOfMonth, group('-', YearNum).maybe()), group('_', DayOfMonth, group('_', YearNum).maybe()), group('/', DayOfMonth, group('/', YearNum).maybe()))), group(MonthNum, oneOf(group(NthDayOfMonth, YearNumNotHour.maybe()), group('.', DayOfMonth, group('.', YearNum).maybe()), group('-', DayOfMonth, group('-', YearNum).maybe()), group('_', DayOfMonth, group('_', YearNum).maybe()), group('/', DayOfMonth, group('/', YearNum).maybe()), group(space, DayOfMonth, group(space, oneOf(YearNumNotHour, RelativeYear)).maybe()))), group(group('the', space).maybe(), NthDayOfMonth, oneOf(group(MonthNameNoDot, YearNumNotHour.maybe()), group('.', MonthNoDot, group('.', YearNum).maybe()), group('-', MonthNoDot, group('-', YearNum).maybe()), group('_', MonthNoDot, group('_', YearNum).maybe()), group('/', MonthNoDot, group('/', YearNum).maybe()), group(space, group(group('day', space).maybe(), 'of', space).maybe(), MonthName, group(space.or(group(space.maybe(), ',', space.maybe())), oneOf(YearNumNotHour, RelativeYear)).maybe())).maybe()), group(DayOfMonthNum, oneOf(group(MonthNameNoDot, YearNumNotHour.maybe()), group('.', MonthNoDot, group('.', YearNum).maybe()), group('-', MonthNoDot, group('-', YearNum).maybe()), group('_', MonthNoDot, group('_', YearNum).maybe()), group('/', MonthNoDot, group('/', YearNum).maybe()), group(space, Month, group(space, oneOf(YearNumNotHour, RelativeYear)).maybe()))))).parseAs(DateNode);
export class HoursNode extends ParseNode {
hours(input) {
return parseInt(this.substringOf(input));
}
}
const Hours = named('Hours', /2[0-3]|[01]?[0-9]/).parseAs(HoursNode);
export class MinutesNode extends ParseNode {
minutes(input) {
return parseInt(this.substringOf(input));
}
}
const Minutes = named('Minutes', /[0-5][0-9]/).parseAs(MinutesNode);
export class SecondsNode extends ParseNode {
seconds(input) {
return parseInt(this.substringOf(input));
}
}
const Seconds = named('Seconds', /[0-5][0-9]/).parseAs(SecondsNode);
export class MillisecondsNode extends ParseNode {
milliseconds(input) {
return parseInt(this.substringOf(input).padEnd(3, '0'));
}
}
const Milliseconds = named('Milliseconds', /\d{1,3}/).parseAs(MillisecondsNode);
export let AmPmValue = /*#__PURE__*/function (AmPmValue) {
AmPmValue[AmPmValue["AM"] = 0] = "AM";
AmPmValue[AmPmValue["PM"] = 1] = "PM";
return AmPmValue;
}({});
export class AmPmNode extends ParseNode {
amPm(input) {
switch (input.substring(this.from, this.from + 1)) {
case 'a':
case 'A':
return AmPmValue.AM;
case 'p':
case 'P':
return AmPmValue.PM;
default:
throw new Error(`unexpected`);
}
}
}
const AmPm = named('AmPm', /[ap]m?(?!\w)/i).parseAs(AmPmNode);
export class TimeNode extends ParseNode {
hours(input) {
var _this$find4;
return (_this$find4 = this.find(HoursNode)) === null || _this$find4 === void 0 ? void 0 : _this$find4.hours(input);
}
minutes(input) {
var _this$find5;
return (_this$find5 = this.find(MinutesNode)) === null || _this$find5 === void 0 ? void 0 : _this$find5.minutes(input);
}
seconds(input) {
var _this$find6;
return (_this$find6 = this.find(SecondsNode)) === null || _this$find6 === void 0 ? void 0 : _this$find6.seconds(input);
}
milliseconds(input) {
var _this$find7;
return (_this$find7 = this.find(MillisecondsNode)) === null || _this$find7 === void 0 ? void 0 : _this$find7.milliseconds(input);
}
amPm(input) {
var _this$find8;
return (_this$find8 = this.find(AmPmNode)) === null || _this$find8 === void 0 ? void 0 : _this$find8.amPm(input);
}
dateFns(input) {
let hours = this.hours(input);
const minutes = this.minutes(input);
const seconds = this.seconds(input);
const milliseconds = this.milliseconds(input);
const amPm = this.amPm(input);
if (hours != null && amPm != null) {
if (hours < 0 || hours > 12) {
throw new Error('hour out of range');
}
if (amPm === AmPmValue.PM && hours !== 12) hours += 12;else if (amPm === AmPmValue.AM && hours === 12) hours = 0;
}
return [...(hours != undefined ? [['setHours', hours]] : []), ...(minutes != undefined ? [['setMinutes', minutes]] : []), ...(seconds != undefined ? [['setSeconds', seconds]] : []), ...(milliseconds != undefined ? [['setMilliseconds', milliseconds]] : [[seconds != undefined ? 'startOfSecond' : minutes != undefined ? 'startOfMinute' : 'startOfHour']])];
}
}
const AtTime = named('AtTime', Hours, group(':', Minutes, group(':', Seconds, group('.', Milliseconds).maybe()).maybe()).maybe(), group(space.maybe(), AmPm).maybe()).parseAs(TimeNode);
const Time = named('Time', longestOf(group(Hours, group(':', Minutes, group(':', Seconds, group('.', Milliseconds).maybe()).maybe()), group(space.maybe(), AmPm).maybe()), group(Hours, group(':', Minutes, group(':', Seconds, group('.', Milliseconds).maybe()).maybe()).maybe(), group(space.maybe(), AmPm)))).parseAs(TimeNode);
export class NowNode extends ParseNode {
dateFns() {
return [['now']];
}
}
const Now = named('Now', /now|(the\s+)?present(\s+time)?/).parseAs(NowNode);
export class QuantityNumNode extends ParseNode {
quantity(input) {
return parseInt(this.substringOf(input));
}
}
export const QuantityNum = named('QuantityNum', /\d+/).parseAs(QuantityNumNode);
export class QuantityWordNode extends ParseNode {
static quantities = {
zero: 0,
an: 1,
a: 1,
one: 1,
two: 2,
three: 3,
four: 4,
five: 5,
six: 6,
seven: 7,
eight: 8,
nine: 9,
ten: 10,
eleven: 11,
twelve: 12,
thirteen: 13,
fourteen: 14,
fifteen: 15,
sixteen: 16,
seventeen: 17,
eighteen: 18,
nineteen: 19,
twenty: 20
};
quantity(input) {
return QuantityWordNode.quantities[this.substringOf(input).toLowerCase()];
}
}
export const QuantityWord = named('QuantityWord', new RegExp(Object.keys(QuantityWordNode.quantities).join('|'), 'i')).parseAs(QuantityWordNode);
export class QuantityNode extends ParseNode {
quantity(input) {
var _ref4;
return (_ref4 = this.find(QuantityNumNode) || this.find(QuantityWordNode)) === null || _ref4 === void 0 ? void 0 : _ref4.quantity(input);
}
}
export const Quantity = named('Quantity', oneOf(QuantityNum, QuantityWord)).parseAs(QuantityNode);
export class DateTimeUnitNode extends ParseNode {
unit(input) {
switch (this.substringOf(input).toLowerCase()) {
case 'y':
case 'yr':
case 'year':
case 'years':
return 'years';
case 'mo':
case 'mos':
case 'month':
case 'months':
return 'months';
case 'w':
case 'wk':
case 'wks':
case 'week':
case 'weeks':
return 'weeks';
case 'd':
case 'day':
case 'days':
return 'days';
case 'h':
case 'hr':
case 'hrs':
case 'hour':
case 'hours':
return 'hours';
case 'm':
case 'min':
case 'mins':
case 'minute':
case 'minutes':
return 'minutes';
case 's':
case 'sec':
case 'secs':
case 'second':
case 'seconds':
return 'seconds';
case 'ms':
case 'milli':
case 'millis':
case 'millisecond':
case 'milliseconds':
return 'milliseconds';
default:
throw new Error('unexpected');
}
}
dateFnName(input) {
switch (this.unit(input)) {
case 'years':
return 'addYears';
case 'months':
return 'addMonths';
case 'weeks':
return 'addWeeks';
case 'days':
return 'addDays';
case 'hours':
return 'addHours';
case 'minutes':
return 'addMinutes';
case 'seconds':
return 'addSeconds';
case 'milliseconds':
return 'addMilliseconds';
default:
throw new Error('unexpected');
}
}
}
export const DateTimeUnit = named('DateTimeUnit', /years?|yrs?|y|months?|mos?|weeks?|wks?|w|days?|d|hours?|hrs?|h|minutes?|mins?|m|seconds?|secs?|s|milliseconds?|millis?|ms/i).parseAs(DateTimeUnitNode);
export class DateTimeIntervalPartNode extends ParseNode {
dateFns(input) {
var _this$find9, _this$find10;
const quantity = (_this$find9 = this.find(QuantityNode)) === null || _this$find9 === void 0 ? void 0 : _this$find9.quantity(input);
const dateFnName = (_this$find10 = this.find(DateTimeUnitNode)) === null || _this$find10 === void 0 ? void 0 : _this$find10.dateFnName(input);
if (quantity == null || dateFnName == null) throw new Error(`unexpected`);
return [[dateFnName, quantity]];
}
}
export const DateTimeIntervalPart = named('DateTimeIntervalPart', Quantity, space.maybe(), DateTimeUnit).parseAs(DateTimeIntervalPartNode);
export class DateTimeIntervalNode extends ParseNode {
dateFns(input) {
const fns = [];
for (const node of this.findAll(DateTimeIntervalPartNode)) {
fns.push(...node.dateFns(input));
}
return fns;
}
}
const DateTimeInterval = named('DateTimeInterval', DateTimeIntervalPart, group(space.maybe(), group(',', space.maybe()).maybe(), group('and', space).maybe(), DateTimeIntervalPart).repeat(0, Infinity)).parseAs(DateTimeIntervalNode);
export class DateTimeOffsetNode extends ParseNode {
dateFns(input) {
var _this$find11;
const fns = ((_this$find11 = this.find(DateTimeIntervalNode)) === null || _this$find11 === void 0 ? void 0 : _this$find11.dateFns(input)) || [];
if (this.find('BeforeNow')) {
for (const fn of fns) fn[1] = -fn[1];
}
return fns;
}
}
export const BeforeNow = named('BeforeNow', oneOf('ago', /in\s+the\s+past/i, group('before', space, Now)));
export const AfterNow = named('AfterNow', oneOf(group(/after|from/i, space, Now), /in\s+the\s+future/i));
export const DateTimeOffset = named('DateTimeOffset', DateTimeInterval, space.maybe(), oneOf(BeforeNow, AfterNow)).parseAs(DateTimeOffsetNode);
export class RangeEndDateTimeOffsetNode extends DateTimeOffsetNode {
dateFns(input) {
const fns = super.dateFns(input);
if (this.find('Later')) return fns;
return [['now'], ...fns];
}
}
export const RangeEndDateTimeOffset = named('RangeEndDateTimeOffset', DateTimeInterval, space.maybe(), oneOf(BeforeNow, AfterNow, named('Later', /after\s+(then|that)|thereafter|later/))).parseAs(RangeEndDateTimeOffsetNode);
export class RelativeDayNode extends ParseNode {
dateFns(input) {
var _this$find12;
const quantity = (_this$find12 = this.find(QuantityNode)) === null || _this$find12 === void 0 ? void 0 : _this$find12.quantity(input);
if (quantity != null) {
return [['addDays', this.find('BeforeNow') ? -quantity : quantity]];
}
const offset = this.find('Tomorrow') ? 1 : this.find('Yesterday') ? -1 : this.find('DayBeforeYesterday') ? -2 : this.find('DayAfterTomorrow') ? 2 : 0;
return [...(offset ? [['addDays', offset]] : []), ['startOfDay'], ['makeInterval', ['addDays', 1]]];
}
}
const RelativeDayBase = oneOf(named('Today', group('today')), named('Yesterday', group('yesterday')), named('Tomorrow', group('tomorrow')), group(group('the', space).maybe(), group('day', space), oneOf(named('DayBeforeYesterday', group('before', space, /yesterday|last/)), named('DayAfterTomorrow', group('after', space, /tomorrow|next/)))));
export const RelativeDay = named('RelativeDay', RelativeDayBase).parseAs(RelativeDayNode);
export class RangeEndRelativeDayNode extends RelativeDayNode {
dateFns(input) {
return [['now'], ...super.dateFns(input)];
}
}
export const RangeEndRelativeDay = named('RangeEndRelativeDay', RelativeDayBase).parseAs(RangeEndRelativeDayNode);
export class DayOfWeekNode extends ParseNode {
dayOfWeek(input) {
switch (this.substringOf(input).toLowerCase()) {
case 'sunday':
case 'sun':
return 0;
case 'monday':
case 'mon':
return 1;
case 'tuesday':
case 'tues':
case 'tue':
return 2;
case 'wednesday':
case 'wed':
return 3;
case 'thursday':
case 'thurs':
case 'thur':
case 'thu':
return 4;
case 'friday':
case 'fri':
return 5;
case 'saturday':
case 'sat':
return 6;
}
throw new Error(`invalid day of week: ${this.substringOf(input)}`);
}
dateFns(input) {
const dayOfWeek = this.dayOfWeek(input);
return [['setDay', dayOfWeek], ['closestToNow', [['if', {
afterNow: [['addWeeks', -1]]
}]], [['if', {
beforeNow: [['addWeeks', 1]]
}]]], ['startOfDay'], ['makeInterval', ['addDays', 1]]];
}
}
export const DayOfWeek = named('DayOfWeek', /sun(day)?|tue(s(day)?)?|wed(nesday)?|thu(r(s(day)?)?)?|fri(day)?|sat(urday)?/i).parseAs(DayOfWeekNode);
export class RelativeDayOfWeekNode extends ParseNode {
dateFns(input) {
var _this$find13;
const dayOfWeek = (_this$find13 = this.find(DayOfWeekNode)) === null || _this$find13 === void 0 ? void 0 : _this$find13.dayOfWeek(input);
if (dayOfWeek == null) throw new Error(`failed to find DayOfWeekNode`);
if (this.find('Next')) {
return [['setDay', dayOfWeek], ['startOfDay'], ['if', {
beforeNow: [['addWeeks', 1]]
}], ['makeInterval', ['addDays', 1]]];
}
if (this.find('AfterNext')) {
return [['setDay', dayOfWeek], ['startOfDay'], ['if', {
beforeNow: [['addWeeks', 1]]
}], ['addWeeks', 1], ['makeInterval', ['addDays', 1]]];
}
if (this.find('Last')) {
return [['setDay', dayOfWeek], ['startOfDay'], ['addDays', 1], ['if', {
afterNow: [['addWeeks', -1]]
}], ['addDays', -1], ['makeInterval', ['addDays', 1]]];
}
if (this.find('BeforeLast')) {
return [['setDay', dayOfWeek], ['startOfDay'], ['addDays', 1], ['if', {
afterNow: [['addWeeks', -1]]
}], ['addDays', -1], ['addWeeks', -1], ['makeInterval', ['addDays', 1]]];
}
return [];
}
}
export const RelativeDayOfWeek = named('RelativeDayOfWeek', oneOf(group(named('Last', 'last'), space, DayOfWeek), group(named('Next', 'next'), space, DayOfWeek), group(DayOfWeek, space, named('BeforeLast', /before\s+last/i)), group(DayOfWeek, space, named('AfterNext', /after\s+next/i)))).parseAs(RelativeDayOfWeekNode);
const SpecificDay = oneOf(RelativeDay, DayDate, RelativeDayOfWeek, DayOfWeek);
const RangeEndSpecificDay = oneOf(RangeEndRelativeDay, DayDate, RelativeDayOfWeek, DayOfWeek);
export class RangeEndRelativeIntervalNode extends RelativeIntervalNode {
dateFns() {
return [['now'], ...super.dateFns()];
}
}
export class RangeEndRelativeSecondNode extends RangeEndRelativeIntervalNode {
get intervalName() {
return 'Second';
}
}
export class RangeEndRelativeMinuteNode extends RangeEndRelativeIntervalNode {
get intervalName() {
return 'Minute';
}
}
export class RangeEndRelativeHourNode extends RangeEndRelativeIntervalNode {
get intervalName() {
return 'Hour';
}
}
export class RangeEndRelativeWeekNode extends RangeEndRelativeIntervalNode {
get intervalName() {
return 'Week';
}
}
export class RangeEndRelativeMonthNode extends RangeEndRelativeIntervalNode {
get intervalName() {
return 'Month';
}
}
export class RangeEndRelativeYearNode extends RangeEndRelativeIntervalNode {
get intervalName() {
return 'Year';
}
}
const RangeEndRelativeIntervalNodes = {
Second: RangeEndRelativeSecondNode,
Minute: RangeEndRelativeMinuteNode,
Hour: RangeEndRelativeHourNode,
Week: RangeEndRelativeWeekNode,
Month: RangeEndRelativeMonthNode,
Year: RangeEndRelativeYearNode
};
export const RangeEndRelativeInterval = intervalName => named(`RangeEndRelative${intervalName}`, RelativeIntervalBase(intervalName)).parseAs(RangeEndRelativeIntervalNodes[intervalName]);
export const RangeEndRelativeSecond = RangeEndRelativeInterval('Second');
export const RangeEndRelativeMinute = RangeEndRelativeInterval('Minute');
export const RangeEndRelativeHour = RangeEndRelativeInterval('Hour');
export const RangeEndRelativeWeek = RangeEndRelativeInterval('Week');
export const RangeEndRelativeMonth = RangeEndRelativeInterval('Month');
export const RangeEndRelativeYear = RangeEndRelativeInterval('Year');
function negateDateFns(dateFns) {
return dateFns.map(fn => {
switch (fn[0]) {
case 'addDays':
case 'addHours':
case 'addMilliseconds':
case 'addMinutes':
case 'addMonths':
case 'addSeconds':
case 'addWeeks':
case 'addYears':
return [fn[0], -fn[1]];
}
return fn;
});
}
class DateTimeOffsetIntervalUnitNode extends ParseNode {
dateFns(input) {
switch (this.substringOf(input).toLowerCase()) {
case 'year':
case 'yr':
return [['addYears', 1]];
case 'month':
case 'mon':
return [['addMonths', 1]];
case 'week':
case 'wk':
return [['addWeeks', 1]];
case 'day':
return [['addDays', 1]];
case 'hour':
case 'hr':
return [['addHours', 1]];
case 'minute':
case 'min':
return [['addMinutes', 1]];
case 'second':
case 'sec':
return [['addSeconds', 1]];
}
throw new Error(`invalid input`);
}
}
const DateTimeOffsetIntervalUnit = named('DateTimeOffsetIntervalUnit', /year|yr|month|mon|week|wk|day|hour|hr|minute|min|second|sec/i).parseAs(DateTimeOffsetIntervalUnitNode);
export class DateTimeOffsetIntervalNode extends ParseNode {
dateFns(input) {
var _this$find14, _this$find15;
const fns = ((_this$find14 = this.find(DateTimeIntervalNode)) === null || _this$find14 === void 0 ? void 0 : _this$find14.dateFns(input)) ?? ((_this$find15 = this.find(DateTimeOffsetIntervalUnitNode)) === null || _this$find15 === void 0 ? void 0 : _this$find15.dateFns(input));
if (!fns) throw new Error(`expected to find a DateTimeIntervalNode`);
return this.find('Past') ? [...negateDateFns(fns), ['makeInterval', ['now']]] : [['makeInterval', ...fns]];
}
}
export class RangeEndDateTimeOffsetIntervalNode extends DateTimeOffsetIntervalNode {
dateFns(input) {
return [['now'], ...super.dateFns(input)];
}
}
export const DateTimeOffsetIntervalBase = group(oneOf(named('Past', /(the\s+)?(past|last)/i), named('Future', /(the\s+)?(next|coming)/i)), space, oneOf(DateTimeInterval, DateTimeOffsetIntervalUnit));
export const DateTimeOffsetInterval = named('DateTimeOffsetInterval', DateTimeOffsetIntervalBase).parseAs(DateTimeOffsetIntervalNode);
export const RangeEndDateTimeOffsetInterval = named('RangeEndDateTimeOffsetInterval', DateTimeOffsetIntervalBase).parseAs(RangeEndDateTimeOffsetIntervalNode);
export class DateTimeNode extends ParseNode {
date(input) {
var _ref5;
return (_ref5 = this.find(DateNode) || this.find(RelativeDayNode) || this.find(RelativeDayOfWeekNode) || this.find(DayOfWeekNode) || this.find(RelativeIntervalNode) || this.find(RelativeMonthNameNode) || this.find(MonthNameNode) || this.find(DateTimeOffsetNode) || this.find(DateTimeOffsetIntervalNode) || this.find(NowNode)) === null || _ref5 === void 0 ? void 0 : _ref5.dateFns(input);
}
time(input) {
var _this$find16;
return (_this$find16 = this.find(TimeNode)) === null || _this$find16 === void 0 ? void 0 : _this$find16.dateFns(input);
}
dateFns(input) {
const Time = this.time(input);
const Date = this.date(input);
if (Date && Time) {
const lastIfIndex = Date.findIndex(op => op[0] === 'if');
return [...Date.filter((op, index) => op[0] !== 'makeInterval' && (index < lastIfIndex || !op[0].startsWith('startOf'))), ...Time];
}
return Date || Time || [];
}
}
export const DateTime = named('DateTime', longestOf(Date, RelativeSecond, RelativeMinute, RelativeHour, RelativeWeek, RelativeMonthName, MonthName, RelativeMonth, DateTimeOffsetInterval, group(oneOf(DateTimeOffset, SpecificDay), group(/\s+(at\s+)?|\s*,\s*|\s+/i, AtTime).maybe()), group(Time, group(/\s+(on\s+)?|\s*,\s*|\s+/i, SpecificDay).maybe()), group(AtTime, group(/\s+(on\s+)?|\s*,\s*/i, SpecificDay)), Now)).parseAs(DateTimeNode);
export const RangeEndDateTime = named('RangeEndDateTime', longestOf(Date, RangeEndRelativeSecond, RangeEndRelativeMinute, RangeEndRelativeHour, RangeEndRelativeWeek, RangeEndRelativeMonth, RangeEndDateTimeOffsetInterval, group(oneOf(RangeEndDateTimeOffset, RangeEndSpecificDay), group(space, group('at', space).maybe(), AtTime).maybe()), group(Time, group(space, group('on', space).maybe(), RangeEndSpecificDay).maybe()), group(AtTime, group(space, group('on', space), RangeEndSpecificDay)), Now)).parseAs(DateTimeNode);
export class RangeNode extends ParseNode {
dateFns(input) {
var _this$find17, _this$find18, _endFns;
const RangeStart = (_this$find17 = this.find('RangeStart')) === null || _this$find17 === void 0 ? void 0 : _this$find17.find(DateTimeNode);
if (!RangeStart) throw new Error('unexpected');
let start = RangeStart.dateFns(input);
const RangeEnd = (_this$find18 = this.find('RangeEnd')) === null || _this$find18 === void 0 ? void 0 : _this$find18.find(DateTimeNode);
if (!RangeEnd) throw new Error('unexpected');
let end = RangeEnd.dateFns(input);
if (!start.some(f => f[0] === 'setYear' || f[0] === 'addYears') && end.some(f => f[0] === 'setYear' || f[0] === 'addYears')) {
start = [...end.filter(f => f[0] === 'setYear' || f[0] === 'addYears' || f[0] === 'startOfYear'), ...start.filter(f => f[0] !== 'closestToNow')];
end = end.filter(f => f[0] !== 'setYear' && f[0] !== 'addYears' && f[0] !== 'startOfYear');
}
const through = this.find('Through') != null;
const endFns = end.flatMap(fn => fn[0] === 'makeInterval' ? fn.slice(1) : [fn]);
if (!through && end.find(fn => fn[0] === 'makeInterval') && (_endFns = endFns[endFns.length - 1]) !== null && _endFns !== void 0 && (_endFns = _endFns[0]) !== null && _endFns !== void 0 && _endFns.startsWith('add')) {
endFns.pop();
}
return [...start.filter(fn => fn[0] !== 'makeInterval'), ['makeInterval', ...endFns]];
}
}
export const Range = named('Range', group(group('from', space).maybe(), named('RangeStart', DateTime), oneOf(group(space, oneOf('to', named('Through', 'through'), 'until'), space), /\s*-\s*/), named('RangeEnd', RangeEndDateTime))).parseAs(RangeNode);
export class RootNode extends ParseRootNode {
dateFns(input) {
var _ref6;
return ((_ref6 = this.find(RangeNode) || this.find(DateTimeNode)) === null || _ref6 === void 0 ? void 0 : _ref6.dateFns(input)) || [];
}
}
export const Root = group(space.maybe(), oneOf(Range, DateTime), space.maybe()).parseAs(RootNode);
export function parse(input) {
return base.parse(input, {
grammar: Root
});
}
export function tellMeWhen(when, options) {
return base.tellMeWhen(when, {
...options,
grammar: Root
});
}
//# sourceMappingURL=en-US.mjs.map