UNPKG

datetoken

Version:

Parse relative datetime tokens into date objects

657 lines (642 loc) 19.5 kB
var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __esm = (fn, res) => function __init() { return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; }; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/token/token.ts function lookupIdentifier(ident) { if (keywords.hasOwnProperty(ident)) { return keywords[ident] ?? "ILLEGAL" /* ILLEGAL */; } return "ILLEGAL" /* ILLEGAL */; } var keywords, Token, NOW; var init_token = __esm({ "src/token/token.ts"() { "use strict"; keywords = { now: "NOW" /* NOW */, s: "MODIFIER" /* MODIFIER */, m: "MODIFIER" /* MODIFIER */, h: "MODIFIER" /* MODIFIER */, d: "MODIFIER" /* MODIFIER */, w: "MODIFIER" /* MODIFIER */, M: "MODIFIER" /* MODIFIER */, Y: "MODIFIER" /* MODIFIER */, Q: "MODIFIER" /* MODIFIER */, Q1: "MODIFIER" /* MODIFIER */, Q2: "MODIFIER" /* MODIFIER */, Q3: "MODIFIER" /* MODIFIER */, Q4: "MODIFIER" /* MODIFIER */, bw: "MODIFIER" /* MODIFIER */, mon: "MODIFIER" /* MODIFIER */, tue: "MODIFIER" /* MODIFIER */, thu: "MODIFIER" /* MODIFIER */, fri: "MODIFIER" /* MODIFIER */, sat: "MODIFIER" /* MODIFIER */, sun: "MODIFIER" /* MODIFIER */ }; Token = class { type; literal; constructor(tokenType, tokenLiteral) { this.type = tokenType; this.literal = tokenLiteral; } }; NOW = new Token("NOW" /* NOW */, "now"); } }); // src/token/index.ts var init_token2 = __esm({ "src/token/index.ts"() { "use strict"; init_token(); } }); // src/ast/ast.ts import * as dateFn from "date-fns"; function newNowExpression() { return new NowExpression(new Token("NOW" /* NOW */, "now")); } var daysOfWeek, NowExpression, ModifierExpression, SnapExpression, AmountModifiers, SnapModifiers; var init_ast = __esm({ "src/ast/ast.ts"() { "use strict"; init_token2(); daysOfWeek = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"]; NowExpression = class { token; constructor(token) { this.token = token; } operate(date) { return date; } toString() { return this.token.literal; } toJSON() { return { type: "now" }; } }; ModifierExpression = class { token; amount; operator; modifier; constructor(token, amount = 1, operator, modifier) { this.token = token; this.amount = amount; this.operator = operator; this.modifier = modifier; } operate(date) { switch (this.operator) { case "+" /* PLUS */: switch (this.modifier) { case "s": return dateFn.addSeconds(date, this.amount); case "m": return dateFn.addMinutes(date, this.amount); case "h": return dateFn.addHours(date, this.amount); case "d": return dateFn.addDays(date, this.amount); case "w": return dateFn.addWeeks(date, this.amount); case "M": return dateFn.addMonths(date, this.amount); case "Y": return dateFn.addYears(date, this.amount); } break; case "-" /* MINUS */: switch (this.modifier) { case "s": return dateFn.subSeconds(date, this.amount); case "m": return dateFn.subMinutes(date, this.amount); case "h": return dateFn.subHours(date, this.amount); case "d": return dateFn.subDays(date, this.amount); case "w": return dateFn.subWeeks(date, this.amount); case "M": return dateFn.subMonths(date, this.amount); case "Y": return dateFn.subYears(date, this.amount); } break; } return date; } toString() { return `${this.operator}${this.amount}${this.modifier}`; } toJSON() { return { type: "amount", amount: this.amount, modifier: this.modifier, operator: this.operator }; } }; SnapExpression = class { token; modifier; operator; constructor(token, modifier, operator) { this.token = token; this.modifier = modifier; this.operator = operator; } operate(date) { switch (this.operator) { case "/" /* SLASH */: switch (this.modifier) { case "s": return dateFn.startOfSecond(date); case "m": return dateFn.startOfMinute(date); case "h": return dateFn.startOfHour(date); case "d": return dateFn.startOfDay(date); case "w": case "bw": return dateFn.startOfWeek(date); case "mon": case "tue": case "wed": case "thu": case "fri": case "sat": case "sun": { const weekDayOrdinal = daysOfWeek.indexOf(this.modifier); const todayOrdinal = dateFn.getDay(date); const delta = ((todayOrdinal - weekDayOrdinal) % 7 + 7) % 7; return dateFn.subDays(date, delta); } case "M": return dateFn.startOfMonth(date); case "Y": return dateFn.startOfYear(date); case "Q": const month = dateFn.getMonth(date); const quarter = Math.floor(month / 3); return dateFn.setMonth(dateFn.startOfYear(date), 3 * quarter); case "Q1": return dateFn.startOfYear(date); case "Q2": return dateFn.setMonth(dateFn.startOfYear(date), 4 - 1); case "Q3": return dateFn.setMonth(dateFn.startOfYear(date), 7 - 1); case "Q4": return dateFn.setMonth(dateFn.startOfYear(date), 10 - 1); } break; case "@" /* AT */: switch (this.modifier) { case "s": return dateFn.endOfSecond(date); case "m": return dateFn.endOfMinute(date); case "h": return dateFn.endOfHour(date); case "d": return dateFn.endOfDay(date); case "w": return dateFn.endOfWeek(date); case "M": return dateFn.endOfMonth(date); case "Y": return dateFn.endOfYear(date); case "bw": { if (dateFn.isThisWeek(date) && !dateFn.isWeekend(date)) { return date; } return dateFn.endOfDay(dateFn.addDays(dateFn.startOfWeek(date), 5)); } case "mon": case "tue": case "wed": case "thu": case "fri": case "sat": case "sun": { const weekDayOrdinal = daysOfWeek.indexOf(this.modifier); const todayOrdinal = dateFn.getDay(date); const delta = ((weekDayOrdinal - todayOrdinal) % 7 + 7) % 7; return dateFn.addDays(date, delta); } case "Q": const month = dateFn.getMonth(date); const quarter = Math.floor(month / 3); return dateFn.endOfMonth(dateFn.setMonth(date, 3 * quarter + 2)); case "Q1": return dateFn.endOfMonth(dateFn.setMonth(date, 3 - 1)); case "Q2": return dateFn.endOfMonth(dateFn.setMonth(date, 6 - 1)); case "Q3": return dateFn.endOfMonth(dateFn.setMonth(date, 9 - 1)); case "Q4": return dateFn.endOfMonth(dateFn.setMonth(date, 12 - 1)); } break; } return date; } toString() { return `${this.operator}${this.modifier}`; } toJSON() { return { type: "snap", modifier: this.modifier, operator: this.operator }; } }; ((AmountModifiers2) => { const values = ["s", "m", "h", "d", "w", "M", "Y"]; AmountModifiers2.valuesString = `(${values.map((v) => `"${v}"`).join(",")})`; function checkModifier(modifier) { return values.includes(modifier); } AmountModifiers2.checkModifier = checkModifier; })(AmountModifiers || (AmountModifiers = {})); ((SnapModifiers2) => { const values = [ "s", "m", "h", "d", "w", "bw", "M", "Y", "mon", "tue", "wed", "thu", "fri", "sat", "sun", "Q", "Q1", "Q2", "Q3", "Q4" ]; SnapModifiers2.valuesString = `(${values.map((v) => `"${v}"`).join(",")})`; function checkModifier(modifier) { return values.includes(modifier); } SnapModifiers2.checkModifier = checkModifier; })(SnapModifiers || (SnapModifiers = {})); } }); // src/ast/index.ts var init_ast2 = __esm({ "src/ast/index.ts"() { "use strict"; init_ast(); } }); // src/exceptions/index.ts var InvalidTokenError; var init_exceptions = __esm({ "src/exceptions/index.ts"() { "use strict"; InvalidTokenError = class extends Error { }; } }); // src/lexer/lexer.ts function isDigit(payload) { return /^\d+$/.test(payload); } function isLetter(payload) { return /^\w+$/.test(payload); } var Lexer; var init_lexer = __esm({ "src/lexer/lexer.ts"() { "use strict"; init_token2(); Lexer = class { invalid; position; readPosition; input; currentChar; constructor(input = "") { this.invalid = !input || "string" !== typeof input || input.trim().length < 2; if (this.invalid) { this.position = this.readPosition = 0; this.input = ""; this.currentChar = ""; } else { this.position = this.readPosition = 0; this.input = input.trim(); this.currentChar = ""; this.readChar(); } } isInvalid() { return this.invalid; } nextToken() { let token; if (this.currentChar === "") { return new Token("" /* END */, this.currentChar); } else if (this.currentChar === "+") { token = new Token("+" /* PLUS */, this.currentChar); } else if (this.currentChar === "-") { token = new Token("-" /* MINUS */, this.currentChar); } else if (this.currentChar === "/") { token = new Token("/" /* SLASH */, this.currentChar); } else if (this.currentChar === "@") { token = new Token("@" /* AT */, this.currentChar); } else if (isDigit(this.currentChar)) { return new Token("NUMBER" /* NUMBER */, this.readNumber()); } else if (isLetter(this.currentChar)) { const literal = this.readWord(); return new Token(lookupIdentifier(literal), literal); } else { token = new Token("ILLEGAL" /* ILLEGAL */, this.currentChar); } this.readChar(); return token; } readChar() { if (this.position >= this.input.length) { this.readPosition = 0; this.currentChar = ""; } else { this.currentChar = this.input[this.readPosition] ?? ""; this.position = this.readPosition; } this.readPosition++; } peekChar() { if (this.position >= this.input.length) { return ""; } return this.input[this.readPosition] ?? ""; } readNumber() { const pos = this.position; while (isDigit(this.currentChar)) { this.readChar(); } return this.input.substring(pos, this.position); } readWord() { const pos = this.position; while (isLetter(this.currentChar) || isDigit(this.currentChar)) { this.readChar(); } return this.input.substring(pos, this.position); } }; } }); // src/lexer/index.ts var init_lexer2 = __esm({ "src/lexer/index.ts"() { "use strict"; init_lexer(); } }); // src/parser/parser.ts var Parser; var init_parser = __esm({ "src/parser/parser.ts"() { "use strict"; init_ast2(); init_ast2(); init_token2(); Parser = class { lexer; errors; currentToken; peekToken; constructor(lexer) { this.errors = []; this.lexer = lexer; } getErrors() { return this.errors; } nextToken() { this.currentToken = this.peekToken; this.peekToken = this.lexer.nextToken(); } parse() { if (this.lexer.isInvalid()) { this.addError("Invalid token"); } const nodes = []; this.nextToken(); this.nextToken(); while (this.currentToken.type !== "" /* END */) { const node = this.parseExpression(); if (node) { nodes.push(node); } this.nextToken(); } return nodes; } addError(message) { this.errors.push(message); } parseNowExpression() { return newNowExpression(); } parseModifierExpression() { let curToken = this.currentToken; const operator = curToken.literal; let amount = 1; let modifier; this.nextToken(); curToken = this.currentToken; if (curToken.type === "NUMBER" /* NUMBER */) { amount = parseInt(curToken.literal, 10); this.nextToken(); } curToken = this.currentToken; if (curToken.type === "MODIFIER" /* MODIFIER */) { modifier = curToken.literal; if (!AmountModifiers.checkModifier(modifier)) { this.addError( `Expected modifier literal as any of "${AmountModifiers.valuesString}", got "${curToken.literal}"` ); } return new ModifierExpression(curToken, amount, operator, modifier); } else { this.addError(`Expected NUMBER or MODIFIER, got "${curToken.literal}"`); } return void 0; } parseSnapExpression() { let curToken = this.currentToken; const operator = curToken.literal; this.nextToken(); curToken = this.currentToken; if (curToken.type !== "MODIFIER" /* MODIFIER */) { this.addError(`Expected snap modifier literal, got "${curToken.literal}"`); } else if (!SnapModifiers.checkModifier(curToken.literal)) { this.addError( `Expected snap modifier literal as any of "${SnapModifiers.valuesString}", got "${curToken.literal}"` ); } const modifier = curToken.literal; return new SnapExpression(curToken, modifier, operator); } parseExpression() { const curToken = this.currentToken; if ("NOW" /* NOW */ === curToken.type) { return this.parseNowExpression(); } else if ("+" /* PLUS */ === curToken.type || "-" /* MINUS */ === curToken.type) { return this.parseModifierExpression(); } else if ("/" /* SLASH */ === curToken.type || "@" /* AT */ === curToken.type) { return this.parseSnapExpression(); } else if ("ILLEGAL" /* ILLEGAL */ === curToken.type) { this.addError(`Illegal operator: "${curToken.literal}"`); } return void 0; } }; } }); // src/parser/index.ts var init_parser2 = __esm({ "src/parser/index.ts"() { "use strict"; init_parser(); } }); // src/utils/time.ts var Clock; var init_time = __esm({ "src/utils/time.ts"() { "use strict"; Clock = class _Clock { getTime() { return /* @__PURE__ */ new Date(); } forward(millis) { return /* @__PURE__ */ new Date(); } static create() { return new _Clock(); } }; } }); // src/models/models.ts var Token3; var init_models = __esm({ "src/models/models.ts"() { "use strict"; init_ast2(); init_exceptions(); init_lexer2(); init_parser2(); init_time(); Token3 = class _Token { get at() { return this.startAt || this.clock.getTime(); } set at(value) { this.startAt = value; } get nodes() { return this.expressionNodes; } get isSnapped() { return this.expressionNodes.some((node) => node instanceof SnapExpression); } get isModified() { return this.expressionNodes.some((node) => node instanceof ModifierExpression); } static fromString(value, at, clock) { const lexer = new Lexer(value); const parser = new Parser(lexer); const nodes = parser.parse(); if (parser.getErrors().length > 0) { throw new InvalidTokenError(parser.getErrors().join("\n")); } const token = new _Token(nodes); if (at) { token.startAt = at; } if (clock) { token.clock = clock; } return token; } expressionNodes; startAt; clock = Clock.create(); constructor(nodes, at, clock = Clock.create()) { this.expressionNodes = nodes; if (at !== void 0) { this.startAt = at; } this.clock = clock; } toDate() { return this.expressionNodes.reduce((acc, node) => node.operate(acc), this.at); } toString() { return this.expressionNodes.map((n) => n.toString()).join(""); } toJSON() { return this.nodes.map((node) => node.toJSON()); } }; } }); // src/models/index.ts var models_exports = {}; __export(models_exports, { Token: () => Token3, TokenModel: () => Token3 }); var init_models2 = __esm({ "src/models/index.ts"() { "use strict"; init_models(); } }); // src/utils/utils.ts init_models2(); function tokenToDate(token, at, clock) { return Token3.fromString(token, at, clock).toDate(); } // src/index.ts init_models2(); var datetoken = tokenToDate; var index_default = tokenToDate; if (typeof module !== "undefined" && typeof module.exports !== "undefined") { module.exports = tokenToDate; module.exports.datetoken = tokenToDate; module.exports.Token = (init_models2(), __toCommonJS(models_exports)).Token; } export { Token3 as Token, datetoken, index_default as default };